modelcontextprotocol / modelcontextprotocol/typescript-sdk
StreamableHTTPServerTransport destroys sessions on TCP keepalive timeout (regression in 1.25+)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 13.4k
- Forks
- 2.2k
- Avg merge
- 3d 15h
- Merged PRs (30d)
- 4
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.1
Test 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
- modelcontextprotocol/typescript-sdk#1708 — client-side 404 handling for expired sessions
- modelcontextprotocol/typescript-sdk#389 — session expiry status codes
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at StreamableHTTPServerTransport and reproduce the sequence with mcp-proxy: initialize, wait 8 seconds, then send tools/list with the same Mcp-Session-Id. Trace what happens when the TCP connection closes and compare SDK 1.24.x with 1.27.x. Done means sessions survive independent HTTP requests and are removed only by DELETE or an explicit timeout policy.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100