MCP Streamable HTTP client: surface session_terminated to model instead of auto-reinitializing per spec

Resolved 💬 3 comments Opened May 5, 2026 by zulven Closed May 6, 2026

Symptom

When the user's MCP server sleeps, redeploys, or returns 404 mcp_session_terminated for any other reason, claude-code surfaces the error to the model as a tool result instead of auto-reinitialising the session and replaying the call. The model sees something like:

Streamable HTTP error: Error POSTing to endpoint: {"type":"error","error":{"type":"not_found_error","message":"MCP session has been terminated or no longer exists on the server.","details":{"error_code":"mcp_session_terminated"}},"request_id":"req_011Caj9XocsEiGmk1EK83q7T"}

…in the middle of an otherwise-successful conversation. The next tool call usually succeeds (the client does re-init implicitly on the next request — it just leaks the failure of the one that triggered it).

Why it's a bug, not a feature

Per the MCP Streamable HTTP spec, §Session Management point 4:

When a client receives HTTP 404 in response to a request containing an MCP-Session-Id, it MUST start a new session by sending a new InitializeRequest without a session ID attached.

The spec is explicit that 404 mcp_session_terminated is a normal lifecycle event the client must transparently handle — not an error to surface to the model. claude-code's current behaviour is non-spec-compliant.

Repro

Any production MCP server that drops in-memory sessions across process boundaries reproduces this. Common shapes:

  • Fly.io / GCP Cloud Run / AWS ECS auto-stop: the server's machine sleeps after idle, wakes on the next request as a fresh process with empty session state.
  • Server redeploy: same effect.
  • Horizontal scaling without sticky sessions: a different replica gets the request than handled the initialize.
  • Network blip that the upstream proxy translates to a session-loss.

The 2026 MCP roadmap calls this out explicitly as a top problem ("stateful sessions fight with load balancers") and the canonical persistence pattern is still unresolved upstream (modelcontextprotocol/python-sdk#880). Servers can mitigate (we have — see "Workaround" below), but every server doing it independently doesn't scale; the spec puts the responsibility on the client and the client is the right layer to fix this in once for everyone.

Desired behaviour

When the Streamable HTTP transport gets back a 404 with the structured mcp_session_terminated shape (or any 404 to a request bearing an mcp-session-id):

  1. Discard the local mcp-session-id cookie.
  2. POST a fresh InitializeRequest to the same endpoint without a session-id, capture the new id from the response header.
  3. Re-issue the original tool call against the new session.
  4. Return the tool's actual result to the model. Surface the error only if step 2 or 3 also fails (a real outage rather than a stale session).

This is the spec-prescribed behaviour and the only way I'm aware of to deliver consistent UX across surfaces (Code, Desktop, mobile, web) without each MCP server independently implementing server-side workarounds.

Workaround we shipped (alfred-shell)

Until claude-code lands the spec-compliant client behaviour, our MCP server now lazy-recreates sessions: on a request with an unknown mcp-session-id and a valid auth token, it transparently creates a fresh transport bound to that exact id rather than 404'ing. Source if useful as a reference: zulven/alfred@3e16e1e — alfred-shell/src/http.ts.

This works around the symptom for our users but doesn't fix it for the broader MCP ecosystem — most third-party MCP servers will still 404, and end-users on Code / Desktop / mobile will still see the error mid-conversation.

Severity

Medium. Not a hard failure — the next tool call usually succeeds, so it's recoverable. But it leaks an internal protocol detail to the model (which then often "narrates" the failure to the user, training in a "retry" pattern), and reduces trust in the MCP layer. Easy fix at exactly one layer.

Happy to provide more detail or test fixes against alfred-shell if useful.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗