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.
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).
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.
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>