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_TOKENandMCP_BEARER_TOKEN. Every client with the bearer token acts as the token owner. - Per-user OAuth: additionally set
OAUTH_ENABLED=truewith 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; leaveMCP_BEARER_TOKENunset 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.0so the published port works. That is allowed only becauseMCP_BEARER_TOKENor OAuth is configured; otherwise it exits with code 2. - The port is published on
127.0.0.1only. The reverse proxy on the same host is the public entry point. DATA_DIRmust 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
.envout of version control and out of the image (mode 0600;setupwrites it that way). Compose passes it as environment, so it is visible todocker inspectfor 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_KEYmust 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 pytestReview the diff of
tools.jsonanddocs/tools.md: renamed or removed operations change tool names. Until the new spec is pinned,gitea_api_requestreaches 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.