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.1
Test sequence:
- POST
/mcp with initialize — 200, get Mcp-Session-Id
- Wait 8 seconds (past Node.js default
keepAliveTimeout of 5s)
- POST
/mcp with tools/list and 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.x
mcp-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
Description
MCP sessions served by
StreamableHTTPServerTransportare destroyed when the underlying TCP connection closes due to the Node.js defaultkeepAliveTimeout(5 seconds). Subsequent requests with a validMcp-Session-Idreceive 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-Idheader is the continuity mechanism, not a persistent TCP connection.Reproduction
Using
mcp-proxy(npm) as the server wrapper:Test sequence:
/mcpwithinitialize— 200, getMcp-Session-IdkeepAliveTimeoutof 5s)/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
/mcpduring 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 nexttools/callfails.Affected setups:
Expected behavior
Sessions should persist independently of TCP connection lifetime. The
Mcp-Session-Idheader 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)server.keepAliveTimeout: default 5000msRelated