[BUG] Stale MCP Session ID Cached Permanently
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:
- Start the minimal server:
uvicorn minimal_server:app --port 8000 - Add it to Claude Code
.mcp.jsonas aurlentry:{"type": "url", "url": "http://localhost:8000/mcp"} - Open a Claude Code session. Confirm the server's MCP tools appear (
/mcp). - Restart the server: stop
uvicorn, then restart it. The in-memory session store is wiped. - 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-idheader-based sessions) - MCP server framework:
fastapi_mcpv0.4.0 withmount_http(), backed by PythonmcpSDK v1.27.0 - Session state storage: in-process Python memory (default
fastapi_mcpbehavior, no external persistence) .mcp.jsonentry 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_
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗