Files
AegisGitea-MCP/README.md
latte 59e1ea53a8
Some checks failed
docker / lint (push) Has been cancelled
docker / test (push) Has been cancelled
docker / docker-build (push) Has been cancelled
lint / lint (push) Has been cancelled
test / test (push) Has been cancelled
Add OAuth2/OIDC per-user Gitea authentication
Introduce a GiteaOAuthValidator for JWT and userinfo validation and
fallbacks, add /oauth/token proxy, and thread per-user tokens through
the
request context and automation paths. Update config and .env.example for
OAuth-first mode, add OpenAPI, extensive unit/integration tests,
GitHub/Gitea CI workflows, docs, and lint/test enforcement (>=80% cov).
2026-02-25 16:54:01 +01:00

150 lines
4.0 KiB
Markdown

# AegisGitea-MCP
Security-first MCP server for self-hosted Gitea with per-user OAuth2/OIDC authentication.
AegisGitea-MCP exposes MCP tools over HTTP/SSE and validates each user token against Gitea so tool access follows each user's actual repository permissions.
## Securing MCP with Gitea OAuth
### 1) Create a Gitea OAuth2 application
1. Open `https://git.hiddenden.cafe/user/settings/applications` (or admin application settings).
2. Create an OAuth2 app.
3. Set redirect URI to the ChatGPT callback URL shown after creating a New App.
4. Save the app and keep:
- `Client ID`
- `Client Secret`
Required scopes:
- `read:repository`
- `write:repository` (only needed when using write tools)
### 2) Configure this MCP server
```bash
cp .env.example .env
```
Set OAuth-first values:
```env
GITEA_URL=https://git.hiddenden.cafe
OAUTH_MODE=true
GITEA_OAUTH_CLIENT_ID=<your-client-id>
GITEA_OAUTH_CLIENT_SECRET=<your-client-secret>
OAUTH_EXPECTED_AUDIENCE=<optional; defaults to client id>
```
### 3) Configure ChatGPT New App
In ChatGPT New App:
- MCP server URL: `https://<your-mcp-domain>/mcp/sse`
- Authentication: OAuth
- OAuth client ID: Gitea OAuth app client ID
- OAuth client secret: Gitea OAuth app client secret
After creation, copy the ChatGPT callback URL and add it to the Gitea OAuth app redirect URIs.
### 4) OAuth-protected MCP behavior
The server publishes protected-resource metadata:
- `GET /.well-known/oauth-protected-resource`
Example response:
```json
{
"resource": "https://git.hiddenden.cafe",
"authorization_servers": ["https://git.hiddenden.cafe"],
"bearer_methods_supported": ["header"],
"scopes_supported": ["read:repository", "write:repository"],
"resource_documentation": "https://hiddenden.cafe/docs/mcp-gitea"
}
```
If a tool call is missing/invalid auth, MCP endpoints return `401` with:
```http
WWW-Authenticate: Bearer resource_metadata="https://<mcp-host>/.well-known/oauth-protected-resource", scope="read:repository"
```
## Architecture
```text
ChatGPT App
-> Authorization Code Flow
-> Gitea OAuth2/OIDC (issuer: https://git.hiddenden.cafe)
-> Access token
-> MCP Server (/mcp/sse, /mcp/tool/call)
-> OIDC discovery + JWKS cache
-> Scope enforcement (read:repository / write:repository)
-> Per-request Gitea API calls with Authorization: Bearer <user token>
```
## Example curl
Protected resource metadata:
```bash
curl -s https://<mcp-host>/.well-known/oauth-protected-resource | jq
```
Expected 401 challenge when missing token:
```bash
curl -i https://<mcp-host>/mcp/tool/call \
-H "Content-Type: application/json" \
-d '{"tool":"list_repositories","arguments":{}}'
```
Authenticated tool call:
```bash
curl -s https://<mcp-host>/mcp/tool/call \
-H "Authorization: Bearer <user_access_token>" \
-H "Content-Type: application/json" \
-d '{"tool":"list_repositories","arguments":{}}'
```
## Threat model
- Shared bot tokens are dangerous:
- one leaked token can expose all repositories reachable by that bot account.
- blast radius is repository-wide and cross-user.
- Token-in-URL is insecure:
- URLs leak via logs, proxies, browser history, and referers.
- bearer tokens must be sent in `Authorization` headers only.
- Per-user OAuth reduces lateral access:
- each call runs as the signed-in user.
- users only see repositories they already have permission for in Gitea.
## CI/CD
Gitea workflows were added under `.gitea/workflows/`:
- `lint.yml`: Ruff + formatting + mypy.
- `test.yml`: lint + pytest + enforced coverage (`>=80%`).
- `docker.yml`: lint+test gated Docker build, SHA tag, `latest` tag on `main`.
## Docker hardening
`docker/Dockerfile` uses a multi-stage build, non-root runtime user, production env flags, minimal runtime dependencies, and a healthcheck.
## Commands
- `make test`
- `make lint`
- `make format`
- `make docker-build`
- `make docker-up`
## Documentation
- `docs/api-reference.md`
- `docs/security.md`
- `docs/configuration.md`
- `docs/deployment.md`
- `docs/write-mode.md`