Skip to content

feat(mcp-bridge): Streamable HTTP transport (epic #87 item 14, PR1) - #105

Merged
hyperpolymath merged 1 commit into
mainfrom
feat/streamable-http-transport-item14
May 20, 2026
Merged

feat(mcp-bridge): Streamable HTTP transport (epic #87 item 14, PR1)#105
hyperpolymath merged 1 commit into
mainfrom
feat/streamable-http-transport-item14

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

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.jsBOJ_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

  • node --test mcp-bridge/tests/dispatch_test.js mcp-bridge/tests/http_transport_test.js41/41 pass (baseline 15 + 26 new)
  • deno test --allow-net --allow-env --allow-read mcp-bridge/tests/41/41 pass
  • stdio smoke unchanged on Deno + Node (echo '{...initialize...}' | deno run ... / ... | node ...)
  • BOJ_TRANSPORT=http BOJ_HTTP_PORT=7780 ... main.js & + curl -X POST http://localhost:7780/mcp ... returns Mcp-Session-Id header on Deno + Node
  • BOJ_HTTP_AUTH=bearer BOJ_HTTP_AUTH_TOKENS=... → 401 with no/bad token, 200 with valid token
  • BOJ_HTTP_AUTH=none BOJ_HTTP_BIND=0.0.0.0 refuses to start, logs error, exits 1
  • 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

Implements the bridge half of ADR-0013: HTTP+SSE transport alongside
stdio. PR1 of 2 — Workers / Durable-Objects shim and mTLS/OIDC auth
are owed in PR2.

What's in:
- BOJ_TRANSPORT=stdio|http|both mode selection in main.js
- lib/http-transport.js — POST /mcp + GET /mcp (SSE) + DELETE /mcp +
  GET /healthz; per-session UUID issuance via Mcp-Session-Id; 30-min
  idle expiry; bearer / none auth; loopback-refuse on auth=none +
  non-loopback bind; zero new deps (Deno.serve / node:http)
- lib/dispatcher.js — transport-neutral dispatch core consumed by both
  stdio (main.js) and HTTP, same hardeningGate, same tool surface
- boj://capabilities/deployment resource flagging the 5 host-local-only
  cartridges (browser-mcp, container-mcp, local-coord-mcp, sandbox-mcp,
  ffmpeg-mcp) so clients can detect Worker-incompatible capabilities
- glama.json declares BOJ_TRANSPORT, BOJ_HTTP_PORT (7780),
  BOJ_HTTP_BIND, BOJ_HTTP_AUTH, BOJ_HTTP_AUTH_TOKENS
- tests/http_transport_test.js — 26 tests covering session manager,
  auth, parse / size / unknown-session paths, end-to-end POST flow,
  bearer reject/accept, DELETE teardown, healthz, deployment resource

Test plan:
- node --test mcp-bridge/tests/dispatch_test.js mcp-bridge/tests/http_transport_test.js
  → 41/41 pass (was 15/15 baseline; +26 new)
- deno test --allow-net --allow-env --allow-read mcp-bridge/tests/
  → 41/41 pass
- stdio smoke (Deno + Node): unchanged behavior
- http smoke (Deno + Node): Mcp-Session-Id issued, dispatch parity
- bearer reject (no token / bad token) → 401; valid token → 200
- BOJ_HTTP_AUTH=none + BOJ_HTTP_BIND=0.0.0.0 refuses to start, exit 1

What's owed in PR2 (tracked):
- mTLS + OIDC auth modes
- Cloudflare Workers deployment guide + wrangler.toml example
- Durable-Object wrapper around SessionManager
- Static cartridge-manifest bundling for cold-start
- Per-cartridge `requires_local` flag wired from cartridge.json
  (deployment resource list is static for PR1)

Refs #87 (item 14), ADR-0013.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 31 issues detected

Severity Count
🔴 Critical 19
🟠 High 5
🟡 Medium 7

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Stale AI session file -- delete",
    "type": "stale",
    "file": "GEMINI.md",
    "action": "delete",
    "rule_module": "root_hygiene",
    "severity": "medium"
  },
  {
    "reason": "Issue in quality.yml",
    "type": "missing_workflow",
    "file": "quality.yml",
    "action": "create",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "Issue in security-policy.yml",
    "type": "missing_workflow",
    "file": "security-policy.yml",
    "action": "create",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
    "type": "unpinned_action",
    "file": "governance.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "Python file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/boj-server/boj-server/.github/scripts/validate-eclexiaiser.py",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/boj-server/boj-server/cartridges/sanctify-mcp/adapter/mod.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/boj-server/boj-server/cartridges/academic-workflow-mcp/adapter/mod.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/boj-server/boj-server/cartridges/fireflag-mcp/adapter/mod.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/boj-server/boj-server/cartridges/ephapax-mcp/adapter/mod.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/boj-server/boj-server/cartridges/bofig-mcp/adapter/mod.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

@hyperpolymath
hyperpolymath merged commit 864434d into main May 20, 2026
16 of 23 checks passed
@hyperpolymath
hyperpolymath deleted the feat/streamable-http-transport-item14 branch May 20, 2026 07:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant