docs: rewrite README and docs for the full-access design, generate tool inventory
This commit is contained in:
1 parent
68bd8700dc
commit
7c7cfc05ff
13 files changed
+1693
-1026
No files matched your search
+110
-62
@@ -1,78 +1,126 @@
|
||||
# Deployment
|
||||
|
||||
## Secure Defaults
|
||||
This page covers running the HTTP server for remote clients. For a local stdio setup see
|
||||
[getting-started.md](getting-started.md). Read [hardening.md](hardening.md) as well: this server
|
||||
hands the power of its Gitea token to every client that can authenticate.
|
||||
|
||||
- Default bind is `127.0.0.1`.
|
||||
- Binding `0.0.0.0` requires `ALLOW_INSECURE_BIND=true`.
|
||||
- Write mode disabled by default.
|
||||
- Policy checks run before tool execution.
|
||||
- OAuth-protected MCP challenge responses are enabled by default for tool calls.
|
||||
## Choose a mode
|
||||
|
||||
## Local stdio install (single user)
|
||||
|
||||
The local transport needs only the core package (no web stack):
|
||||
|
||||
```bash
|
||||
pip install aegis-gitea-mcp # or: uvx aegis-gitea-mcp
|
||||
GITEA_URL=https://git.hiddenden.cafe GITEA_TOKEN=<pat> aegis-gitea-mcp
|
||||
```
|
||||
|
||||
It authenticates with your Gitea PAT, runs policy + `WRITE_MODE` + audit, and
|
||||
serves over stdio for Claude Desktop / Claude Code. See
|
||||
[local-quickstart.md](local-quickstart.md).
|
||||
|
||||
## Local Development (HTTP server)
|
||||
|
||||
```bash
|
||||
make install-dev
|
||||
cp .env.example .env
|
||||
make run
|
||||
```
|
||||
|
||||
The HTTP server requires the web stack. From a published package that is the
|
||||
`[server]` extra:
|
||||
|
||||
```bash
|
||||
pip install 'aegis-gitea-mcp[server]'
|
||||
aegis-gitea-mcp-server
|
||||
```
|
||||
- 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
|
||||
|
||||
Use `docker/Dockerfile`:
|
||||
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:
|
||||
|
||||
- Multi-stage image build.
|
||||
- Non-root runtime user.
|
||||
- Production env flags (`NODE_ENV=production`, `ENVIRONMENT=production`).
|
||||
- Only required app files copied.
|
||||
- Healthcheck on `/health`.
|
||||
```yaml
|
||||
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
|
||||
|
||||
Run examples:
|
||||
|
||||
```bash
|
||||
docker compose --profile prod up -d
|
||||
docker compose --profile dev up -d
|
||||
volumes:
|
||||
aegis-mcp-data:
|
||||
```
|
||||
|
||||
## CI/CD (Gitea Workflows)
|
||||
Points that matter:
|
||||
|
||||
Workflows live in `.gitea/workflows/`:
|
||||
- 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.
|
||||
|
||||
- `lint.yml`: ruff + format checks + mypy.
|
||||
- `test.yml`: lint + tests + coverage fail-under `80`.
|
||||
- `docker.yml`: lint + test + docker smoke-test gating; image publish on push to `main`/`dev` and on approved PR review targeting `main`/`dev`; tags include commit SHA plus `latest` (`main`) or `dev` (`dev`).
|
||||
Check it: `curl http://127.0.0.1:8080/health`.
|
||||
|
||||
Docker publish settings:
|
||||
- `vars.PUSH_IMAGE=true` enables registry push.
|
||||
- `vars.REGISTRY_IMAGE` sets the target image name (for example `registry.example.com/org/aegis-gitea-mcp`).
|
||||
- `vars.REGISTRY_HOST` is optional and overrides the login host detection.
|
||||
- `secrets.REGISTRY_USER` and `secrets.REGISTRY_TOKEN` are required when push is enabled.
|
||||
## Reverse proxy and TLS
|
||||
|
||||
## Production Recommendations
|
||||
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.
|
||||
|
||||
- Place MCP behind TLS reverse proxy.
|
||||
- Set `PUBLIC_BASE_URL=https://<your-mcp-domain>` so OAuth metadata advertises HTTPS endpoints.
|
||||
- Restrict inbound traffic to expected clients.
|
||||
- Persist and monitor audit logs.
|
||||
- Monitor `/metrics` and auth-failure events.
|
||||
- Rotate OAuth client credentials when required.
|
||||
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:
|
||||
|
||||
```bash
|
||||
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.
|
||||
Reference in new issue
Block a user