No description
  • Go 96.9%
  • Shell 1.4%
  • Dockerfile 1%
  • Makefile 0.7%
Find a file
Ole-Morten Duesund 886092a600 feat(session): MCP session registry + spawn-on-initialize (forgejo-mcp-broker-t81)
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>
2026-04-27 17:24:25 +02:00
.beads feat(session): MCP session registry + spawn-on-initialize (forgejo-mcp-broker-t81) 2026-04-27 17:24:25 +02:00
.claude bd init: initialize beads issue tracking 2026-04-24 16:34:50 +02:00
cmd/broker feat(cmd/broker): wire config → log → store → httpserver (forgejo-mcp-broker-t37) 2026-04-24 17:29:37 +02:00
docs docs: initial planning artifacts for fjmcp-broker 2026-04-24 16:21:01 +02:00
internal feat(session): MCP session registry + spawn-on-initialize (forgejo-mcp-broker-t81) 2026-04-27 17:24:25 +02:00
.gitignore bd init: initialize beads issue tracking 2026-04-24 16:34:50 +02:00
AGENTS.md bd init: initialize beads issue tracking 2026-04-24 16:34:50 +02:00
CLAUDE.md docs: fill CLAUDE.md with phase-1 session learnings 2026-04-24 17:37:36 +02:00
go.mod feat(store): SQLite with embedded migrations (forgejo-mcp-broker-9jh) 2026-04-24 17:22:47 +02:00
go.sum feat(store): SQLite with embedded migrations (forgejo-mcp-broker-9jh) 2026-04-24 17:22:47 +02:00
LICENSE bd init: initialize beads issue tracking 2026-04-24 16:34:50 +02:00
Makefile feat: bootstrap Go project layout (forgejo-mcp-broker-n84) 2026-04-24 16:54:27 +02:00
README.md bd init: initialize beads issue tracking 2026-04-24 16:34:50 +02:00

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's FORGEJO_ACCESS_TOKEN in 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.