[BUG] v2.1.147 regression: HTTP MCP servers without optional GET listening stream - 404 misinterpreted as session expiry, transport torn down
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?
Starting in v2.1.147, Claude Code attempts to open the optional GET listening stream against an HTTP MCP server's URL. When the server returns 404 (a legitimate response per the MCP Streamable HTTP spec — the GET stream is optional), Claude Code now misinterprets it as MCP session expired, tears down the transport, retries in a reconnect loop, and ultimately fails tools/list with Not connected. The user sees the server silently absent from their session.
The same gateway works on v2.1.146 and earlier.
This breaks every HTTP MCP server that implements only the POST half of Streamable HTTP — which is allowed by spec.
This appears to be a side-effect of the auto-reconnect logic added in response to enhancement #59442 (closed) — the same logic that #62008 reports is also not working on its happy path. We landed on the over-broad trigger end of the same bug.
What Should Happen?
Per MCP Streamable HTTP transport spec §2 (server-to-client SSE) (https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http):
▎ Clients MAY issue an HTTP GET to the MCP endpoint. This can be used to open an SSE stream, allowing the server to communicate to the client, without the client first sending data via HTTP POST. […]
▎
▎ If the server does not offer an SSE stream at this endpoint, the server MUST return HTTP 405 Method Not Allowed.
So:
- Clients MAY open a GET listening stream — it is optional, not required for correct operation.
- The session-expiry mechanism in the spec (§session management clause 4 (https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#session-management)) applies to POST requests carrying an Mcp-Session-Id that the server has invalidated. A 404 to the initial GET stream-open attempt is not a session-expiry signal — the session id has nothing to do with it.
Expected client behavior:
- Mcp-Session-Id-carrying POST receiving 404 → legitimate session expiry → reconnect. (This is the fix #59442 asked for, and what #62008 reports still doesn't work.)
- Initial GET stream-open receiving 404 or 405 → "server doesn't offer the optional listening stream" → log and continue. The POST request channel remains valid and tool calls should proceed normally.
The forgiving behavior in v2.1.146 and earlier was already correct on the GET-stream-open side. The v2.1.147 change appears to have widened the session-expiry trigger to catch any 404 anywhere on the transport, which is too broad.
Error Messages/Logs
Side-by-side --debug log diff. Both versions issue the GET listening-stream open. Both receive 404 from the same gateway. The diff is the reaction.
v2.1.146 (working):
[DEBUG] MCP server "my-http-mcp": Successfully connected (transport: http) in 883ms
[DEBUG] MCP server "my-http-mcp": Connection error: Streamable HTTP error: Failed to open SSE stream: Not Found
[DEBUG] MCP server "my-http-mcp": Connection error: Streamable HTTP error: Failed to open SSE stream: Not Found
[DEBUG] MCP server "my-http-mcp": Channel notifications skipped: server did not declare claude/channel capability
... 15 seconds pass (user types prompt) ...
[DEBUG] MCP server "my-http-mcp": Calling MCP tool: jira_search_issues
[DEBUG] MCP server "my-http-mcp": Tool 'jira_search_issues' completed successfully in 538ms
v2.1.147 (broken) — lines added in v2.1.147 marked with +:
[DEBUG] MCP server "my-http-mcp": Successfully connected (transport: http) in 327ms
[DEBUG] MCP server "my-http-mcp": Channel notifications skipped: server did not declare claude/channel capability
[DEBUG] MCP server "my-http-mcp": Connection error: Streamable HTTP error: Failed to open SSE stream: Not Found
+ [DEBUG] MCP server "my-http-mcp": MCP session expired (server returned 404), triggering reconnection
+ [DEBUG] MCP server "my-http-mcp": Closing transport (session expired)
+ [DEBUG] MCP server "my-http-mcp": HTTP transport closed/disconnected, attempting automatic reconnection
+ [DEBUG] MCP server "my-http-mcp": Connection error: Streamable HTTP error: Failed to open SSE stream: Not Found
+ ... reconnect loop, more 404s ...
+ [ERROR] MCP server "my-http-mcp" Failed to fetch tools: Not connected
+ [DEBUG] MCP server "my-http-mcp": Terminal connection error 1/3
The new MCP session expired (server returned 404), triggering reconnection branch is the regression. The 404 here is the response to the GET listening-stream open, which carries no Mcp-Session-Id and is not a session-expiry signal.
Steps to Reproduce
Minimal ~/.claude.json:
{
"mcpServers": {
"my-http-mcp": {
"type": "http",
"url": "https://<your-host>/mcp",
"headers": {
"Authorization": "Bearer <token>"
}
}
}
}
Server behavior (spec-compliant, POST-only — does not offer the optional GET listening stream):
┌────────────────────────────────────────┬─────────────────────────────────────────────────────────────────┐
│ Request │ Response │
├────────────────────────────────────────┼─────────────────────────────────────────────────────────────────┤
│ POST /mcp (initialize) │ 200, returns protocolVersion: 2025-03-26, issues Mcp-Session-Id │
├────────────────────────────────────────┼─────────────────────────────────────────────────────────────────┤
│ POST /mcp (tools/list with session id) │ 200, returns tools │
├────────────────────────────────────────┼─────────────────────────────────────────────────────────────────┤
│ GET /mcp (open listening stream) │ 404 │
├────────────────────────────────────────┼─────────────────────────────────────────────────────────────────┤
│ OPTIONS /mcp │ 200, Allow: POST │
└────────────────────────────────────────┴─────────────────────────────────────────────────────────────────┘
Direct curl against the gateway works perfectly (initialize + notifications/initialized + tools/list all succeed).
In Claude Code v2.1.147+, the server appears in claude mcp list as ✓ Connected (because mcp list only does the initialize probe), but tools are absent from interactive sessions and --debug shows the reconnect loop above.
- Set up the config above pointing at any POST-only HTTP MCP server.
- Run claude mcp list — observe ✓ Connected (misleading).
- Open an interactive session: claude --debug.
- Ask Claude to call any tool from the server.
- Observe: tools are not surfaced; debug log shows MCP session expired → reconnect loop → Failed to fetch tools: Not connected.
Performed by extracting platform binaries from the published npm tarballs (@anthropic-ai/claude-code-linux-x64@2.1.X) and running each side-by-side against the same MCP gateway with the same ~/.claude.json config:
┌─────────┬────────────────┬───────────────────────┐
│ Version │ Released (UTC) │ Result │
├─────────┼────────────────┼───────────────────────┤
│ 2.1.142 │ 2026-05-14 │ ✅ works │
├─────────┼────────────────┼───────────────────────┤
│ 2.1.145 │ 2026-05-19 │ ✅ works │
├─────────┼────────────────┼───────────────────────┤
│ 2.1.146 │ 2026-05-20 │ ✅ last known good │
├─────────┼────────────────┼───────────────────────┤
│ 2.1.147 │ 2026-05-21 │ ❌ first broken │
├─────────┼────────────────┼───────────────────────┤
│ 2.1.148 │ 2026-05-22 │ ❌ broken (inferred) │
├─────────┼────────────────┼───────────────────────┤
│ 2.1.149 │ 2026-05-22 │ ❌ broken (confirmed) │
├─────────┼────────────────┼───────────────────────┤
│ 2.1.150 │ 2026-05-23 │ ❌ broken (confirmed) │
└─────────┴────────────────┴───────────────────────┘
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.146
Claude Code Version
2.1.150
Platform
Anthropic API
Operating System
Other Linux
Terminal/Shell
WSL (Windows Subsystem for Linux)
Additional Information
_No response_
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗