release: v0.2.0 — local stdio package, safe full-API coverage & resource-type authz #63

Merged
Latte merged 25 commits from dev into main 2026-06-27 13:43:56 +00:00

25 Commits

Author SHA1 Message Date
Latte 2bb74807bc Merge pull request 'feat: harden local stdio MCP, CI package smoke, CLAUDE.md conventions' (#64) from feat/local-mcp-hardening-and-ci into dev
docker / lint (push) Successful in 33s
docker / test (push) Successful in 31s
docker / test (pull_request) Successful in 36s
docker / lint (pull_request) Successful in 43s
lint / lint (push) Successful in 45s
test / test (push) Successful in 44s
lint / lint (pull_request) Successful in 43s
test / test (pull_request) Successful in 45s
test / package (pull_request) Successful in 1m0s
docker / docker (pull_request) Successful in 49s
test / package (push) Successful in 1m53s
docker / docker (push) Successful in 42s
Reviewed-on: #64
2026-06-27 13:29:09 +00:00
Latte 4db37d200e ci: stop artifact upload from failing the build on Gitea runners
docker / test (push) Successful in 34s
docker / lint (push) Successful in 40s
lint / lint (push) Successful in 43s
docker / lint (pull_request) Successful in 43s
docker / test (pull_request) Successful in 34s
test / test (push) Successful in 44s
lint / lint (pull_request) Successful in 44s
test / package (push) Successful in 1m8s
test / test (pull_request) Successful in 44s
test / package (pull_request) Successful in 53s
docker / docker (push) Successful in 1m5s
docker / docker (pull_request) Successful in 43s
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.
2026-06-27 15:26:56 +02:00
Latte 3d527f8690 ci: build the package and smoke-test both install profiles
docker / test (pull_request) Successful in 34s
docker / test (push) Successful in 36s
docker / lint (pull_request) Successful in 39s
docker / lint (push) Successful in 40s
test / test (push) Successful in 42s
test / package (pull_request) Failing after 2m27s
lint / lint (pull_request) Successful in 43s
lint / lint (push) Successful in 43s
test / test (pull_request) Successful in 44s
docker / docker (pull_request) Successful in 1m10s
docker / docker (push) Successful in 34s
test / package (push) Failing after 2m5s
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.
2026-06-27 15:19:43 +02:00
Latte d79ff2d476 docs: forbid AI attribution and document the stdio transport in CLAUDE.md
Record that no 'Generated with Claude Code' / Co-Authored-By / 'made by Claude'
attribution may appear in commits, PRs, releases, comments or docs. Add stdio
transport notes (stdout reserved for JSON-RPC, build_server vs _serve).
2026-06-27 15:19:42 +02:00
Latte 5d4a98d06e feat(stdio): harden local MCP transport and add end-to-end tests
Reserve stdout for the JSON-RPC stream: _configure_stderr_logging() pins all
logging to stderr (and rewrites any stray stdout handler) so a log line can
never corrupt the stdio protocol. Extract a pure, testable build_server() from
_serve(). Add end-to-end tests over the mcp in-memory transport (initialize +
tools/list + tools/call), covering a successful round trip and a policy denial
surfaced as an MCP error.
2026-06-27 15:19:42 +02:00
Latte f660fa32c1 Merge pull request 'ci: reuse existing REGISTRY_TOKEN secret for package publish' (#62) from feat/local-package-and-full-coverage into dev
docker / test (push) Successful in 31s
docker / lint (push) Successful in 37s
lint / lint (push) Successful in 39s
test / test (push) Successful in 38s
docker / docker (push) Successful in 43s
docker / test (pull_request) Successful in 33s
docker / lint (pull_request) Successful in 39s
lint / lint (pull_request) Successful in 39s
test / test (pull_request) Successful in 39s
docker / docker (pull_request) Successful in 38s
Reviewed-on: #62
2026-06-27 13:05:18 +00:00
Latte 83c7416677 Merge branch 'dev' into feat/local-package-and-full-coverage
docker / test (push) Successful in 34s
docker / lint (push) Successful in 42s
docker / lint (pull_request) Successful in 43s
docker / test (pull_request) Successful in 32s
lint / lint (push) Successful in 45s
test / test (push) Successful in 45s
lint / lint (pull_request) Successful in 44s
test / test (pull_request) Successful in 35s
docker / docker (push) Successful in 46s
docker / docker (pull_request) Successful in 41s
2026-06-27 13:02:10 +00:00
Latte da66200be7 Merge pull request 'ci: reuse existing REGISTRY_TOKEN secret for package publish' (#60) from feat/publish-reuse-registry-token into dev
docker / test (push) Successful in 33s
docker / lint (push) Successful in 38s
lint / lint (push) Successful in 40s
test / test (push) Successful in 39s
docker / docker (push) Successful in 43s
docker / test (pull_request) Successful in 33s
docker / lint (pull_request) Successful in 39s
lint / lint (pull_request) Successful in 39s
test / test (pull_request) Successful in 39s
docker / docker (pull_request) Successful in 58s
Reviewed-on: #60
2026-06-27 12:49:17 +00:00
Latte 1ca5bcbc6b ci: reuse existing REGISTRY_TOKEN secret for package publish
docker / test (pull_request) Successful in 34s
test / test (pull_request) Successful in 43s
docker / docker (pull_request) Successful in 39s
docker / test (push) Successful in 34s
docker / lint (push) Successful in 40s
test / test (push) Successful in 42s
lint / lint (push) Successful in 44s
docker / lint (pull_request) Successful in 44s
lint / lint (pull_request) Successful in 42s
docker / docker (push) Successful in 46s
The repo already has a write:package REGISTRY_TOKEN secret (used by docker.yml).
Reuse it for uv publish instead of requiring new GITEA_PACKAGE_* secrets:
authenticate as GITHUB_ACTOR with the token as password. Update packaging docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 14:46:44 +02:00
Latte c551b3cfc3 ci: reuse existing REGISTRY_TOKEN secret for package publish
docker / lint (push) Successful in 36s
lint / lint (push) Successful in 38s
docker / test (push) Successful in 30s
test / test (push) Successful in 37s
docker / docker (push) Successful in 39s
docker / test (pull_request) Successful in 36s
docker / lint (pull_request) Successful in 41s
lint / lint (pull_request) Successful in 43s
test / test (pull_request) Successful in 43s
docker / docker (pull_request) Successful in 47s
The repo already has a write:package REGISTRY_TOKEN secret (used by docker.yml).
Reuse it for uv publish instead of requiring new GITEA_PACKAGE_* secrets:
authenticate as GITHUB_ACTOR with the token as password. Update packaging docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 14:39:10 +02:00
Latte cf19a320b0 Merge pull request 'feat: local stdio package + safe full-API coverage' (#59) from feat/local-package-and-full-coverage into dev
docker / test (push) Successful in 33s
docker / lint (push) Successful in 38s
lint / lint (push) Successful in 39s
test / test (push) Successful in 39s
docker / docker (push) Successful in 43s
Reviewed-on: #59
2026-06-27 12:23:31 +00:00
Latte 499bf98d92 ci: build and publish package to Gitea registry on tag
docker / test (push) Successful in 38s
lint / lint (push) Successful in 45s
docker / lint (push) Successful in 44s
test / test (push) Successful in 44s
docker / docker (push) Successful in 40s
docker / lint (pull_request) Successful in 40s
docker / test (pull_request) Successful in 34s
lint / lint (pull_request) Successful in 41s
test / test (pull_request) Successful in 40s
docker / docker (pull_request) Successful in 37s
Add .gitea/workflows/publish.yml: on a v* tag, gate on the existing lint + test
jobs, then build sdist+wheel with uv and publish to the self-hosted Gitea PyPI
registry using least-privilege Actions secrets (GITEA_PACKAGE_USER /
GITEA_PACKAGE_TOKEN). The job fails loudly when the secrets are absent rather
than publishing anonymously, uploads the built artifacts, and leaves a disabled
public-PyPI stub. Public PyPI is intentionally not published in this pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:18:07 +02:00
Latte 385b442b6f docs: local vs server quickstart, authz model, packaging
Reframe the README around two transports and add a local stdio quickstart with
uvx/pip and Claude Desktop / Claude Code wiring. New docs: local-quickstart.md
and packaging.md (uv build/publish). Document resource-type-aware authorization
and classified gitea_request in security.md; stdio env vars + audit-log
fallback in configuration.md; local install in deployment.md; core+adapters in
architecture.md. Add the missing root AGENTS.md contract, update CLAUDE.md with
the core/adapter layout, fail-closed invariants, and the branching flow
(HEAD -> feature -> dev -> main). Update roadmap/todo and .env.example.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:17:01 +02:00
Latte 2859a7f917 style: apply ruff/black formatting to stdio_app
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:10:14 +02:00
Latte 1636ae1501 test: stdio adapter dispatch, owner resolution and local env bootstrap
Cover the stdio adapter: local-mode env bootstrap (OAuth off, API-key gate off,
per-user audit path), missing-env failure, PAT owner resolution, and dispatch
(unknown tool, write-mode policy denial, and the happy path pinning request
context to the PAT owner via the shared registry). Tidy the boundary-test
assertion so ruff and black agree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:10:05 +02:00
Latte 3392d8f69b feat(security): resource-type-aware authorization with fail-closed defaults
Add aegis_gitea_mcp.authz: classify every dispatched call (typed tools and
gitea_request) by resource type (repository/org/user_self/user_owned/
misc_global/admin/unknown) and enforce a type-specific rule in service-PAT
mode, on top of policy + WRITE_MODE. Every decision fails closed:

- org: signed-in user must be a verified org member (Gitea-checked).
- user_owned: owner must be the caller or a member org of the caller.
- user_self: token-owner-scoped endpoints denied (token is the bot's).
- admin: default-deny; allowed only with RAW_API_ALLOW_SENSITIVE opt-in AND a
  verified site admin.
- misc_global: reads allowed, writes denied.
- unknown / unverifiable: denied and audited.

Wire it into the server's service-PAT dispatch: repository calls keep the
existing per-user collaborator check; non-repo calls (previously blanket-denied)
now go through the resource-type gate, opening the org/user/admin surface
safely. Verification results are cached briefly (fail-closed: positives only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:09:50 +02:00
Latte 2d7f12d0d0 feat: safe full-API coverage via classified gitea_request dispatch
Add a deterministic (method, path) read/write classifier with an explicit
render-only override table that can only downgrade provably side-effect-free
POSTs (markdown/markup) to reads, never the reverse — so a mutating call cannot
slip past the write-mode gate. Add a known-Gitea-prefix gate: gitea_request now
fails closed on any path whose top segment is not a recognized /api/v1 route
instead of passing unknown paths through. Expose raw_relative_segments for the
authorization layer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:09:30 +02:00
Latte 8902c4f642 feat: add local stdio adapter and uv-installable package with extras
Add aegis_gitea_mcp.stdio_app: a single-user, local MCP server over stdio
(official mcp SDK) that serves the same tools from the shared registry,
resolves the PAT owner via GET /user and pins request context to it, and runs
policy + WRITE_MODE + secret sanitization + audit while skipping the per-user
repo probe (the operator is the trusted token owner). Audit log falls back to a
per-user state path when the container default is unwritable.

Packaging: split deps into core (httpx/pydantic/mcp/...) and a [server] extra
(fastapi/uvicorn/PyJWT/python-multipart); add console scripts aegis-gitea-mcp
(stdio) and aegis-gitea-mcp-server (guarded HTTP entry); bump to 0.2.0 and fix
repo URLs. mcp added to requirements for CI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 10:56:16 +02:00
Latte 7da0c46de8 refactor: extract transport-agnostic core and shared tool registry
Introduce aegis_gitea_mcp.registry as the single name->handler source of
truth consumed by every transport adapter, moving TOOL_HANDLERS out of the
FastAPI server module. Add aegis_gitea_mcp.errors.ToolError so core handlers
no longer import fastapi.HTTPException; raw_tools now raises ToolError and the
HTTP adapter maps it back to HTTPException, preserving status codes and audit
behavior. Add a subprocess boundary test asserting the core imports without
pulling in fastapi/uvicorn/starlette.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 10:49:46 +02:00
Latte dd253f87e5 chore: add PLAN.md and branch for local package + full coverage
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 10:44:06 +02:00
Latte aefb243a05 Merge pull request 'Feat/raw api dispatch' (#58) from feat/raw-api-dispatch into dev
docker / test (push) Successful in 27s
docker / lint (push) Successful in 33s
lint / lint (push) Successful in 35s
test / test (push) Successful in 35s
docker / docker (push) Successful in 42s
Reviewed-on: #58
2026-06-26 10:33:10 +00:00
Latte 7f7aaab5a6 test(raw-api): cover gitea_request handler and path parsing
docker / lint (push) Successful in 38s
docker / test (push) Successful in 33s
docker / test (pull_request) Successful in 32s
test / test (push) Successful in 40s
lint / lint (push) Successful in 42s
docker / lint (pull_request) Successful in 39s
test / test (pull_request) Successful in 39s
lint / lint (pull_request) Successful in 40s
docker / docker (pull_request) Successful in 31s
docker / docker (push) Successful in 44s
Covers read allow + repository parsing, write denied without write-mode, write
allowed only for whitelisted repos, non-repo write denial, sensitive-path
denial (incl. GET) and override, cross-repo search handling, unknown-method and
traversal rejection before any network call, killswitch, response truncation,
and the raw path-parsing helpers and raw-aware extractors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:26:57 +02:00
Latte 8c84d76bd5 docs(raw-api): document gitea_request, env vars and policy examples
Adds docs/raw-api.md (two-layer policy, sensitive denylist, env vars, write-mode
warning), links it from index and api-reference, documents RAW_API_ENABLED /
RAW_API_ALLOW_SENSITIVE in .env.example, and adds commented virtual-tool-name
deny examples to policy.yaml.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:26:57 +02:00
Latte 8e41fd12af feat(raw-api): register gitea_request tool and wire server dispatch
Registers gitea_request in AVAILABLE_TOOLS with write_operation=False
(deliberate: a static flag cannot describe a read-or-write tool; the handler
authorizes writes per-method) and maps the tool name to raw_api_request_tool in
the server handler registry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:26:56 +02:00
Latte 2844c42ec8 feat(raw-api): add gitea_request schema, path parsing, client dispatch and handler
Adds the RawApiRequestArgs schema (extra=forbid), raw path normalization/
parsing helpers, a GiteaClient.raw_request that audits method+path only (never
the body), and the raw_api_request_tool handler. The handler derives a coarse
virtual tool name (gitea_request:METHOD:topsegment) plus repository/target_path
from the path and runs them back through the policy engine, enforces an
admin/credential sensitive-path denylist, and bounds responses. Two config
flags gate it: RAW_API_ENABLED (killswitch) and RAW_API_ALLOW_SENSITIVE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:26:26 +02:00