# Architecture ## Overview AegisGitea MCP is a Python 3.10+ application built on **FastAPI**. It acts as a bridge between an AI client (such as Claude, Claude Code, or Cowork) and a self-hosted Gitea instance, implementing the [Model Context Protocol (MCP)](https://modelcontextprotocol.io). ``` AI Client (Claude / Claude Code / Cowork) │ │ HTTP (Authorization: Bearer ) ▼ ┌────────────────────────────────────────────┐ │ FastAPI Server │ │ server.py │ │ - Route: GET/POST /mcp │ │ - Route: POST /mcp/tool/call │ │ - Route: GET /mcp/tools │ │ - Route: GET /health │ │ - Streamable HTTP transport │ │ - Legacy SSE alias (GET/POST /mcp/sse) │ └───────┬───────────────────┬────────────────┘ │ │ ┌────▼────┐ ┌────▼──────────────┐ │ auth │ │ Tool dispatcher │ │ auth.py│ │ (server.py) │ └────┬────┘ └────────┬──────────┘ │ │ │ ┌────────▼──────────┐ │ │ Tool handlers │ │ │ tools/repo.py │ │ └────────┬──────────┘ │ │ │ ┌────────▼──────────┐ │ │ GiteaClient │ │ │ gitea_client.py │ │ └────────┬──────────┘ │ │ HTTPS │ ▼ │ Gitea instance │ ┌────▼────────────────────┐ │ AuditLogger │ │ audit.py │ │ /var/log/aegis-mcp/ │ │ audit.log │ └─────────────────────────┘ ``` --- ## Source Modules ### `server.py` The entry point and FastAPI application. Responsibilities: - Defines all HTTP routes - Reads configuration on startup and initialises `GiteaClient` - Applies authentication middleware to protected routes - Dispatches tool calls to the appropriate handler function - Handles CORS ### `auth.py` API key validation. Responsibilities: - `APIKeyValidator` class: holds the set of valid keys, tracks failed attempts per IP - Constant-time comparison to prevent timing side-channels - Rate limiting: blocks IPs that exceed `MAX_AUTH_FAILURES` within `AUTH_FAILURE_WINDOW` - Helper functions for key generation and hashing - Singleton pattern (`get_validator()`) with test-friendly reset (`reset_validator()`) ### `config.py` Pydantic `BaseSettings` model. Responsibilities: - Loads all configuration from environment variables or `.env` - Validates values (log level enum, token format, key minimum length) - Parses comma-separated `MCP_API_KEYS` into a list - Exposes computed properties (e.g. base URL for Gitea API) ### `gitea_client.py` Async HTTP client for the Gitea API. Responsibilities: - Wraps `httpx.AsyncClient` with bearer token authentication - Maps HTTP status codes to typed exceptions (`GiteaAuthenticationError`, `GiteaNotFoundError`, etc.) - Enforces file size limit before returning file contents - Logs all API calls to the audit logger Key methods: | Method | Gitea endpoint | |---|---| | `get_current_user()` | `GET /api/v1/user` | | `list_repositories()` | `GET /api/v1/user/repos` | | `get_repository()` | `GET /api/v1/repos/{owner}/{repo}` | | `get_file_contents()` | `GET /api/v1/repos/{owner}/{repo}/contents/{path}` | | `get_tree()` | `GET /api/v1/repos/{owner}/{repo}/git/trees/{ref}` | ### `audit.py` Structured audit logging using `structlog`. Responsibilities: - Initialises a `structlog` logger writing JSON to the configured log file - `log_tool_invocation()`: records tool calls with result and correlation ID - `log_access_denied()`: records failed authentication - `log_security_event()`: records rate limit triggers and other security events - Auto-generates UUID correlation IDs when none is provided ### `mcp_protocol.py` MCP data models and tool registry. Responsibilities: - Pydantic models: `MCPTool`, `MCPToolCallRequest`, `MCPToolCallResponse`, `MCPListToolsResponse` - `AVAILABLE_TOOLS` list: the canonical list of tools exposed to clients - `get_tool_by_name()`: lookup helper used by the dispatcher ### `tools/repository.py` Concrete tool handler functions. Responsibilities: - `list_repositories_tool()`: calls `GiteaClient.list_repositories()`, formats the result - `get_repository_info_tool()`: calls `GiteaClient.get_repository()`, formats metadata - `get_file_tree_tool()`: calls `GiteaClient.get_tree()`, flattens to a list of paths - `get_file_contents_tool()`: calls `GiteaClient.get_file_contents()`, decodes base64 All handlers return a plain string. `server.py` wraps this in an `MCPToolCallResponse`. --- ## Request Lifecycle ``` 1. Client sends POST /mcp/tool/call │ 2. FastAPI routes the request to the tool-call handler in server.py │ 3. OAuth middleware validates the Bearer token via Gitea OIDC/JWKS or userinfo ├── Fail → AuditLogger.log_access_denied() → HTTP 401 / 429 └── Pass → continue │ 4. AuditLogger.log_tool_invocation(status="pending") │ 5. Tool dispatcher looks up the tool by name (mcp_protocol.get_tool_by_name) │ 6. Policy engine checks read/write mode and repository/path policy │ 7. If GITEA_TOKEN is configured, service-PAT authz checks GET /repos/{owner}/{repo}/collaborators/{user}/permission │ 8. Tool handler function (tools/repository.py) is called │ 9. GiteaClient makes an async HTTP call to the Gitea API │ 10. Result (or error) is returned to server.py │ 11. AuditLogger.log_tool_invocation(status="success" | "error") │ 12. MCPToolCallResponse is returned to the client ``` --- ## Key Design Decisions **Read by default, writes opt-in.** Read tools are available by default. Write-capable tools require `WRITE_MODE=true`, repository write policy/whitelist approval, and `write:repository` authorization. **Gitea controls repository access.** Without `GITEA_TOKEN`, Gitea enforces repository permissions on API calls made with the user's token. With `GITEA_TOKEN`, the service PAT can only execute after the server verifies the requesting user's actual repository permission through Gitea and writes an audit denial if the check fails. **Public tool discovery.** `GET /mcp/tools` requires no authentication so that MCP clients can discover the available tools without credentials. All other endpoints require authentication. **Minimal persisted state.** The audit log is persisted for tamper-evident review. Dynamic OAuth client registrations are persisted when DCR is enabled. Rate limit counters and short-lived authz caches are in-memory and reset on restart. **Async throughout.** FastAPI + `httpx.AsyncClient` means all Gitea API calls are non-blocking, allowing the server to handle concurrent requests efficiently.