fix: use per-session transports in HTTP mode instead of one shared transport#3
Open
informatJonas wants to merge 1 commit into
Open
Conversation
…ansport StreamableHTTPServerTransport supports exactly one session per instance (it throws "Server already initialized" on a second initialize call). The original code created a single transport shared across every incoming connection, so only the first client to ever connect could use the server — every reconnect after that (health checks, idle timeouts, a second client) failed permanently until the process was restarted. Fixed by keeping a Map of sessionId -> transport, creating a fresh MCP server + transport pair on each new initialize request, and routing subsequent requests by the Mcp-Session-Id header. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What's broken
StreamableHTTPServerTransport(from@modelcontextprotocol/sdk) supports exactly one session per instance — it throwsServer already initializedon a secondinitializecall against the same transport. The HTTP server (src/http-server.ts) creates a single shared transport instance for the entire process and routes every incoming connection through it:Result: only the first client that ever connects can use the server. Any subsequent
initialize— a second client, or the same client reconnecting after an idle timeout / health check — fails permanently until the process is restarted:{"jsonrpc":"2.0","error":{"code":-32000,"message":"Bad Request: Mcp-Session-Id header is required"},"id":null}and the server logs:
This makes the HTTP transport unreliable behind any client that periodically re-checks connectivity (e.g. Claude Code's
claude mcp listhealth check does a fresh handshake) — filed in more detail as #2 with full repro steps.The fix
Keep a
Map<sessionId, StreamableHTTPServerTransport>. On each request:Mcp-Session-Idheader matches a known session, route to that session's transportinitialize), create a newServer+StreamableHTTPServerTransportpair, register it in the map once the SDK assigns a session id (onsessioninitialized), and remove it ononsessionclosedThis is the standard multi-session pattern for
StreamableHTTPServerTransport(one transport instance per session, looked up by session id).Testing
Verified against a self-hosted Penpot 2.16.2 instance:
initializeconcurrently without a restart (previously the 2nd+ would fail)get_profile,list_teams, etc.) work correctly on each session/healthendpoint still reports the rightactiveSessionCountCloses #2.