[BUG] Stale MCP Session ID Cached Permanently

Resolved 💬 3 comments Opened Apr 11, 2026 by thefrankyes1 Closed Apr 14, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Observed behavior

When an MCP server that uses Streamable HTTP transport with in-process session state is restarted (redeployment, container restart, OOM kill, Watchtower image pull), Claude Code's MCP client caches the old session ID permanently for the lifetime of the Claude Code process.

Every subsequent tool call to that MCP server fails with:

Streamable HTTP error: Error POSTing to endpoint:
{"jsonrpc":"2.0","id":"server-error","error":{"code":-32600,"message":"Session not found"}}

The failure is total — every tool on that server returns this error, not just the first call. The user has no way to recover from inside the session. claude mcp subcommands (add, get, list, remove, reset-project-choices) do not offer a live in-session reconnect. The only recovery is a full Claude Code process restart, which destroys all in-session context and continuity.

This was reproduced six times on first-party MCP infrastructure over a one-week period, across multiple Claude Code sessions. The server itself is healthy (direct HTTP calls to the same backend endpoints succeed); the MCP layer is the exclusive failure point.

What Should Happen?

Expected behavior

One of the following:

Option A (preferred) — Silent auto-reinit: When the client receives {"code":-32600,"message":"Session not found"}, it should silently re-send the MCP initialize request to obtain a fresh session ID, then retry the original tool call transparently. The MCP protocol allows this — a client can always re-initialize. The user should not need to restart Claude Code to recover from a server-side session wipe.

Option B (acceptable) — In-session reconnect command: Add a claude mcp reconnect <server-name> CLI command (or equivalent REPL command) that forces a fresh initialize handshake against the named server and refreshes the client's cached session ID without restarting the process.

Error Messages/Logs

## Working hypothesis

Full three-layer diagnosis documented at:
[`openspec/projects/bonehive/research-inputs/mcp-session-diagnosis.md`](../../projects/bonehive/research-inputs/mcp-session-diagnosis.md)

Summary (confirmed for `fastapi_mcp v0.4.0` + `mcp` SDK v1.27.0):

1. The MCP server stores Streamable HTTP session state in **in-process Python memory**. Any process restart wipes all session records — this is expected behavior for a default in-memory session store.
2. A fresh MCP handshake (`initialize` → `notifications/initialized` → `tools/list`) against the restarted server succeeds end-to-end — the server itself is healthy.
3. Claude Code's client appears to send the stale pre-restart session ID to every subsequent tool call and not re-initialize after receiving a deterministic `"Session not found"` error. The server side of session recovery works (step 2 confirms this); the client recovery does not.

Stale-session probe (run against the live server post-restart):


curl -s -X POST http://knowledge-db:8000/mcp \
  -H "Content-Type: application/json" \
  -H "mcp-session-id: stale-nonexistent-session-abc123" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
# Returns: {"jsonrpc":"2.0","id":"server-error","error":{"code":-32600,"message":"Session not found"}}


## Minimal reproduction (confirmed with `fastapi_mcp v0.4.0`)


# minimal_server.py
from fastapi import FastAPI
from fastapi_mcp import FastApiMCP

app = FastAPI()
mcp = FastApiMCP(app)
mcp.mount()  # default: in-memory session state

Steps to Reproduce

Steps:

  1. Start the minimal server: uvicorn minimal_server:app --port 8000
  2. Add it to Claude Code .mcp.json as a url entry: {"type": "url", "url": "http://localhost:8000/mcp"}
  3. Open a Claude Code session. Confirm the server's MCP tools appear (/mcp).
  4. Restart the server: stop uvicorn, then restart it. The in-memory session store is wiped.
  5. In the same Claude Code session (do NOT restart Claude Code), invoke any tool from that server.

Expected: tool call succeeds after transparent re-init, or returns a user-actionable error with a reconnect path.
Actual: every call returns {"code":-32600,"message":"Session not found"} permanently for the lifetime of the Claude Code process.

Confirming the server is healthy post-restart:

curl -s -X POST http://localhost:8000/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' 
# Returns: {"result": {"protocolVersion":"2024-11-05", ...}}  ← server works

Environment

  • Claude Code version: 2.1.101
  • OS: macOS 15 (Darwin 25.4.0, arm64)
  • MCP transport: Streamable HTTP (mcp-session-id header-based sessions)
  • MCP server framework: fastapi_mcp v0.4.0 with mount_http(), backed by Python mcp SDK v1.27.0
  • Session state storage: in-process Python memory (default fastapi_mcp behavior, no external persistence)
  • .mcp.json entry type: url (remote HTTP server)

Workaround in use

A local stdio-transport MCP shim (scripts/mcp-shim/shim.py) was built as a workaround. The shim proxies tool calls to the server's direct HTTP endpoints, bypassing the Streamable HTTP session layer entirely. Because the shim subprocess is spawned per Claude Code session and exits with it, there is no session state to go stale. This workaround eliminates the failure but does not fix the underlying client behavior.

Claude Model

Sonnet (default)

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.101

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

VS Code integrated terminal

Additional Information

_No response_

View original on GitHub ↗

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