Skip to content

refactor!: switch to stateless HTTP transport#304

Merged
GLips merged 6 commits intomainfrom
refactor/stateless-http-transport
Mar 24, 2026
Merged

refactor!: switch to stateless HTTP transport#304
GLips merged 6 commits intomainfrom
refactor/stateless-http-transport

Conversation

@GLips
Copy link
Copy Markdown
Owner

@GLips GLips commented Mar 24, 2026

Summary

  • Stateless HTTP: Each POST creates a fresh McpServer + StreamableHTTPServerTransport with sessionIdGenerator: undefined. Removes the entire session registry, SSE transport, /messages endpoint, and progress notification interval.
  • Backward compat: StreamableHTTP is served at both /mcp and /sse — existing client configs that point at /sse keep working since modern MCP clients probe with POST first.
  • Express 5: Upgraded from Express 4 to align with the MCP SDK's own dependency. Async route handler errors are natively caught.
  • DNS rebinding protection: Uses the SDK's createMcpExpressApp() instead of raw express(), which applies localhost Host header validation automatically.
  • Error handling: Express error middleware returns JSON-RPC errors instead of HTML 500. Per-request transport.close() / mcpServer.close() cleanup on response end.
  • GET/DELETE return 405 on both /mcp and /sse.

Net: -514 lines across server + tests.

Breaking changes

  • SSE protocol transport is removed. Clients using the old SSEClientTransport must upgrade to StreamableHTTPClientTransport. The /sse URL still works.
  • No mcp-session-id header in responses. Clients relying on session persistence across requests will see each request as independent.

Test plan

  • pnpm test — 46 pass, 1 skipped (integration, needs API key)
  • pnpm type-check — clean
  • pnpm lint — clean
  • StreamableHTTP connects and lists tools via /mcp
  • StreamableHTTP connects and lists tools via /sse (backward compat)
  • Responses contain no mcp-session-id header
  • GET and DELETE on /mcp and /sse return 405
  • Multiple concurrent StreamableHTTP clients work simultaneously
  • Server starts and stops cleanly

GLips added 6 commits March 24, 2026 08:15
Each request creates its own McpServer and transport with
sessionIdGenerator: undefined. Removes the session registry,
SSE transport, /messages endpoint, and progress notification
interval. StreamableHTTP is served at both /mcp and /sse for
backward compatibility. GET and DELETE return 405.
Express 4 does not catch rejected promises from async handlers.
Without a try/catch, a failure in connect() or handleRequest()
causes an unhandled rejection that crashes the process.
Aligns with the MCP SDK which already depends on Express 5.
Express 5 natively catches async route handler rejections,
so the manual try/catch added in the previous commit is no
longer needed.
Close transport and McpServer when the response ends, matching
the SDK's recommended pattern for stateless servers. Add Express
error-handling middleware that returns a JSON-RPC error response
instead of Express's default HTML 500.
Replace raw express() with the SDK's createMcpExpressApp(), which
applies localhost DNS rebinding protection middleware automatically.
This also handles express.json() globally, removing the need for
per-route body parsing.
@GLips GLips merged commit 9dfb1cb into main Mar 24, 2026
1 check passed
@GLips GLips deleted the refactor/stateless-http-transport branch March 24, 2026 17:47
GLips added a commit that referenced this pull request Mar 24, 2026
Keep active connection tracking and graceful shutdown from the
progress notifications branch. The squash merge of #304 into
main didn't include these additions.
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