docs: Add documentation site and API reference

This commit is contained in:
matsv
2026-02-13 15:12:14 +01:00
parent d82fe87113
commit e17d34e6d7
7 changed files with 968 additions and 0 deletions

255
docs/api-reference.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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.