docs: Add documentation site and API reference
This commit is contained in:
255
docs/api-reference.md
Normal file
255
docs/api-reference.md
Normal file
@@ -0,0 +1,255 @@
|
||||
# API Reference
|
||||
|
||||
## HTTP Endpoints
|
||||
|
||||
### `GET /`
|
||||
|
||||
Returns basic server information. No authentication required.
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "AegisGitea MCP",
|
||||
"version": "0.1.0",
|
||||
"status": "running"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /health`
|
||||
|
||||
Health check endpoint. No authentication required.
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"gitea_connected": true
|
||||
}
|
||||
```
|
||||
|
||||
Returns HTTP 200 when healthy. Returns HTTP 503 when Gitea is unreachable.
|
||||
|
||||
---
|
||||
|
||||
### `GET /mcp/tools`
|
||||
|
||||
Returns the list of available MCP tools. No authentication required (needed for ChatGPT tool discovery).
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": [
|
||||
{
|
||||
"name": "list_repositories",
|
||||
"description": "...",
|
||||
"inputSchema": { ... }
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `POST /mcp/tool/call`
|
||||
|
||||
Executes an MCP tool. **Authentication required.**
|
||||
|
||||
**Request headers**
|
||||
|
||||
```
|
||||
Authorization: Bearer <api-key>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**Request body**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "<tool-name>",
|
||||
"arguments": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "..."
|
||||
}
|
||||
],
|
||||
"isError": false
|
||||
}
|
||||
```
|
||||
|
||||
On error, `isError` is `true` and `text` contains the error message.
|
||||
|
||||
---
|
||||
|
||||
### `GET /mcp/sse`
|
||||
|
||||
Server-Sent Events stream endpoint. Authentication required. Used for streaming MCP sessions.
|
||||
|
||||
---
|
||||
|
||||
### `POST /mcp/sse`
|
||||
|
||||
Sends a client message over an active SSE session. Authentication required.
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
All authenticated endpoints require a bearer token:
|
||||
|
||||
```
|
||||
Authorization: Bearer <api-key>
|
||||
```
|
||||
|
||||
Alternatively, the key can be passed as a query parameter (useful for tools that do not support custom headers):
|
||||
|
||||
```
|
||||
GET /mcp/tool/call?api_key=<api-key>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MCP Tools
|
||||
|
||||
### `list_repositories`
|
||||
|
||||
Lists all Gitea repositories accessible to the bot user.
|
||||
|
||||
**Arguments:** none
|
||||
|
||||
**Example response text**
|
||||
|
||||
```
|
||||
Found 3 repositories:
|
||||
|
||||
1. myorg/backend - Backend API service [Python] ★ 42
|
||||
2. myorg/frontend - React frontend [TypeScript] ★ 18
|
||||
3. myorg/infra - Infrastructure as code [HCL] ★ 5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `get_repository_info`
|
||||
|
||||
Returns metadata for a single repository.
|
||||
|
||||
**Arguments**
|
||||
|
||||
| Name | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `owner` | string | Yes | Repository owner (user or organisation) |
|
||||
| `repo` | string | Yes | Repository name |
|
||||
|
||||
**Example response text**
|
||||
|
||||
```
|
||||
Repository: myorg/backend
|
||||
Description: Backend API service
|
||||
Language: Python
|
||||
Stars: 42
|
||||
Forks: 3
|
||||
Default branch: main
|
||||
Private: false
|
||||
URL: https://gitea.example.com/myorg/backend
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `get_file_tree`
|
||||
|
||||
Returns the file and directory structure of a repository.
|
||||
|
||||
**Arguments**
|
||||
|
||||
| Name | Type | Required | Default | Description |
|
||||
|---|---|---|---|---|
|
||||
| `owner` | string | Yes | — | Repository owner |
|
||||
| `repo` | string | Yes | — | Repository name |
|
||||
| `ref` | string | No | default branch | Branch, tag, or commit SHA |
|
||||
| `recursive` | boolean | No | `false` | Recursively list all subdirectories |
|
||||
|
||||
> **Note:** Recursive mode is disabled by default to limit response size. Enable with care on large repositories.
|
||||
|
||||
**Example response text**
|
||||
|
||||
```
|
||||
File tree for myorg/backend (ref: main):
|
||||
|
||||
src/
|
||||
src/main.py
|
||||
src/config.py
|
||||
tests/
|
||||
tests/test_main.py
|
||||
README.md
|
||||
requirements.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `get_file_contents`
|
||||
|
||||
Returns the contents of a single file.
|
||||
|
||||
**Arguments**
|
||||
|
||||
| Name | Type | Required | Default | Description |
|
||||
|---|---|---|---|---|
|
||||
| `owner` | string | Yes | — | Repository owner |
|
||||
| `repo` | string | Yes | — | Repository name |
|
||||
| `filepath` | string | Yes | — | Path to the file within the repository |
|
||||
| `ref` | string | No | default branch | Branch, tag, or commit SHA |
|
||||
|
||||
**Limits**
|
||||
|
||||
- Files larger than `MAX_FILE_SIZE_BYTES` (default 1 MB) are rejected.
|
||||
- Binary files that cannot be decoded as UTF-8 are returned as raw base64.
|
||||
|
||||
**Example response text**
|
||||
|
||||
```
|
||||
Contents of myorg/backend/src/main.py (ref: main):
|
||||
|
||||
import fastapi
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Responses
|
||||
|
||||
All errors follow this structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Error: <description>"
|
||||
}
|
||||
],
|
||||
"isError": true
|
||||
}
|
||||
```
|
||||
|
||||
Common error scenarios:
|
||||
|
||||
| Scenario | HTTP Status | `isError` |
|
||||
|---|---|---|
|
||||
| Missing or invalid API key | 401 | — (rejected before tool runs) |
|
||||
| Rate limited IP address | 429 | — |
|
||||
| Tool not found | 404 | — |
|
||||
| Repository not found in Gitea | 200 | `true` |
|
||||
| File too large | 200 | `true` |
|
||||
| Gitea API unavailable | 200 | `true` |
|
||||
168
docs/architecture.md
Normal file
168
docs/architecture.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# 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 <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.
|
||||
104
docs/configuration.md
Normal file
104
docs/configuration.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# Configuration
|
||||
|
||||
All configuration is done through environment variables. Copy `.env.example` to `.env` and set the values before starting the server.
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Gitea Settings
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `GITEA_URL` | Yes | — | Base URL of your Gitea instance (e.g. `https://gitea.example.com`) |
|
||||
| `GITEA_TOKEN` | Yes | — | API token of the Gitea bot user |
|
||||
|
||||
The `GITEA_TOKEN` must be a token belonging to a user that has at least read access to all repositories you want the AI to access. The server validates the token on startup by calling the Gitea `/api/v1/user` endpoint.
|
||||
|
||||
---
|
||||
|
||||
## MCP Server Settings
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `MCP_HOST` | No | `0.0.0.0` | Interface to bind to |
|
||||
| `MCP_PORT` | No | `8080` | Port to listen on |
|
||||
| `MCP_DOMAIN` | No | — | Public domain name (used for Traefik labels in Docker) |
|
||||
| `LOG_LEVEL` | No | `INFO` | Log level: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` |
|
||||
|
||||
---
|
||||
|
||||
## Authentication Settings
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `AUTH_ENABLED` | No | `true` | Enable or disable API key authentication |
|
||||
| `MCP_API_KEYS` | Yes (if auth enabled) | — | Comma-separated list of valid API keys |
|
||||
| `MAX_AUTH_FAILURES` | No | `5` | Number of failed attempts before rate limiting an IP |
|
||||
| `AUTH_FAILURE_WINDOW` | No | `300` | Time window in seconds for counting failures |
|
||||
|
||||
### API Key Requirements
|
||||
|
||||
- Minimum length: 32 characters
|
||||
- Recommended: generate with `make generate-key` (produces 64-character hex keys)
|
||||
- Multiple keys: separate with commas — useful during key rotation
|
||||
|
||||
```env
|
||||
# Single key
|
||||
MCP_API_KEYS=abc123...
|
||||
|
||||
# Multiple keys (grace period during rotation)
|
||||
MCP_API_KEYS=newkey123...,oldkey456...
|
||||
```
|
||||
|
||||
> **Warning:** Setting `AUTH_ENABLED=false` disables all authentication. Only do this in isolated development environments.
|
||||
|
||||
---
|
||||
|
||||
## File Access Settings
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `MAX_FILE_SIZE_BYTES` | No | `1048576` | Maximum file size the server will return (bytes). Default: 1 MB |
|
||||
| `REQUEST_TIMEOUT_SECONDS` | No | `30` | Timeout for upstream Gitea API calls (seconds) |
|
||||
|
||||
---
|
||||
|
||||
## Audit Logging Settings
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `AUDIT_LOG_PATH` | No | `/var/log/aegis-mcp/audit.log` | Absolute path for the JSON audit log file |
|
||||
|
||||
The directory is created automatically if it does not exist (requires write permission).
|
||||
|
||||
---
|
||||
|
||||
## Full Example
|
||||
|
||||
```env
|
||||
# Gitea
|
||||
GITEA_URL=https://gitea.example.com
|
||||
GITEA_TOKEN=abcdef1234567890abcdef1234567890
|
||||
|
||||
# Server
|
||||
MCP_HOST=0.0.0.0
|
||||
MCP_PORT=8080
|
||||
MCP_DOMAIN=mcp.example.com
|
||||
LOG_LEVEL=INFO
|
||||
|
||||
# Auth
|
||||
AUTH_ENABLED=true
|
||||
MCP_API_KEYS=a1b2c3d4e5f6...64chars
|
||||
MAX_AUTH_FAILURES=5
|
||||
AUTH_FAILURE_WINDOW=300
|
||||
|
||||
# Limits
|
||||
MAX_FILE_SIZE_BYTES=1048576
|
||||
REQUEST_TIMEOUT_SECONDS=30
|
||||
|
||||
# Audit
|
||||
AUDIT_LOG_PATH=/var/log/aegis-mcp/audit.log
|
||||
```
|
||||
126
docs/deployment.md
Normal file
126
docs/deployment.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Deployment
|
||||
|
||||
## Local / Development
|
||||
|
||||
```bash
|
||||
make install-dev
|
||||
source venv/bin/activate # Linux/macOS
|
||||
# venv\Scripts\activate # Windows
|
||||
|
||||
cp .env.example .env
|
||||
# Edit .env
|
||||
make generate-key # Add key to .env
|
||||
make run
|
||||
```
|
||||
|
||||
The server listens on `http://0.0.0.0:8080` by default.
|
||||
|
||||
---
|
||||
|
||||
## Docker
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
make docker-build
|
||||
# or: docker build -f docker/Dockerfile -t aegis-gitea-mcp .
|
||||
```
|
||||
|
||||
### Configure
|
||||
|
||||
Create a `.env` file (copy from `.env.example`) with your settings before starting the container.
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
make docker-up
|
||||
# or: docker-compose up -d
|
||||
```
|
||||
|
||||
### Logs
|
||||
|
||||
```bash
|
||||
make docker-logs
|
||||
# or: docker-compose logs -f
|
||||
```
|
||||
|
||||
### Stop
|
||||
|
||||
```bash
|
||||
make docker-down
|
||||
# or: docker-compose down
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## docker-compose.yml Overview
|
||||
|
||||
The included `docker-compose.yml` provides:
|
||||
|
||||
- **Health check:** polls `GET /health` every 30 seconds
|
||||
- **Audit log volume:** mounts a named volume at `/var/log/aegis-mcp` so logs survive container restarts
|
||||
- **Resource limits:** 1 CPU, 512 MB memory
|
||||
- **Security:** non-root user, `no-new-privileges`
|
||||
- **Traefik labels:** commented out — uncomment and set `MCP_DOMAIN` to enable automatic HTTPS via Traefik
|
||||
|
||||
### Enabling Traefik
|
||||
|
||||
1. Set `MCP_DOMAIN=mcp.yourdomain.com` in `.env`.
|
||||
2. Uncomment the Traefik labels in `docker-compose.yml`.
|
||||
3. Make sure Traefik is running with a `web` and `websecure` entrypoint and Let's Encrypt configured.
|
||||
|
||||
---
|
||||
|
||||
## Dockerfile Details
|
||||
|
||||
The image uses a multi-stage build:
|
||||
|
||||
| Stage | Base image | Purpose |
|
||||
|---|---|---|
|
||||
| `builder` | `python:3.11-slim` | Install dependencies |
|
||||
| `final` | `python:3.11-slim` | Minimal runtime image |
|
||||
|
||||
The final image:
|
||||
- Runs as user `aegis` (UID 1000, GID 1000)
|
||||
- Exposes port `8080`
|
||||
- Entry point: `python -m aegis_gitea_mcp.server`
|
||||
|
||||
---
|
||||
|
||||
## Production Checklist
|
||||
|
||||
- [ ] `AUTH_ENABLED=true` and `MCP_API_KEYS` set to a strong key
|
||||
- [ ] `GITEA_TOKEN` belongs to a dedicated bot user with minimal permissions
|
||||
- [ ] TLS terminated at the reverse proxy (Traefik, nginx, Caddy, etc.)
|
||||
- [ ] `AUDIT_LOG_PATH` points to a persistent volume
|
||||
- [ ] Log rotation configured for the audit log file
|
||||
- [ ] API key rotation scheduled (every 90 days recommended)
|
||||
- [ ] `MAX_AUTH_FAILURES` and `AUTH_FAILURE_WINDOW` tuned for your threat model
|
||||
- [ ] Resource limits configured in Docker/Kubernetes
|
||||
|
||||
---
|
||||
|
||||
## Kubernetes (Basic)
|
||||
|
||||
A minimal Kubernetes deployment is not included, but the server is stateless and the Docker image is suitable for use in Kubernetes. Key considerations:
|
||||
|
||||
- Store `.env` values as a `Secret` and expose them as environment variables.
|
||||
- Mount an `emptyDir` or PersistentVolumeClaim at the audit log path.
|
||||
- Use a `readinessProbe` and `livenessProbe` on `GET /health`.
|
||||
- Set `resources.requests` and `resources.limits` for CPU and memory.
|
||||
|
||||
---
|
||||
|
||||
## Updating
|
||||
|
||||
```bash
|
||||
git pull
|
||||
make docker-build
|
||||
make docker-up
|
||||
```
|
||||
|
||||
If you added a new key via `make generate-key` during the update, restart the container to pick up the new `.env`:
|
||||
|
||||
```bash
|
||||
docker-compose restart aegis-mcp
|
||||
```
|
||||
117
docs/getting-started.md
Normal file
117
docs/getting-started.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# Getting Started
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.10 or higher
|
||||
- A running Gitea instance
|
||||
- A Gitea bot user with access to the repositories you want to expose
|
||||
- `make` (optional but recommended)
|
||||
|
||||
## 1. Install
|
||||
|
||||
```bash
|
||||
git clone <repo-url>
|
||||
cd AegisGitea-MCP
|
||||
|
||||
# Install production dependencies
|
||||
make install
|
||||
|
||||
# Or install with dev dependencies (for testing and linting)
|
||||
make install-dev
|
||||
```
|
||||
|
||||
To install manually without `make`:
|
||||
|
||||
```bash
|
||||
python -m venv venv
|
||||
source venv/bin/activate # Linux/macOS
|
||||
# or: venv\Scripts\activate # Windows
|
||||
|
||||
pip install -e .
|
||||
# dev: pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
## 2. Create a Gitea Bot User
|
||||
|
||||
1. In your Gitea instance, create a dedicated user (e.g. `ai-bot`).
|
||||
2. Grant that user **read access** to any repositories the AI should be able to see.
|
||||
3. Generate an API token for the bot user:
|
||||
- Go to **User Settings** > **Applications** > **Generate Token**
|
||||
- Give it a descriptive name (e.g. `aegis-mcp-token`)
|
||||
- Copy the token — you will not be able to view it again.
|
||||
|
||||
## 3. Configure
|
||||
|
||||
Copy the example environment file and fill in your values:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Minimum required settings in `.env`:
|
||||
|
||||
```env
|
||||
GITEA_URL=https://gitea.example.com
|
||||
GITEA_TOKEN=<your-bot-user-token>
|
||||
AUTH_ENABLED=true
|
||||
MCP_API_KEYS=<your-generated-api-key>
|
||||
```
|
||||
|
||||
See [Configuration](configuration.md) for the full list of settings.
|
||||
|
||||
## 4. Generate an API Key
|
||||
|
||||
The MCP server requires clients to authenticate with a bearer token. Generate one:
|
||||
|
||||
```bash
|
||||
make generate-key
|
||||
# or: python scripts/generate_api_key.py
|
||||
```
|
||||
|
||||
Copy the printed key into `MCP_API_KEYS` in your `.env` file.
|
||||
|
||||
## 5. Run
|
||||
|
||||
```bash
|
||||
make run
|
||||
# or: python -m aegis_gitea_mcp.server
|
||||
```
|
||||
|
||||
The server starts on `http://0.0.0.0:8080` by default.
|
||||
|
||||
Verify it is running:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/health
|
||||
# {"status": "healthy", ...}
|
||||
```
|
||||
|
||||
## 6. Connect an AI Client
|
||||
|
||||
### ChatGPT
|
||||
|
||||
Use this single URL in the ChatGPT MCP connector:
|
||||
|
||||
```
|
||||
http://<host>:8080/mcp/sse?api_key=<your-api-key>
|
||||
```
|
||||
|
||||
ChatGPT uses the SSE transport: it opens a persistent GET stream on this URL and sends tool call messages back via POST to the same URL. The `api_key` query parameter is the recommended method because the ChatGPT interface does not support setting custom request headers.
|
||||
|
||||
### Other MCP clients
|
||||
|
||||
Clients that support custom headers can use:
|
||||
|
||||
- **SSE URL:** `http://<host>:8080/mcp/sse`
|
||||
- **Tool discovery URL:** `http://<host>:8080/mcp/tools` (no auth required)
|
||||
- **Tool call URL:** `http://<host>:8080/mcp/tool/call`
|
||||
- **Authentication:** `Authorization: Bearer <your-api-key>`
|
||||
|
||||
For a production deployment behind a reverse proxy, see [Deployment](deployment.md).
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Configuration](configuration.md) — tune file size limits, rate limiting, log paths
|
||||
- [API Reference](api-reference.md) — available tools and endpoints
|
||||
- [Security](security.md) — understand authentication and audit logging
|
||||
- [Deployment](deployment.md) — Docker and Traefik setup
|
||||
43
docs/index.md
Normal file
43
docs/index.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# AegisGitea MCP - Documentation
|
||||
|
||||
AegisGitea MCP is a security-first [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that provides controlled AI access to self-hosted Gitea repositories.
|
||||
|
||||
## Overview
|
||||
|
||||
AegisGitea MCP acts as a secure bridge between AI assistants (such as ChatGPT) and your Gitea instance. It exposes a limited set of read-only tools that allow an AI to browse repositories and read file contents, while enforcing strict authentication, rate limiting, and comprehensive audit logging.
|
||||
|
||||
**Version:** 0.1.0 (Alpha)
|
||||
**License:** MIT
|
||||
**Requires:** Python 3.10+
|
||||
|
||||
## Documentation
|
||||
|
||||
| Document | Description |
|
||||
|---|---|
|
||||
| [Getting Started](getting-started.md) | Installation and first-time setup |
|
||||
| [Configuration](configuration.md) | All environment variables and settings |
|
||||
| [API Reference](api-reference.md) | HTTP endpoints and MCP tools |
|
||||
| [Architecture](architecture.md) | System design and data flow |
|
||||
| [Security](security.md) | Authentication, rate limiting, and audit logging |
|
||||
| [Deployment](deployment.md) | Docker and production deployment |
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# 1. Clone and install
|
||||
git clone <repo-url>
|
||||
cd AegisGitea-MCP
|
||||
make install-dev
|
||||
|
||||
# 2. Configure
|
||||
cp .env.example .env
|
||||
# Edit .env with your Gitea URL and token
|
||||
|
||||
# 3. Generate an API key
|
||||
make generate-key
|
||||
|
||||
# 4. Run
|
||||
make run
|
||||
```
|
||||
|
||||
The server starts at `http://localhost:8080`.
|
||||
155
docs/security.md
Normal file
155
docs/security.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# Security
|
||||
|
||||
## Authentication
|
||||
|
||||
AegisGitea MCP uses bearer token authentication. Clients must include a valid API key with every tool call.
|
||||
|
||||
### How It Works
|
||||
|
||||
1. The client sends `Authorization: Bearer <key>` with its request.
|
||||
2. The server extracts the token and validates it against the configured `MCP_API_KEYS`.
|
||||
3. Comparison is done in **constant time** to prevent timing attacks.
|
||||
4. If validation fails, the failure is counted against the client's IP address.
|
||||
|
||||
### Generating API Keys
|
||||
|
||||
Use the provided script to generate cryptographically secure 64-character hex keys:
|
||||
|
||||
```bash
|
||||
make generate-key
|
||||
# or: python scripts/generate_api_key.py
|
||||
```
|
||||
|
||||
Keys must be at least 32 characters long. The script also saves metadata (creation date, expiration) to a `keys/` directory.
|
||||
|
||||
### Multiple Keys (Grace Period During Rotation)
|
||||
|
||||
You can configure multiple keys separated by commas. This allows you to add a new key and remove the old one without downtime:
|
||||
|
||||
```env
|
||||
MCP_API_KEYS=newkey...,oldkey...
|
||||
```
|
||||
|
||||
Remove the old key from the list after all clients have been updated.
|
||||
|
||||
---
|
||||
|
||||
## Key Rotation
|
||||
|
||||
Rotate keys regularly (recommended: every 90 days).
|
||||
|
||||
```bash
|
||||
make rotate-key
|
||||
# or: python scripts/rotate_api_key.py
|
||||
```
|
||||
|
||||
The rotation script:
|
||||
1. Reads the current key from `.env`
|
||||
2. Generates a new key
|
||||
3. Offers to replace the key immediately or add it alongside the old key (grace period)
|
||||
4. Creates a backup of your `.env` before modifying it
|
||||
|
||||
### Checking Key Age
|
||||
|
||||
```bash
|
||||
make check-key-age
|
||||
# or: python scripts/check_key_age.py
|
||||
```
|
||||
|
||||
Exit codes: `0` = OK, `1` = expiring within 7 days (warning), `2` = already expired (critical).
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
Failed authentication attempts are tracked per client IP address.
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `MAX_AUTH_FAILURES` | `5` | Maximum failures before the IP is blocked |
|
||||
| `AUTH_FAILURE_WINDOW` | `300` | Rolling window in seconds |
|
||||
|
||||
Once an IP exceeds the threshold, all further requests from that IP return HTTP 429 until the window resets. This is enforced entirely in memory — a server restart resets the counters.
|
||||
|
||||
---
|
||||
|
||||
## Audit Logging
|
||||
|
||||
All security-relevant events are written to a structured JSON log file.
|
||||
|
||||
### Log Location
|
||||
|
||||
Default: `/var/log/aegis-mcp/audit.log`
|
||||
Configurable via `AUDIT_LOG_PATH`.
|
||||
|
||||
The directory is created automatically on startup.
|
||||
|
||||
### What Is Logged
|
||||
|
||||
| Event | Description |
|
||||
|---|---|
|
||||
| Tool invocation | Every call to a tool: tool name, arguments, result status, correlation ID |
|
||||
| Access denied | Failed authentication attempts: IP address, reason |
|
||||
| Security event | Rate limit triggers, invalid key formats, startup authentication status |
|
||||
|
||||
### Log Format
|
||||
|
||||
Each entry is a JSON object on a single line:
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-02-13T10:00:00Z",
|
||||
"event": "tool_invocation",
|
||||
"correlation_id": "a1b2c3d4-...",
|
||||
"tool": "get_file_contents",
|
||||
"owner": "myorg",
|
||||
"repo": "backend",
|
||||
"path": "src/main.py",
|
||||
"result": "success",
|
||||
"client_ip": "10.0.0.1"
|
||||
}
|
||||
```
|
||||
|
||||
### Using Logs for Monitoring
|
||||
|
||||
Because entries are newline-delimited JSON, they are easy to parse:
|
||||
|
||||
```bash
|
||||
# Show all failed tool calls
|
||||
grep '"result": "error"' /var/log/aegis-mcp/audit.log | jq .
|
||||
|
||||
# Show all access-denied events
|
||||
grep '"event": "access_denied"' /var/log/aegis-mcp/audit.log | jq .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Access Control Model
|
||||
|
||||
AegisGitea MCP does **not** implement its own repository access control. Access to repositories is determined entirely by the Gitea bot user's permissions:
|
||||
|
||||
- If the bot user has no access to a repository, it will not appear in `list_repositories` and `get_repository_info` will return an error.
|
||||
- Grant the bot user the minimum set of repository permissions needed.
|
||||
|
||||
**Principle of least privilege:** create a dedicated bot user and grant it read-only access only to the repositories that the AI needs to see.
|
||||
|
||||
---
|
||||
|
||||
## Network Security Recommendations
|
||||
|
||||
- Run the MCP server behind a reverse proxy (e.g. Traefik or nginx) with TLS.
|
||||
- Do not expose the server directly on a public port without TLS.
|
||||
- Restrict inbound connections to known AI client IP ranges where possible.
|
||||
- The `/mcp/tools` endpoint is intentionally public (required for ChatGPT plugin discovery). If this is undesirable, restrict it at the network/proxy level.
|
||||
|
||||
---
|
||||
|
||||
## Container Security
|
||||
|
||||
The provided Docker image runs with:
|
||||
|
||||
- A non-root user (`aegis`, UID 1000)
|
||||
- `no-new-privileges` security option
|
||||
- CPU and memory resource limits (1 CPU, 512 MB)
|
||||
|
||||
See [Deployment](deployment.md) for details.
|
||||
Reference in New Issue
Block a user