Files
AegisGitea-MCP/docs/architecture.md

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:

  • 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.