claude-code - 💡(How to fix) Fix MCP HTTP-Streamable Server: Init-Handshake fails on reconnect [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#55970Fetched 2026-05-05 06:01:34
View on GitHub
Comments
1
Participants
2
Timeline
4
Reactions
0
Author
Timeline (top)
labeled ×3commented ×1

When a local MCP server (Streamable HTTP transport, @modelcontextprotocol/sdk) is restarted while a Code-CLI session is active, the Code-CLI client keeps the old mcp-session-id in internal state and continues sending tool calls with this ID. After restart, the server no longer recognizes the ID and returns:

Streamable HTTP error: Error POSTing to endpoint: event: message
data: {"jsonrpc":"2.0","error":{"code":-32000,"message":"Bad Request: Server not initialized"},"id":null}

Expected: On "Server not initialized" response, the client should automatically send a fresh initialize request, rebuild the session, and retry the original tool call.

Actual: Client returns the error to the caller (LLM tool-use). Only a full Code-CLI restart creates a new session — the /mcp slash-command reload does not solve it.

Error Message

Streamable HTTP error: Error POSTing to endpoint: event: message data: {"jsonrpc":"2.0","error":{"code":-32000,"message":"Bad Request: Server not initialized"},"id":null}

Root Cause

Server restarts happen during code updates and config patches. Today every restart forces a full Code-CLI restart (losing prompt cache, requiring re-orientation). The MCP standard (JSON-RPC 2.0 + initialize handshake) allows clean re-initialization in principle.

Fix Action

Workaround

Full Code-CLI restart (/exit + new launch). Chat context survives in transcript JSONL. Practical sequence in shared multi-client setup: systemctl restart <mcp-server> → immediately claude mcp list (race) before other clients reconnect.

Code Example

Streamable HTTP error: Error POSTing to endpoint: event: message
data: {"jsonrpc":"2.0","error":{"code":-32000,"message":"Bad Request: Server not initialized"},"id":null}

---

HTTP/2 400
content-type: application/json
{"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request: Server already initialized"},"id":null}

---

sessionIdGenerator: () => sessionId || randomUUID()
RAW_BUFFERClick to expand / collapse

MCP HTTP-Streamable Server: Init-Handshake fails on reconnect

Description

When a local MCP server (Streamable HTTP transport, @modelcontextprotocol/sdk) is restarted while a Code-CLI session is active, the Code-CLI client keeps the old mcp-session-id in internal state and continues sending tool calls with this ID. After restart, the server no longer recognizes the ID and returns:

Streamable HTTP error: Error POSTing to endpoint: event: message
data: {"jsonrpc":"2.0","error":{"code":-32000,"message":"Bad Request: Server not initialized"},"id":null}

Expected: On "Server not initialized" response, the client should automatically send a fresh initialize request, rebuild the session, and retry the original tool call.

Actual: Client returns the error to the caller (LLM tool-use). Only a full Code-CLI restart creates a new session — the /mcp slash-command reload does not solve it.

Reproduction

  1. Start Code-CLI session with a configured Streamable-HTTP MCP server
  2. Call any MCP tool (server registers session)
  3. Restart the server process (e.g. systemctl restart wiki-mcp.service)
  4. Call MCP tool again
  5. Expected: success after automatic re-initialize
  6. Actual: "Bad Request: Server not initialized"

Setup

  • Code-CLI version: latest (Opus 4.7 1M-context build, 2026-05)
  • MCP-Server: @modelcontextprotocol/sdk StreamableHTTPServerTransport, deployed as own systemd daemon behind nginx
  • Configured via Anthropic Custom-Connector (claude.ai web UI) — verified claude mcp list lists it as claude.ai <name>: ✓ Connected

Relation to existing issues

  • #43177 "MCP stdio servers never auto-reconnect after disconnect" — same family but stdio transport. This report is HTTP-Streamable (StreamableHTTPServerTransport).
  • #42509 "mcp-proxy.anthropic.com returns 502 after initial handshake" — overlaps Class A symptom but with proxy-side 502. This report's Class A is the JSON-RPC "Server not initialized" from the SDK, plus Class B "Server already initialized" triggered by Anthropic Custom-Connector backend, neither of which #42509 covers.

Why this matters

Server restarts happen during code updates and config patches. Today every restart forces a full Code-CLI restart (losing prompt cache, requiring re-orientation). The MCP standard (JSON-RPC 2.0 + initialize handshake) allows clean re-initialization in principle.

Workaround

Full Code-CLI restart (/exit + new launch). Chat context survives in transcript JSONL. Practical sequence in shared multi-client setup: systemctl restart <mcp-server> → immediately claude mcp list (race) before other clients reconnect.

Possible fixes (for discussion)

  1. Client-Side: On "Server not initialized" response, discard the session-id, send fresh initialize, retry the tool call
  2. SDK Server-Side relaxation: StreamableHTTPServerTransport accepts arbitrary client session-id and initializes lazily on first request (own server-side mitigation tried — non-trivial because of internal state machine)
  3. Protocol clarification: HTTP 410 Gone on unknown session-id instead of JSON-RPC error — gives client a clear signal to re-initialize

Live evidence — two symptom classes on the same setup

Setup with two MCP servers (wiki-mcp and knowledge-mcp) on the same Streamable-HTTP-SDK base, configured via Anthropic Custom-Connector. Two related symptom classes observed repeatedly:

Class A — "Server not initialized" after server restart (Code-CLI client)

Code-CLI with HTTP-MCP server connection. Server restart → tool calls fail with "Server not initialized". Reproducible (2026-05-03, 0830–0852 CEST). Code-CLI restart workaround confirmed.

Class B — "Server already initialized" on connector add / reconnect (Anthropic Custom-Connector backend)

Other path: claude.ai Custom-Connector is added, OAuth flow succeeds, but the backend's first MCP initialize request returns:

HTTP/2 400
content-type: application/json
{"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request: Server already initialized"},"id":null}

Reproducibly triggered when the MCP server (StreamableHTTPServerTransport) still holds a session from the previous client. The Anthropic Custom-Connector backend retries multiple times — all attempts fail with the same error. Connector is never registered.

Live evidence (timestamps CEST):

  • 2026-05-03 14:50 — first knowledge-mcp connector-add attempts fail with verify(knowledge) failed: aud (nginx njs layer). After custom-audience workaround the auth passes, immediately followed by "Server already initialized". Workaround: systemctl restart knowledge-mcp → successful connect.
  • 2026-05-03 20:25 — connector ran stably since 14:53. After ~5h35min server idle (no MCP call), reuse attempt fails with auth error ofid_f46ca96c23d9fd4d. Workaround systemctl restart knowledge-mcp → green.
  • 2026-05-04 04:00–09:11 — secondary client (local OpenClaw gateway with @modelcontextprotocol/[email protected]) tries auto-reconnect every 10–15 min via health-monitor. 56 consecutive "Server already initialized" errors in /tmp/openclaw/openclaw-2026-05-04.log, all on knowledge-mcp. Server-side: knowledge-mcp.service was up the whole time (Active: active (running) since 2026-05-03 20:28:31 CEST; 14h ago).
  • 2026-05-04 11:22 — direct race-test: sudo systemctl restart knowledge-mcp on the server, then within 1s claude mcp list from Code-CLI → connector flips to ✓ Connected. Direct curl probe a second later with the same Bearer token returns "Server already initialized" — proving that the server post-restart only allows one client to complete initialize, all subsequent clients are locked out until next restart.

Hypothesis

Server-state (StreamableHTTPServerTransport's internal session-map) accumulates or invalidates without client-side resync:

  • Class A: Server forgot the session, client still holds it → "not initialized"
  • Class B: Server holds a session, client (or Anthropic Custom-Connector backend) has none → "already initialized"

Both classes are resolved by server restart → server restart cleans the session-map and allows a fresh init. But the restart only "buys" one fresh client connection — the next client to attempt initialize immediately gets locked out again.

Appendix — Server-side mitigation (limited effect)

Tried in our own server, in StreamableHTTPServerTransport, to reuse the client session-id instead of generating a new one:

sessionIdGenerator: () => sessionId || randomUUID()

Helps with fresh connections after restart, but does not solve the reconnect problem — the transport state machine expects an initialize handshake, and a tool call with an unknown session-id arrives before it.

extent analysis

TL;DR

The most likely fix is to modify the client-side behavior to discard the session-id and send a fresh initialize request when receiving a "Server not initialized" response.

Guidance

  1. Verify the issue: Confirm that the problem occurs when the MCP server is restarted while a Code-CLI session is active, and the client continues to send tool calls with the old session-id.
  2. Client-side fix: Implement a check for the "Server not initialized" response, and when received, discard the current session-id, send a new initialize request, and retry the original tool call.
  3. Server-side consideration: Although a server-side relaxation of the StreamableHTTPServerTransport to accept arbitrary client session-ids and initialize lazily on first request might help, it may introduce non-trivial changes to the internal state machine.
  4. Protocol clarification: Consider using an HTTP 410 Gone response instead of a JSON-RPC error to signal the client to re-initialize, providing a clear indication of the need to restart the session.

Example

// Pseudo-code example of client-side fix
if (response.error && response.error.message === 'Server not initialized') {
  // Discard current session-id
  sessionId = null;
  // Send fresh initialize request
  initialize();
  // Retry original tool call
  retryToolCall();
}

Notes

The provided server-side mitigation attempt to reuse the client session-id has limited effect and does not fully resolve the reconnect issue. A more comprehensive solution may require a combination of client-side and server-side changes.

Recommendation

Apply the client-side workaround to discard the session-id and send a fresh initialize request when receiving a "Server not initialized" response, as it is a more straightforward and less invasive change compared to modifying the server-side behavior.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING