# Architecture ## Overview AegisGitea MCP is a Python 3.10+ application built on **FastAPI**. It acts as a bridge between an AI client (such as ChatGPT) and a self-hosted Gitea instance, implementing the [Model Context Protocol (MCP)](https://modelcontextprotocol.io). ``` AI Client (ChatGPT) │ │ HTTP (Authorization: Bearer ) ▼ ┌────────────────────────────────────────────┐ │ FastAPI Server │ │ server.py │ │ - Route: POST /mcp/tool/call │ │ - Route: GET /mcp/tools │ │ - Route: GET /health │ │ - SSE support (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/repos/search` | | `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. auth.validate_api_key() checks the Authorization header ├── 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. Tool handler function (tools/repository.py) is called │ 7. GiteaClient makes an async HTTP call to the Gitea API │ 8. Result (or error) is returned to server.py │ 9. AuditLogger.log_tool_invocation(status="success" | "error") │ 10. MCPToolCallResponse is returned to the client ``` --- ## Key Design Decisions **Read-only by design.** The MCP tools only read data from Gitea. No write operations are implemented. **Gitea controls access.** The server does not maintain its own repository ACL. The Gitea bot user's permissions are the source of truth. If the bot cannot access a repo, the server cannot either. **Public tool discovery.** `GET /mcp/tools` requires no authentication so that ChatGPT's plugin system can discover the available tools without credentials. All other endpoints require authentication. **Stateless server.** No database or persistent state beyond the audit log file. Rate limit counters 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.