Commit graph

1 commit

Author SHA1 Message Date
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