Files
AegisGitea-MCP/docs/architecture.md
T
Latte b275f5c0c2
test / test (push) Has been cancelled
lint / lint (push) Has been cancelled
docker / test (pull_request) Successful in 13s
docker / lint (pull_request) Successful in 2m3s
lint / lint (pull_request) Successful in 16s
test / test (pull_request) Successful in 14s
docker / docker-test (pull_request) Successful in 42s
docker / docker-publish (pull_request) Has been skipped
docs: retarget setup to Claude connectors
2026-06-13 21:05:21 +02:00

7.6 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 Claude, Claude Code, or Cowork) and a self-hosted Gitea instance, implementing the Model Context Protocol (MCP).

AI Client (Claude / Claude Code / Cowork)
      │
      │  HTTP  (Authorization: Bearer <key>)
      ▼
┌────────────────────────────────────────────┐
│            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.