Skip to content

StreamableHTTPServerTransport has no public API to reconstruct a session-aware transport from persisted session data #1658

@arthurchan35

Description

@arthurchan35

Summary
The issue is for Stateful server Persistent Storage pattern for version 1.27.1 and the problem should also be there in the main branch.
The SDK documentation describes three multi-node deployment patterns in src/examples/README.md, including Pattern 2 (Persistent Storage) where session state is stored in an external database and any node can serve any session. However,
StreamableHTTPServerTransport (and its underlying WebStandardStreamableHTTPServerTransport) provides no public API to reconstruct a session-aware transport instance from externally persisted session data.

To Reproduce
Any client starting a connection to the MCP server in a multi-node environment would fail:
The first request to the initialize method goes through and returns a response with an mcp-session-id. However, the second request to the notifications/initialized method(and any following requests) fails because it hits a different pod/node while carrying the mcp-session-id issued by the first pod/node.

My analysis
WebStandardStreamableHTTPServerTransport stores session state (sessionId, _initialized) as private instance fields that are only set during the initialize handshake on the original transport instance. The validateSession() method
— which checks the mcp-session-id request header against this.sessionId — is also private and cannot be overridden. This means in a multi-node environment, when a request arrives at a different node than the one that handled initialize, there is no supported way to create a transport that:

  1. Knows the existing session ID
  2. Validates incoming mcp-session-id headers against it
  3. Includes mcp-session-id in response headers

Workaround:

  • Stateless mode (sessionIdGenerator: undefined) for non-intialize requests — which disables session validation entirely, violating the MCP spec's requirement that servers validate session IDs
  • Hydrate/re-construct a transport instance by setting private fields — setting _webStandardTransport.sessionId and _webStandardTransport._initialized directly, which is what I chose to go with, but of cause it's not robust without SDK official support.

Suggestions
Introduce a public mechanism to construct a transport that is "pre-initialized" with an existing session ID. This could take several forms:

  • A hydrate(sessionId: string) method on the transport that sets sessionId and marks the transport as initialized
  • Constructor options like existingSessionId that skip the initialization requirement
  • A TransportStore / TransportFactory abstraction that the SDK's request handling delegates to for session-aware transport creation/retrieval — replacing the simple Map<sessionId, transport> pattern in the examples

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions