Identity gains a redact field that the executor honours, so the OAuth
resolver no longer needs an executor subclass overriding private
internals. Opaque agm_ tokens issued by our authorization server are now
masked by the secret patterns wherever they appear in content.
Authorization server for MCP clients (SDK provider with dynamic client
registration, PKCE, refresh rotation and revocation) that signs users in
through Gitea and runs tool calls with the user's own Gitea token.
- consent page with CSRF protection and a browser-binding cookie on the
Gitea callback (confused-deputy protection for the shared Gitea app)
- encrypted token store (Fernet, HKDF-derived key, 0600, atomic writes,
symlink and corrupt-file refusal); our tokens stored only as hashes
- tokens bound to the MCP endpoint resource and the gitea scope; the
static bearer keeps working next to OAuth
- code replay and refresh token reuse revoke the grant
- automatic upstream refresh with per-session locking; invalid_grant
ends the session and never falls back to the admin token
- OAuth-only deployments (no GITEA_TOKEN, no bearer) may start
- rate limit on the OAuth endpoints, query strings dropped from access logs
The audit logger is opened at startup (exit 2 on failure) and obtained before
every call, which is refused with audit_unavailable otherwise. Audit records only
take validated path and sudo values and use the request id as correlation id. The
rate limiter evicts idle keys, caps tracked keys, audits the first excess per key
and window and uses the settings passed to the app. FORWARDED_ALLOW_IPS is now a
setting, and startup warns about a missing host allow-list and unavailable OAuth.
Low-level SDK server built from the catalog plus gitea_api_request, with TOOLSETS/READ_ONLY/DISABLED_TOOLS filters, schema validation without value echo, one audit event per call, static bearer verifier, identity resolver hook, /health, DNS-rebinding protection, rate limiting and fail-closed startup checks.
Require consent before sending the token over plain http to a non-loopback
host, strip control characters from server-supplied text, restrict env values
and tokens to printable ASCII, validate the URLs like other env values and
fsync the directory after the atomic replace.
Slash safety is decided per (path template, parameter) so single-segment
routes such as releases/tags/{tag} and editorconfig/{filepath} keep %2F.
Adds authorization_header, remote_password, mirror_password, mirror_token and
the AWS key fields to the redacted request fields. Deeply nested JSON from
Gitea is treated as text instead of raising RecursionError, and base64
uploads are capped before decoding.
Replace lint/test/docker workflows with ci.yml (gitleaks, lint, test-unit, test-integration, test-mcp, build-and-push, docs-check, security). Rebuild the Dockerfile as a digest-pinned multi-stage uv build running as uid 1000, add .dockerignore, a hardened compose file and a gitleaks config. The integration harness now falls back to /etc/hostname and /proc/self to find its own container.
Adds scripts/generate_catalog.py (with --check), the naming rules, schema conversion, the committed tools.json for all 537 operations and the load_catalog() loader.
Verifies the Gitea instance, token and scopes, registers or reuses the aegis-gitea-mcp OAuth app, generates missing secrets and writes a 0600 env file from the template. Prints client configs and deployment snippets without any secret value.
Adds the pydantic-settings Settings with validated GITEA_URL, OAuth scopes
and toolsets, the argparse CLI skeleton, catalog.model, identity and
GiteaAPIError. Test fixtures now isolate env, cwd and state paths in tmp.
Removes the FastAPI server, hand-rolled MCP protocol, tool registry and
tools, YAML policy, resource-type authz, API-key and OAuth proxy modules,
automation, observability, cache, the old Gitea client and stdio adapter,
the key-management scripts and their tests (ADR 0006).
Inventory and gap analysis of the 0.2.0 codebase against the full Gitea API
and the official gitea-mcp toolset, the B1..B11 + security chunk plan, ADRs
0001-0006 for the full-access rebuild, SDK/OAuth research notes, and the
instance swagger pinned for tool generation. The audit-logging doc moves to
docs/audit-logging.md.
Document the two publish channels (aegis-gitea-mcp from main,
aegis-gitea-mcp-dev from dev), install commands for each, that both share
the aegis_gitea_mcp module so only one installs per environment, and the
merge-driven stable release flow (bump version -> PR into dev -> promote
dev to main; re-pushing main at the same version is a --check-url no-op).
Replace the tag-only publish trigger (no v* tags ever existed, so the
package was never built) with branch-push publishing:
- dev push -> aegis-gitea-mcp-dev at X.Y.Z.dev<run_number> (always unique)
- main push -> aegis-gitea-mcp at X.Y.Z, a clean no-op via uv --check-url
if that version is already in the registry
Name + version are patched into pyproject.toml at build time only; the
committed file keeps aegis-gitea-mcp / X.Y.Z. Lint + test gates still run
before publish, and the REGISTRY_TOKEN secret is required (fail-closed).
Gitea's act_runner does not reliably support the actions/upload-artifact@v4
backend. Drop the artifact upload from the test workflow (the package job's
purpose is to build and smoke-test, not to store wheels) and make the publish
workflow's upload best-effort (continue-on-error) so a flaky artifact backend
cannot block a release — the package is still published to the registry.
Add a package job to the test workflow: uv build, then verify a clean core
install excludes the web stack and the aegis-gitea-mcp stdio entry exits 2 with
an actionable message, and that the [server] extra pulls in fastapi/uvicorn and
imports the server entry. Catches packaging/console-script regressions in CI.