ADR: the forgejo-mcp Service's shape and credential-less endpoint #21

Closed
opened 2026-10-06 12:34:24 +00:00 by pit · 1 comment
Owner

Parent

#17 — Install forgejo-mcp on the Guest as a LAN-reachable Service

What to build

The decision record that has to be Accepted before any implementation starts: the shape of the forgejo-mcp Service and the identity of its endpoint.

It fixes, in prose, the choices that everything after it depends on: a dedicated Ansible role for forgejo-mcp as a third Service on the forgejo Guest, separate from the forgejo role, with its own play; Streamable HTTP at the MCP path; a credential-less endpoint in passthrough auth mode, with the operator-token fallback deliberately off, so the Service holds no Forgejo credential of its own; a bind on the Guest interface behind a host allowlist (fail-closed: refuses to start on a non-loopback bind with no allowlist); an upstream to Forgejo over the Guest's own loopback, not through the Edge; and no Edge entry, certificate, DNS record or router rule — LAN-reachable, not WAN-published.

The ADR takes the next free number in docs/adr/ at the moment it is written (the address is not reserved ahead of time), and gets a row in the ADR index. (The publish manifest publishes docs/adr/* as a glob, so no manifest edit is needed.)

This ticket is done when the Operator merges it. Implementation does not begin before that.

Acceptance criteria

  • An ADR records the Service's shape and credential-less endpoint, and says why for each: a separate role over extending the existing one; passthrough over a configured token; the fallback left off; Streamable HTTP over SSE/stdio; the bind-plus-allowlist fail-closed behaviour; loopback upstream; no Edge entry.
  • The rejected alternatives are each recorded once: the signed OCI image (amd64-only, drags in a persistent-container pattern); SSE and stdio transports; a token configured on the Service; enabling the operator-token fallback.
  • Numbered with the next free ADR number, listed in the ADR index. (The publish manifest publishes docs/adr/* as a glob, so a new ADR needs no manifest edit.)
  • Accepted — merged by the Operator — before any role work begins.
## Parent #17 — Install forgejo-mcp on the Guest as a LAN-reachable Service ## What to build The decision record that has to be Accepted before any implementation starts: the shape of the forgejo-mcp Service and the identity of its endpoint. It fixes, in prose, the choices that everything after it depends on: a dedicated Ansible role for forgejo-mcp as a third Service on the `forgejo` Guest, separate from the `forgejo` role, with its own play; Streamable HTTP at the MCP path; a **credential-less** endpoint in `passthrough` auth mode, with the operator-token fallback deliberately off, so the Service holds no Forgejo credential of its own; a bind on the Guest interface behind a host allowlist (fail-closed: refuses to start on a non-loopback bind with no allowlist); an upstream to Forgejo over the Guest's own loopback, not through the Edge; and no Edge entry, certificate, DNS record or router rule — LAN-reachable, not WAN-published. The ADR takes the **next free number** in `docs/adr/` at the moment it is written (the address is not reserved ahead of time), and gets a row in the ADR index. (The publish manifest publishes `docs/adr/*` as a glob, so no manifest edit is needed.) This ticket is done when the Operator merges it. Implementation does not begin before that. ## Acceptance criteria - [ ] An ADR records the Service's shape and credential-less endpoint, and says *why* for each: a separate role over extending the existing one; passthrough over a configured token; the fallback left off; Streamable HTTP over SSE/stdio; the bind-plus-allowlist fail-closed behaviour; loopback upstream; no Edge entry. - [ ] The rejected alternatives are each recorded once: the signed OCI image (amd64-only, drags in a persistent-container pattern); SSE and stdio transports; a token configured on the Service; enabling the operator-token fallback. - [ ] Numbered with the next free ADR number, listed in the ADR index. (The publish manifest publishes `docs/adr/*` as a glob, so a new ADR needs no manifest edit.) - [ ] Accepted — merged by the Operator — before any role work begins.
Author
Owner

Implemented on branch hermes/21-forgejo-mcp-adr; PR #43 (pit/infra-forge#43).

  • ADR 0003 — docs/adr/0003-forgejo-mcp-service-shape-and-endpoint.md, the next free number (0001 and 0002 are taken), with a row added to docs/adr/index.md. No manifest edit: docs/adr/* is already a published glob.
  • Records the shape (separate role, own play; Streamable HTTP at /mcp; credential-less passthrough with the operator-token fallback off; bind 0.0.0.0 behind a 10.12.0.141 host allowlist, fail-closed; loopback upstream; no Edge entry) and says why for each. The rejected alternatives — signed OCI image, SSE/stdio, a configured Service token, the operator-token fallback — are each recorded once.
  • Grounded against upstream v3.2.0's README (transport, the fail-closed start / 403 / 401 behaviour, the fallback warning) and the pinned archive's sha256, re-verified by download-and-hash.
  • make fmt clean; relative links resolve in-repo.

Two-axis review (Standards + Spec) run on the diff; the one substantive finding — the first draft called the host allowlist a LAN-only guarantee "by construction rather than a filter", when it is a Host-header check — is fixed in the second commit.

Left open: this ticket is done when you merge, per its own text.

Implemented on branch `hermes/21-forgejo-mcp-adr`; PR #43 (https://forgejo.thepit.space/pit/infra-forge/pulls/43). - **ADR 0003** — `docs/adr/0003-forgejo-mcp-service-shape-and-endpoint.md`, the next free number (0001 and 0002 are taken), with a row added to `docs/adr/index.md`. No manifest edit: `docs/adr/*` is already a published glob. - Records the shape (separate role, own play; Streamable HTTP at `/mcp`; credential-less `passthrough` with the operator-token fallback off; bind `0.0.0.0` behind a `10.12.0.141` host allowlist, fail-closed; loopback upstream; no Edge entry) and says *why* for each. The rejected alternatives — signed OCI image, SSE/stdio, a configured Service token, the operator-token fallback — are each recorded once. - Grounded against upstream v3.2.0's README (transport, the fail-closed start / `403` / `401` behaviour, the fallback warning) and the pinned archive's sha256, re-verified by download-and-hash. - `make fmt` clean; relative links resolve in-repo. Two-axis review (Standards + Spec) run on the diff; the one substantive finding — the first draft called the host allowlist a LAN-only guarantee "by construction rather than a filter", when it is a `Host`-header check — is fixed in the second commit. Left open: this ticket is done when you merge, per its own text.
pit closed this issue 2026-10-06 20:35:27 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
olympus/infra-forge#21
No description provided.