6.9 KiB
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).
AI Client (ChatGPT)
│
│ HTTP (Authorization: Bearer <key>)
▼
┌────────────────────────────────────────────┐
│ 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:
APIKeyValidatorclass: 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_FAILURESwithinAUTH_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_KEYSinto 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.AsyncClientwith 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
structloglogger writing JSON to the configured log file log_tool_invocation(): records tool calls with result and correlation IDlog_access_denied(): records failed authenticationlog_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_TOOLSlist: the canonical list of tools exposed to clientsget_tool_by_name(): lookup helper used by the dispatcher
tools/repository.py
Concrete tool handler functions. Responsibilities:
list_repositories_tool(): callsGiteaClient.list_repositories(), formats the resultget_repository_info_tool(): callsGiteaClient.get_repository(), formats metadataget_file_tree_tool(): callsGiteaClient.get_tree(), flattens to a list of pathsget_file_contents_tool(): callsGiteaClient.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.