把 Gitea (git.zhengchentao.win) 通过 MCP 暴露给 Claude.ai:列 repo、读代码、看 commits / issues / PR / orgs / packages / actions。 设计文档见 vault Coding/gitea-mcp/gitea-mcp 设计.md。 代码模板复用 obsidian-mcp(.NET 10 + ModelContextProtocol SDK + JwtBearer)。 19 个只读 Tool(全部 scope=read:gitea): Repo / 文件: - list_repos / read_repo - list_tree(max_entries=500 防爆) - read_file(max_bytes=1MB,超出 truncated=true) - search_code(走 /repos/search-code,indexer 未启用时返回结构化错误说明) 分支 / 提交: - list_branches / list_commits / read_commit(diff 文件数限 50) Issue / PR: - list_issues / read_issue(含评论) - list_pulls / read_pull(含评论 + 改动文件列表) Org / Package(用户额外授权 read:organization + read:package): - list_orgs / read_org - list_packages / read_package Gitea Actions(运维友好): - list_workflow_runs / read_run_log 技术栈: - .NET 10 + ModelContextProtocol SDK 1.0 - HttpClientFactory + Microsoft.Extensions.Http.Resilience(指数 backoff,5xx/429/网络错误重试) - JwtBearer (HS256, Current+Previous fallback, MapInboundClaims=false) - aud=gitea, scope=read:gitea, iss=https://auth.zhengchentao.win Gitea API client: - Authorization: token <PAT> (admin PAT,仅 read scope) - BaseUrl=https://git.zhengchentao.win - 错误映射:401/403 → UnauthorizedAccessException,404 → KeyNotFoundException,5xx → InvalidOperationException - RepoBlacklist 黑名单(owner/repo 精确匹配,默认空) 部署: - Dockerfile multi-stage,COPY --chown,non-root user - .gitea/workflows/build-image.yml:build + deploy 双 job,buildkit v0.13.2 - 容器内 :8080,宿主端口 9092 - 子域名 git-mcp.zhengchentao.win(区别于 Gitea 本体 git.zhengchentao.win) 测试:6/6 单测过(GiteaRepoFilter 黑名单匹配) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
gitea-mcp
Read access to all Gitea repos (public + private, personal + org) via MCP. OAuth via nas-auth (DCR + PKCE + JWT HS256), admin PAT held internally — Claude never touches the PAT.
Architecture
Claude.ai (MCP client)
│
│ ① GET /.well-known/oauth-authorization-server
↓
git-mcp.zhengchentao.win ←── this service
│
│ ② DCR + PKCE auth flow redirect
↓
auth.zhengchentao.win ←── nas-auth
│
│ ③ JWT (aud=gitea, scope=read:gitea)
↓
Claude.ai → Bearer JWT
│
│ ④ POST /mcp (MCP Streamable HTTP)
↓
git-mcp.zhengchentao.win
│ Bearer <GITEA_ADMIN_PAT>
↓
git.zhengchentao.win ←── Gitea
See gitea-mcp 设计.md for full design rationale.
Tools
| Tool | Description |
|---|---|
list_repos |
List all repos (personal + org, public + private) |
read_repo |
Repo metadata: topics, stars, default branch, mirror flag |
list_tree |
File tree at a ref (recursive optional, max 500 entries) |
read_file |
Raw file content (UTF-8, truncated at 1MB) |
search_code |
Code search via Gitea indexer (requires indexer enabled) |
list_branches |
Branch list + last commit per branch |
list_commits |
Recent commits with author + message |
read_commit |
Full commit details + per-file diff (max 50 files) |
list_issues |
Issues filtered by state (open/closed/all) |
read_issue |
Issue body + all comments |
list_pulls |
Pull requests filtered by state |
read_pull |
PR body + review comments + changed files |
list_orgs |
All organizations visible to admin token |
read_org |
Org metadata |
list_packages |
Packages in registry by owner (container/generic/npm/...) |
read_package |
Package version metadata |
list_workflow_runs |
Gitea Actions workflow run history |
read_run_log |
Run details + job list + log (truncated at 1MB) |
All tools require a valid JWT with scope=read:gitea issued by nas-auth.
Configuration (env vars)
| Variable | Default | Description |
|---|---|---|
Gitea__BaseUrl |
https://git.zhengchentao.win |
Gitea backend URL |
Gitea__AdminPat |
(required) | Gitea read-only PAT — see PAT Setup below |
Gitea__RepoBlacklist |
(empty) | Comma-separated owner/repo pairs to hide from Claude |
Gitea__DefaultLimit |
50 |
Default page size for list operations |
Gitea__MaxFileBytes |
1048576 |
Max file read size in bytes (1MB) |
Jwt__Issuer |
https://auth.zhengchentao.win |
Expected JWT issuer |
Jwt__Audience |
gitea |
Expected JWT audience |
Jwt__SigningKeyCurrent |
(required) | HS256 signing key (shared with nas-auth) |
Jwt__SigningKeyPrevious |
(empty) | Previous key for rotation window |
ASPNETCORE_ENVIRONMENT |
Production |
Use Development locally |
All secrets come from /volume1/docker/compose/.env.shared on NAS — never hardcode them.
Local Development
1. Restore and run
dotnet restore
dotnet run
# Listens on http://localhost:5000
2. Generate a dev JWT
Use dotnet user-jwts to sign a token without running nas-auth:
dotnet user-jwts create \
--issuer https://auth.zhengchentao.win \
--audience gitea \
--name tao \
--claim sub=tao \
--claim scope=read:gitea
Or use jwt.io with alg=HS256 and the key from Jwt:SigningKeyCurrent.
3. Test with MCP Inspector
npx @modelcontextprotocol/inspector
# Transport: Streamable HTTP
# URL: http://localhost:5000/mcp
# Bearer Token: <token from step 2>
4. Run unit tests
dotnet test gitea-mcp.Tests/
PAT Setup (Gitea → Settings → Applications)
Generate a token with only these scopes (principle of least privilege):
read:repositoryread:organizationread:packageread:issueread:user
Do NOT grant write:* or admin:* scopes.
Store the generated token in .env.shared as GITEA_MCP_PAT=<token>.
If the PAT is compromised: revoke in Gitea → generate new → update .env.shared → docker compose up -d gitea-mcp.
Docker Compose (NAS deployment)
# /volume1/docker/compose/gitea-mcp/docker-compose.yml
services:
gitea-mcp:
image: git.zhengchentao.win/zhengchen.tao/gitea-mcp:latest
container_name: gitea-mcp
restart: unless-stopped
ports:
- "9092:8080"
volumes:
- /volume1/docker/gitea-mcp/logs:/app/logs
environment:
- ASPNETCORE_ENVIRONMENT=Production
- Gitea__BaseUrl=https://git.zhengchentao.win
- Gitea__AdminPat=${GITEA_MCP_PAT}
- Gitea__RepoBlacklist=
- Jwt__Issuer=https://auth.zhengchentao.win
- Jwt__Audience=gitea
- Jwt__SigningKeyCurrent=${JWT_SIGNING_KEY_CURRENT}
- TZ=Asia/Shanghai
env_file:
- ../.env.shared