Files
gitea-mcp/README.md
T
zhengchen.tao c7fa6aeb7f
Build Docker Image / build (push) Failing after 5m41s
Build Docker Image / deploy (push) Has been skipped
gitea-mcp: 初次落地 Gitea MCP Server (.NET 10, V1 only-read)
把 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>
2026-05-06 01:32:42 +08:00

5.2 KiB

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:repository
  • read:organization
  • read:package
  • read:issue
  • read: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.shareddocker 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