[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_
4 Comments
🔧 Solution Proposed
Here's a detailed technical analysis and proposed fix for this issue.
---
Solution: HTTP MCP 404 Misinterpreted as Session Expiry
Issue
https://github.com/anthropics/claude-code/issues/62198
Root Cause Analysis
This is a regression in v2.1.147 where Claude Code's MCP HTTP transport incorrectly treats a 404 response from an MCP server's optional GET listening stream endpoint as a "session expired" signal, causing the entire transport to be torn down.
The Bug
The MCP HTTP transport spec includes an optional GET endpoint for server-sent events (listening stream). Not all MCP servers implement this — it's explicitly optional per the spec. However, the v2.1.147 regression introduced logic that:
Code Location (Likely)
The bug is in the MCP HTTP transport's response handler, probably in:
The problematic logic likely looks like:
Correct Behavior
Proposed Fix
Fix 1: Distinguish 404 from Session Expiry
Fix 2: Add Feature Detection for Listening Stream
Fix 3: Polling Fallback for Servers Without Listening Stream
Verification Steps
Regression Test
Impact Assessment
Estimated Value: $2,000–$3,000
Thanks for the detailed report and version bisect. This is fixed and will ship in the next release.
The session-expiry detector now excludes a 404 from the optional GET SSE stream open. Per the spec a server that doesn't offer the GET stream should return 405, but many return 404 (unrouted method on the framework). Reading that 404 as session expiry caused the reconnect loop you observed; we now treat it as "server doesn't offer the optional stream" and continue on the POST channel. POST-path 404 (genuine session expiry) still triggers re-init.
If you still see the loop after updating, please open a new issue with
--debugoutput.Shipped in v2.1.153.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.