Skip to content

feat: serve modern MCP with a stateless SDK v2 handler#176

Merged
mattzcarey merged 3 commits into
mainfrom
feat/mcp-sdk-v2-stateless
Jul 20, 2026
Merged

feat: serve modern MCP with a stateless SDK v2 handler#176
mattzcarey merged 3 commits into
mainfrom
feat/mcp-sdk-v2-stateless

Conversation

@mattzcarey

@mattzcarey mattzcarey commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • upgrade the split MCP TypeScript SDK client/server pins from 2.0.0-beta.2 to the latest published 2.0.0-beta.4
  • replace the hand-wired raw Streamable HTTP transport with createMcpHandler(factory) directly from @modelcontextprotocol/server
  • serve MCP 2026-07-28 from a fresh server per request while retaining the SDK's default stateless 2025 compatibility path
  • preserve OAuth and direct Cloudflare API-token authentication without adding an Agents SDK dependency
  • validate deployment-static Host and browser Origin allowlists before authentication, and serve modern CORS preflights
  • keep OAuth/KV/R2/Analytics Engine/Worker Loader application state while retaining no MCP session, transport, or replay state

Each authenticated request creates a TypeScript SDK handler whose factory closes over its validated AuthProps, preserving the explicit props -> createServer(props) data flow used before this migration. No Node ambient types, AsyncLocalStorage, or implicit global auth state is required.

One response-shaping detail is intentional: ordinary modern requests return JSON under the SDK's responseMode: "auto"; claimless stateless 2025 requests use the upstream SSE response shape. Both paths issue no Mcp-Session-Id.

Validation

  • npm ci
  • npm run check — 17 test files / 289 tests
  • npm ls @modelcontextprotocol/server @modelcontextprotocol/client @modelcontextprotocol/core --depth=1
  • npm audit --omit=dev — 0 vulnerabilities
  • production upload — 1294.29 KiB raw / 242.92 KiB gzip, 120 ms startup

Coverage includes modern discovery/list/call, Code Mode and non-Code-Mode dispatch, concurrent auth and factory isolation, stateless 2025 fallback, GET/DELETE rejection, OAuth-issued tokens, Host/Origin rejection, and CORS preflights.

Production rollout

Deployed from runtime commit ba95888:

  • Worker version: bca4d618-2eab-429b-a62a-71623c98c55e
  • public OAuth metadata: pass
  • unauthenticated /mcp guard: pass (401)
  • same-origin CORS preflight: pass (204)
  • hostile browser Origin rejection: pass (403)
  • modern server/discover: pass, no session ID
  • modern tools/list: pass, no session ID
  • modern Code Mode execute read (GET /user): pass
  • modern non-Code-Mode get_user: pass
  • stateless legacy Code Mode read: pass, SSE, no session ID
  • authenticated legacy GET rejection: pass (405)

The Wrangler token was read in-memory for smoke tests and was not printed or persisted.

@mattzcarey
mattzcarey merged commit b21ae22 into main Jul 20, 2026
5 checks passed
@mattzcarey
mattzcarey deleted the feat/mcp-sdk-v2-stateless branch July 20, 2026 19:09
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