-
Notifications
You must be signed in to change notification settings - Fork 1.8k
StreamableHTTPServerTransport destroys sessions on TCP keepalive timeout (regression in 1.25+) #1852
Description
Description
MCP sessions served by StreamableHTTPServerTransport are destroyed when the underlying TCP connection closes due to the Node.js default keepAliveTimeout (5 seconds). Subsequent requests with a valid Mcp-Session-Id receive a 400 "Session not found" error.
This is a regression introduced between SDK versions 1.24.x and 1.27.x. Sessions should survive across independent HTTP requests — the Mcp-Session-Id header is the continuity mechanism, not a persistent TCP connection.
Reproduction
Using mcp-proxy (npm) as the server wrapper:
# Works — sessions survive 8s idle
npx -y mcp-proxy@6.4.3 --port 9999 -- npx -y @modelcontextprotocol/server-everything
# mcp-proxy 6.4.3 uses @modelcontextprotocol/sdk ^1.24.3
# Broken — sessions die after 5s idle
npx -y mcp-proxy@6.4.4 --port 9999 -- npx -y @modelcontextprotocol/server-everything
# mcp-proxy 6.4.4 uses @modelcontextprotocol/sdk ^1.27.1Test sequence:
- POST
/mcpwithinitialize— 200, getMcp-Session-Id - Wait 8 seconds (past Node.js default
keepAliveTimeoutof 5s) - POST
/mcpwithtools/listand the session ID — 400 "Session not found"
With SDK 1.24.x, step 3 succeeds. With SDK 1.27.x, it fails.
If a client keeps an SSE GET stream open to /mcp during the idle period, the session survives. This confirms the session is being destroyed when the TCP connection closes, not on an application-level timeout.
Impact
This breaks any proxy or gateway that uses HTTP/1.1 connection pooling to talk to a Streamable HTTP MCP server. The gateway opens a connection, sends initialize + tools/list, the connection goes idle, Node.js closes it after 5 seconds, and the session is destroyed. The next tools/call fails.
Affected setups:
- AgentGateway (agentgateway.dev) proxying to mcp-proxy-wrapped servers
- Any HTTP reverse proxy with connection pooling in front of an SDK-based MCP server
Expected behavior
Sessions should persist independently of TCP connection lifetime. The Mcp-Session-Id header identifies the session across requests. A session should only be destroyed by an explicit DELETE request or a server-side session timeout policy, not by TCP keepalive.
Environment
@modelcontextprotocol/sdk: regression between 1.24.x and 1.27.xmcp-proxy: 6.4.3 (works) vs 6.4.4 (broken, only change was SDK bump)- Node.js: tested on v22 and v25
server.keepAliveTimeout: default 5000ms
Related
- StreamableHTTPClientTransport doesn't handle 404 per spec (no session clear + re-init) #1708 — client-side 404 handling for expired sessions
- Streamable HTTP examples use incorrect status code for invalid session IDs #389 — session expiry status codes