Polly StandardResilienceHandler validates SamplingDuration >= 2 * AttemptTimeout at startup. Default SamplingDuration is 30s and our AttemptTimeout is 30s, so the container failed to boot with OptionsValidationException. Set SamplingDuration explicitly to 60s while keeping AttemptTimeout at 30s.
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
↓
gitea-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)
↓
gitea-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