- Go 96.9%
- Shell 1.4%
- Dockerfile 1%
- Makefile 0.7%
Adds internal/session.Registry, the MCP session glue that maps
Mcp-Session-Id to a running forgejo-mcp child + bridge.
Lifecycle:
- First /mcp POST without Mcp-Session-Id: SpawnFunc creates a backend
(in production: supervisor.Start + bridge.New); registry mints a
192-bit hex session id, attaches it to the response header, and
dispatches the request to the new backend.
- Subsequent POSTs with the header dispatch to the existing backend.
- Unknown sids → 410 Gone (per MCP guidance, so clients re-initialise
instead of retrying forever).
- Sids are bound to the OAuth token that minted them: a different
bearer probing a stolen sid gets 403, distinct from "your token is
bad" (401) and "sid unknown" (410).
Cleanup:
- When backend.Done closes (child exited on its own — crash, OOM,
user-driven shutdown), a goroutine reaps the entry.
- Stop tears every session down on broker shutdown. The 30s idle
reaper and Forgejo token rotation come in 5c.
The Registry is decoupled from supervisor and bridge via SpawnFunc, so
tests don't need to fork real processes — they hand the registry a fake
that returns a controllable Backend. Also added oauth.ContextWithSession
so the session tests can inject an oauth.Session into request contexts
without standing up the full bearer-middleware chain.
Tests: 83.3% coverage. Cover spawn-on-initialize, sid reuse, unknown
sid, max-session cap with Retry-After, no-auth-context guard, sid
hijack defense (token mismatch → 403), Done-channel reaping, and
graceful Stop.
Closes forgejo-mcp-broker-t81.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .beads | ||
| .claude | ||
| cmd/broker | ||
| docs | ||
| internal | ||
| .gitignore | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
forgejo-mcp-broker
OAuth 2.1 authorization server and MCP session broker for forgejo-mcp.
Lets MCP clients such as Claude.ai connect to a Forgejo instance through a single public HTTPS endpoint, with per-user authentication delegated to Forgejo's own OAuth2 provider. The broker handles the OAuth dance, then spawns a dedicated forgejo-mcp --transport stdio subprocess for each authenticated session, scoped to the authenticated user's Forgejo access token.
Status: Planning. No code yet. See docs/design.md for the architecture and docs/plan.md for the phased implementation plan.
How it fits
Claude.ai ──HTTPS──▶ Caddy ──▶ fjmcp-broker ──stdio──▶ forgejo-mcp ──▶ Forgejo API
(this) (one per user (per-user
session) token)
fjmcp-broker(this project): one long-running process. Handles OAuth discovery, dynamic client registration, the authorization-code flow against Forgejo, session lifecycle, and stdio-to-streamable-HTTP bridging.forgejo-mcp(existing project): used as-is. Spawned per-session with the authenticated user'sFORGEJO_ACCESS_TOKENin the environment.- Caddy: terminates TLS for the public hostname and reverse-proxies to the broker.
Why a broker instead of adding OAuth to forgejo-mcp?
Process-level isolation. Each user's Forgejo token lives in exactly one subprocess — the broker never needs to demultiplex tokens inside a single shared client. This keeps forgejo-mcp's sync.Once singleton-client pattern valid and avoids a refactor of every tool handler. Full trade-off in docs/design.md.
Quick map
| File | What |
|---|---|
docs/design.md |
Architecture, components, token flow, deployment, security |
docs/plan.md |
Seven-phase implementation plan with acceptance criteria |
License
MIT © 2026 Ole-Morten Duesund.