Files
AegisGitea-MCP/docs/deployment.md
T

5.2 KiB

Deployment

This page covers running the HTTP server for remote clients. For a local stdio setup see getting-started.md. Read hardening.md as well: this server hands the power of its Gitea token to every client that can authenticate.

Choose a mode

  • Admin-token only: set GITEA_TOKEN and MCP_BEARER_TOKEN. Every client with the bearer token acts as the token owner.
  • Per-user OAuth: additionally set OAUTH_ENABLED=true with the OAuth settings. Users sign in with their own Gitea account and act under their own permissions. The static bearer keeps working next to it as the admin identity; leave MCP_BEARER_TOKEN unset to turn that off.

Docker

Image: git.hiddenden.cafe/hiddenden/aegisgitea-mcp. Tag dev is built from the dev branch and latest from main. uv run aegis-gitea-mcp setup prints this compose service, with your port:

services:
  aegis-gitea-mcp:
    image: git.hiddenden.cafe/hiddenden/aegisgitea-mcp:dev
    restart: unless-stopped
    env_file: .env
    environment:
      MCP_TRANSPORT: http
      MCP_HOST: 0.0.0.0  # allowed because authentication is configured
      DATA_DIR: /var/lib/aegis-mcp
    ports:
      - "127.0.0.1:8080:8080"
    volumes:
      - aegis-mcp-data:/var/lib/aegis-mcp
    read_only: true
    tmpfs:
      - /tmp
    cap_drop:
      - ALL
    security_opt:
      - no-new-privileges:true

volumes:
  aegis-mcp-data:

Points that matter:

  • Inside the container the server must bind 0.0.0.0 so the published port works. That is allowed only because MCP_BEARER_TOKEN or OAuth is configured; otherwise it exits with code 2.
  • The port is published on 127.0.0.1 only. The reverse proxy on the same host is the public entry point.
  • DATA_DIR must be a persistent volume writable by the container user. It holds the audit log and the encrypted OAuth token store (oauth-tokens.json, mode 0600). Losing the volume loses all OAuth logins and the audit history. Back it up, and protect the backup like the secrets it protects.
  • Keep .env out of version control and out of the image (mode 0600; setup writes it that way). Compose passes it as environment, so it is visible to docker inspect for anyone with Docker access.

Check it: curl http://127.0.0.1:8080/health.

Reverse proxy and TLS

Terminate TLS at a reverse proxy. PUBLIC_BASE_URL must be the public https URL (it is required for OAuth, and http is only accepted for loopback hosts). Mistral Le Chat needs a valid public certificate.

Caddy:

gitea-mcp.example.com {
    reverse_proxy 127.0.0.1:8080 {
        flush_interval -1
    }
}

nginx: inside the proxying location, set proxy_buffering off; so streamed responses are not held back. Also forward Host (proxy_set_header Host $host;) and keep the default read timeout in mind for long tool calls.

The server rejects requests whose Host is not allowed (DNS-rebinding protection). Allowed hosts are the loopback names with the port, the host of PUBLIC_BASE_URL, and ALLOWED_HOSTS. If you reach it under another name, add that name to ALLOWED_HOSTS.

OAuth application in Gitea

setup --public-base-url https://gitea-mcp.example.com registers the application for you as the admin's user application. To do it by hand: in Gitea open Settings, Applications, Manage OAuth2 Applications, create an application named aegis-gitea-mcp as a confidential client with the redirect URI https://gitea-mcp.example.com/oauth/callback, and put the client id and secret in GITEA_OAUTH_CLIENT_ID and GITEA_OAUTH_CLIENT_SECRET. Then set OAUTH_ENABLED=true, PUBLIC_BASE_URL and TOKEN_ENCRYPTION_KEY (at least 32 characters, openssl rand -hex 32).

Gitea has no instance-wide OAuth application API, so the application belongs to the admin account that created it. Re-running setup reuses it; if the stored client secret is missing, Gitea has to regenerate it, and setup asks before doing so.

OAUTH_SCOPES is the scope list sent to Gitea. Keep it to granular scopes. Narrow it (for example to read:repository,read:issue) to limit what signed-in users' tokens can do through this server regardless of their Gitea rights.

Upgrades

  • Application: pull the new image (or package) and restart. The data volume is kept.

  • TOKEN_ENCRYPTION_KEY must stay the same, or stored OAuth logins can no longer be decrypted and users have to sign in again.

  • Upgrading Gitea: tool names and schemas come from the pinned swagger, so a Gitea upgrade does not change them. To pick up a newer API, re-pin the swagger and regenerate the catalog:

    curl -fsS https://git.example.com/swagger.v1.json -o spec/gitea-swagger.v1.json
    uv run python scripts/generate_catalog.py
    uv run python scripts/check_docs.py --write
    uv run pytest
    

    Review the diff of tools.json and docs/tools.md: renamed or removed operations change tool names. Until the new spec is pinned, gitea_api_request reaches new endpoints.

CI

The workflows in .gitea/workflows/ lint, test and build. The docs-check job runs uv run python scripts/check_docs.py, which fails when docs/tools.md or the README tool summary is stale, or when a document names a tool that does not exist. scripts/generate_catalog.py --check fails when tools.json does not match the pinned swagger.