No description
  • Go 96.9%
  • Shell 1.4%
  • Dockerfile 1%
  • Makefile 0.7%
Find a file
Ole-Morten Duesund 03f67786be test(supervisor): stress tests for FD/goroutine/zombie leaks (forgejo-mcp-broker-31t)
Adds two stress tests:

  TestStress_NoLeaksAcross1000Cycles — spawns and reaps 1000 children
  in sequence, asserts FD count, goroutine count, and zombie status are
  all stable.

  TestStress_StopMidLifecycle — 200 cycles that exercise the Stop path
  (SIGTERM via Close+Signal) rather than relying on natural exit.

Bypassed by -short for the unit-test inner loop.

Notable findings:

* Using the helper-process pattern at this scale was a dead end. Each
  spawn re-execs the test binary, which inherits the parent's open FDs
  and runs Go's `testing` package init. Past a few hundred cycles the
  inner test binaries drag delivery of EOF on their inherited stderr
  pipe ends, leaving drainStderr goroutines blocked in bufio.ReadString
  even after Wait returned. Replacing the helper with /bin/true (for
  quick-exit) and /bin/cat (for echo-loop) sidesteps the recursion and
  is closer to the production case anyway: the broker spawns
  forgejo-mcp, not itself.

* Defensively close stdout/stderr handles in supervisor's reap goroutine
  after cmd.Wait returns. cmd.StderrPipe is supposed to be closed by
  Wait, but under load the kernel doesn't always deliver EOF promptly
  through Go 1.26's pidfd-based wait path; an explicit Close ensures
  drainStderr exits and FDs aren't held longer than needed.

Tests pass under -race with FD/goroutine deltas in single digits across
1000+200 cycles, and Wait4(-1) confirms no zombie children.

Closes forgejo-mcp-broker-31t. Phase 3 complete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 16:04:34 +02:00
.beads test(supervisor): stress tests for FD/goroutine/zombie leaks (forgejo-mcp-broker-31t) 2026-04-27 16:04:34 +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 test(supervisor): stress tests for FD/goroutine/zombie leaks (forgejo-mcp-broker-31t) 2026-04-27 16:04:34 +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.