Skip to content

feat: MCP Streamable HTTP transport#407

Merged
jithinraj merged 3 commits intomainfrom
feat/mcp-http-transport
Feb 23, 2026
Merged

feat: MCP Streamable HTTP transport#407
jithinraj merged 3 commits intomainfrom
feat/mcp-http-transport

Conversation

@jithinraj
Copy link
Member

@jithinraj jithinraj commented Feb 22, 2026

Summary

  • Add Streamable HTTP transport alongside stdio for remote MCP client connectivity (DD-119)
  • Session-isolated architecture: each HTTP session gets its own McpServer + StreamableHTTPServerTransport pair (CVE-2026-25536 defense)
  • Security hardening per DD-123: localhost-only default, CORS deny-all, 1MB body limit, per-session + per-IP rate limiting, Host/Origin validation, Node.js server timeouts (slowloris defense)
  • Conditional RFC 9728 PRM endpoint: served only when both --authorization-servers and --public-url are configured
  • Unprotected mode only in v0.11.0; full OAuth 2.1 deferred to v0.11.x+

New files

  • src/http-transport.ts: HTTP server setup, CORS, health endpoint, conditional PRM, request routing
  • src/session-manager.ts: Mcp-Session-Id lifecycle, TTL eviction (default 30 min), max sessions (default 100), periodic sweep
  • tests/http/http-transport.test.ts: 15 integration tests (health, 404, 405, session ID requirements, PRM, CORS, size limits, protocol version, preflight)
  • tests/http/session-manager.test.ts: 10 unit tests (create, isolate, lookup, terminate, eviction, cleanup)

Modified files

  • src/cli.ts: Added --transport, --port, --host, --cors-origins, --authorization-servers, --public-url, --trust-proxy flags with HTTP/stdio transport branching
  • src/infra/constants.ts: Added HTTP transport default constants

Endpoints

Method Path Behavior
POST /mcp JSON-RPC tool calls (requires Mcp-Session-Id after init)
DELETE /mcp Terminate session
GET /health Health check (200 + version)
GET /.well-known/oauth-protected-resource[/path] PRM (conditional)
GET /mcp 405 Method Not Allowed

CLI flags

--transport <type>              stdio (default) or http
--port <number>                 HTTP port (default: 3000)
--host <address>                Bind address (default: 127.0.0.1)
--cors-origins <list>           Allowed CORS origins (comma-separated)
--authorization-servers <list>  OAuth authorization server URIs
--public-url <url>              Canonical public URL (required for PRM)
--trust-proxy                   Trust X-Forwarded-For (off by default)

Test plan

  • Existing 241 mcp-server tests pass unchanged
  • 25 new tests pass (session manager + HTTP transport)
  • Build: 76/76 targets pass
  • Lint, typecheck, format: clean
  • guard.sh + check-planning-leak.sh: clean

Non-goals (explicitly deferred)

  • Full OAuth 2.1 server implementation (deferred to v0.11.x+): this PR adds OAuth readiness hooks only (PRM discovery endpoint). Token validation and protected mode (401 + WWW-Authenticate) are not implemented.
  • SSE streaming within Streamable HTTP: spec allows JSON-only responses; SSE streaming mechanism is additive and deferred.
  • MCP SDK v2 support: v2 is pre-alpha. Pinned to ~1.27.0 (v1.x stable line).
  • Multi-node session replication: session state is in-memory only. Distributed session stores are out of scope.

Spec deltas

Spec Requirement Status
MCP 2025-06-18 Streamable HTTP transport (POST /mcp) Implemented (JSON-only responses)
MCP 2025-06-18 Mcp-Session-Id lifecycle Implemented (create, require, terminate)
MCP 2025-06-18 Session ID: visible ASCII 0x21-0x7E Validated server-side, max 128 chars
MCP 2025-11-25 Authorization via RFC 9728 PRM Discovery only (conditional); no token validation
MCP 2025-06-18 Accept header: application/json, text/event-stream Tolerant (log warning, do not reject)
MCP 2025-06-18 MCP-Protocol-Version header Validated; 400 on unsupported, assume 2025-03-26 if missing
CVE-2026-25536 Per-session transport isolation Enforced by design (SessionManager)
RFC 9728 Path-aware PRM routing Implemented with URL validation

Round 2 hardening (commit 0912520)

Trust-proxy upgrade

  • TrustProxyValue expanded with linklocal (169.254.0.0/16, fe80::/10), private (RFC 1918), all (discouraged) presets
  • IPv6-mapped IPv4 (::ffff:) recognized in all presets
  • Replaces boolean flag with enterprise-safe enum preventing one-flag-makes-prod-unsafe mistakes

CVE-2026-25536 regression test

  • Explicit test: 3 concurrent sessions verified isolated (unique server, transport, session ID per session)
  • Termination of one session does not affect others

MCP compliance test matrix

  • docs/security/MCP-COMPLIANCE-MATRIX.md: 35 requirements mapped to test names
  • Covers Transport (T1-T13), Security (S1-S9), Session Isolation (C1-C6), Authorization (A1-A7)

Deployer security checklist

  • docs/security/HTTP-TRANSPORT-SECURITY.md: production deployment guidance

Migrate all 9 packages from Zod 3.22.x to Zod 4.3.6. This is a
breaking change for downstream TypeScript consumers who compile
against exported @peac/schema types (z.infer<> types are not
assignment-compatible across Zod majors).

Schema changes:
- z.record() single-arg form removed in Zod 4: add explicit
  z.string() key schema in 5 locations (schema, control, mcp-server)
- .default({}) on object schemas requires output-type-compatible
  values in Zod 4: use .prefault({}) for input-type defaults (policy.ts)
- ZodError.errors alias removed: use .issues in test assertions
- issue.path typed as PropertyKey[] (was (string | number)[]): add
  casts at 2 call sites (protocol/issue.ts, cli/validators.ts)

No behavioral changes to schema validation, receipt issuance, or
verification. All 4138 tests pass across 167 test files.

Workspace enforcement:
- pnpm.overrides forces zod@^4.3.6 across all workspace packages
- Prevents mixed Zod 3/4 which causes runtime TypeError
Add HTTP transport alongside stdio for remote MCP client connectivity.
Each session gets isolated McpServer + transport (CVE-2026-25536 defense).

New files:
- src/http-transport.ts: HTTP server with CORS, rate limiting, PRM
- src/session-manager.ts: session lifecycle, TTL eviction, isolation

Endpoints: POST /mcp (JSON-RPC), DELETE /mcp (terminate session),
GET /health, GET /.well-known/oauth-protected-resource (conditional PRM)

Security (DD-123): localhost-only default, CORS deny-all, 1MB body limit,
per-session + per-IP rate limiting, Host/Origin validation, server timeouts

CLI flags: --transport, --port, --host, --cors-origins,
--authorization-servers, --public-url, --trust-proxy

Tests: 25 new (session manager + HTTP transport integration)
@jithinraj jithinraj changed the base branch from feat/zod4-schema to main February 22, 2026 20:58
@jithinraj jithinraj changed the title feat: MCP Streamable HTTP transport (DD-119, DD-123) feat: MCP Streamable HTTP transport Feb 22, 2026
@jithinraj jithinraj merged commit c60181f into main Feb 23, 2026
7 checks passed
@jithinraj jithinraj deleted the feat/mcp-http-transport branch February 24, 2026 21:52
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