Commit 864434d
## Summary
PR1 of 2 implementing **ADR-0013** — Streamable HTTP transport alongside
the existing stdio bridge. `BOJ_TRANSPORT=stdio` (default) is unchanged.
`BOJ_TRANSPORT=http` exposes `POST /mcp` + `GET /mcp` (SSE) + `DELETE
/mcp` + `GET /healthz`, with per-session UUIDs in `Mcp-Session-Id` and
bearer / loopback-only-`none` auth. Same `hardeningGate`, same tool
surface, zero new dependencies (built on `Deno.serve` / `node:http`).
## What's in
- **`mcp-bridge/lib/http-transport.js`** — listener, session manager
(30-min idle expiry, SSE-fan-out seam for ADR-0011), bearer / none auth,
refuse-to-start on `auth=none` + non-loopback bind. Deno + Node parity.
Zero deps.
- **`mcp-bridge/lib/dispatcher.js`** — transport-neutral JSON-RPC
dispatch core extracted from `main.js`. Both stdio (`main.js`) and HTTP
(`http-transport.js`) call it; the hardening gate runs once, in the
dispatcher.
- **`mcp-bridge/main.js`** — `BOJ_TRANSPORT=stdio|http|both` selector.
Stdio path preserved exactly; HTTP path delegates to the transport
module; SIGINT / SIGTERM graceful shutdown.
- **`boj://capabilities/deployment` resource** — per-deployment
cartridge availability flagging the five host-local-only cartridges
(`browser-mcp`, `container-mcp`, `local-coord-mcp`, `sandbox-mcp`,
`ffmpeg-mcp`). Clients can detect Worker-incompatible capabilities up
front instead of at first failed invoke. (Static list in PR1; PR2 will
derive from per-cartridge `requires_local` flags.)
- **`glama.json`** env-var declarations: `BOJ_TRANSPORT`,
`BOJ_HTTP_PORT` (default `7780`), `BOJ_HTTP_BIND`, `BOJ_HTTP_AUTH`,
`BOJ_HTTP_AUTH_TOKENS`.
- **`mcp-bridge/tests/http_transport_test.js`** — 26 new tests (session
manager, auth modes, parse / size / unknown-session errors, end-to-end
POST flow, bearer reject/accept, DELETE teardown, healthz, deployment
resource).
- **CHANGELOG** entry under Unreleased.
## What's deferred to PR 2
Explicitly owed; tracked here:
- **mTLS auth** (`BOJ_HTTP_AUTH=mtls`) — `configFromEnv` rejects this
mode with a clear "owed in PR2" message.
- **OIDC auth** (`BOJ_HTTP_AUTH=oidc`) — same.
- **Cloudflare Workers** deployment guide
(`docs/deployment/CLOUDFLARE-WORKERS.md`).
- **Durable-Object wrapper** around the session manager.
- **Static cartridge-manifest bundling** for cold-start.
- **Per-cartridge `requires_local`** flag plumbed from each
`cartridge.json` (the deployment resource is static in PR1).
- **`wrangler.toml`** example.
## Test plan
- [x] `node --test mcp-bridge/tests/dispatch_test.js
mcp-bridge/tests/http_transport_test.js` — **41/41 pass** (baseline 15 +
26 new)
- [x] `deno test --allow-net --allow-env --allow-read mcp-bridge/tests/`
— **41/41 pass**
- [x] stdio smoke unchanged on Deno + Node (`echo '{...initialize...}' |
deno run ...` / `... | node ...`)
- [x] `BOJ_TRANSPORT=http BOJ_HTTP_PORT=7780 ... main.js &` + `curl -X
POST http://localhost:7780/mcp ...` returns `Mcp-Session-Id` header on
Deno + Node
- [x] `BOJ_HTTP_AUTH=bearer BOJ_HTTP_AUTH_TOKENS=...` → 401 with no/bad
token, 200 with valid token
- [x] `BOJ_HTTP_AUTH=none BOJ_HTTP_BIND=0.0.0.0` refuses to start, logs
error, exits 1
- [x] SSE `GET /mcp` returns `event: ready` with session id on connect
## Sequencing notes
- **No stdio refactor in user-visible behaviour.** The dispatcher
extraction is a pure refactor of how `handleMessage` is structured;
every existing test still passes. The only observable change is that an
uncaught dispatch exception now yields a `-32603` JSON-RPC error
response instead of being silently swallowed by the top-level `.catch(()
=> {})` — strictly an improvement.
- **Single bridge process, two transports** (per ADR-0002 BoJ-only MCP
rule). No sub-bridge or sub-server pattern.
- **ADR-0013 open questions** — resolved with defensible defaults in
this PR:
1. **Session storage** — ephemeral in-memory, 30-min idle expiry.
Durable storage owed in PR2.
2. **Cold-start on Workers** — deferred to PR2 (static manifest
bundling).
3. **SSE stream lifetime** — keepalive ping every 25 s; clients
reconnect on close. Workers-specific stream limits documented in PR2.
4. **Backpressure** — current `hardeningGate` per-process rate limit
applies; per-session rate limiting and global queue depth are PR2.
5. **TLS termination** — bridge listens HTTP on loopback by default;
reverse-proxy assumed (documented). Direct-TLS opt-in via PR2.
6. **Cowboy listener consolidation** — PR1 keeps the bridge on its own
port (`7780`); same-port path-routing to the Cowboy listener (`7700`)
tracked as a future consolidation in ADR-0013.
Refs #87 (item 14), ADR-0013.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 899cbc8 commit 864434d
7 files changed
Lines changed: 1426 additions & 530 deletions
File tree
- mcp-bridge
- lib
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
32 | 47 | | |
33 | 48 | | |
34 | 49 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
76 | 76 | | |
77 | 77 | | |
78 | 78 | | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
79 | 103 | | |
80 | 104 | | |
81 | 105 | | |
| |||
0 commit comments