modelcontextprotocol / modelcontextprotocol/typescript-sdk

StreamableHTTPServerTransport destroys sessions on TCP keepalive timeout (regression in 1.25+)

Open
#1,852 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug needs repro
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:

  1. POST /mcp with initialize — 200, get Mcp-Session-Id
  2. Wait 8 seconds (past Node.js default keepAliveTimeout of 5s)
  3. 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

  • modelcontextprotocol/typescript-sdk#1708 — client-side 404 handling for expired sessions
  • modelcontextprotocol/typescript-sdk#389 — session expiry status codes

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.