Streamable HTTP client mis-correlates JSON-RPC error body of 405 GET (SSE stream) response to pending tools/list, breaking stateless servers (Linear MCP)

Open 💬 0 comments Opened Jun 10, 2026 by arcticrobotops

Environment

  • Claude Code 2.1.170 (latest; both CLI and claude-desktop harness), macOS (Darwin 25.5.0)
  • Server: https://mcp.linear.app/mcp (streamable HTTP, stateless: no Mcp-Session-Id issued)
  • Auth: valid OAuth tokens (plugin-managed) and also reproducible with a static Authorization: Bearer <linear api key> header
  • 100% reproducible: 96 of 96 logged connection attempts since 2026-05-11 fail identically

Symptom

The connection to Linear's MCP server initializes successfully (server advertises hasTools: true), then tool fetching fails and the server contributes zero tools to the session:

Connection established with capabilities: {"hasTools":true, ... "serverVersion":{"name":"Linear MCP", ...}}
tools/list failed (MCP error -32601: Method not found); retrying once
Failed to fetch tools: MCP error -32601: Method not found

Evidence that the server-side tools/list is healthy

Replaying tools/list manually over plain HTTP with the exact same token, endpoint, and User-Agent: claude-code/2.1.170 (claude-desktop, agent-sdk/0.3.170) succeeds and returns the full 35-tool catalog. This holds across every variation tested: protocol versions 2025-03-26 / 2025-06-18 / 2025-11-25, params: {}, params omitted, _meta.progressToken, id: 0, a bogus Mcp-Session-Id header, and a missing MCP-Protocol-Version header. None of these reproduce the failure.

The actual trigger

The only request that returns -32601 is the GET the client transport opens for the optional server-initiated SSE stream. Linear answers it with HTTP 405 plus a JSON-RPC error body:

GET /mcp  ->  405
{"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not allowed. Use POST for MCP requests."},"id":null}

The client appears to parse that body and fail the pending tools/list request with it. Two details support this: the failure code matches (-32601), and the client log renders the generic JSON-RPC name for the code ("Method not found") rather than the server's actual message text ("Method not allowed. Use POST for MCP requests."), which suggests the error is being reconstructed from the code after being lifted out of the GET response.

Two spec points apply:

  1. Streamable HTTP: a server that does not offer a server-initiated stream responds 405 to the GET, and the client should continue without a stream.
  2. JSON-RPC 2.0: a response with id: null must not be correlated with any outstanding request.

Linear's behavior is also unusual (a correlatable-looking JSON-RPC error body on the 405), and I am reporting that to Linear separately. But the client should be robust to it: a 405 on the GET stream should never affect pending POST requests.

Reproduction

  1. Register Linear as a native HTTP server: claude mcp add --transport http linear https://mcp.linear.app/mcp and authenticate (or attach a Linear API key as a Bearer header).
  2. Start a session. The server connects, then logs tools/list failed (MCP error -32601: Method not found) and contributes no tools.
  3. Compare: curl -X POST https://mcp.linear.app/mcp -H "Authorization: Bearer <token>" -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' returns the full tool list.
  4. Observe the GET: curl -X GET https://mcp.linear.app/mcp -H "Authorization: Bearer <token>" -H "Accept: text/event-stream" returns 405 with the JSON-RPC -32601 body shown above.

Related issues (for triage disambiguation)

  • #62198 is the closest relative: a 2.1.147 regression in the same code path (HTTP servers without the optional GET SSE stream), with a fix in 2.1.153 whose changelog wording covers stateful servers. This report is the stateless, 405-with-error-body case and still reproduces on 2.1.170.
  • This is not the claude-vscode -32601 family (#59604, #59425, #23248): in those, the server genuinely does not implement tools/list. Here it provably does.
  • #61429 (claude.ai managed Linear connector pinned to the removed /sse transport) is a separate problem; its documented workaround is exactly the native HTTP path that this bug breaks.

Workaround

A stdio bridge avoids the GET stream entirely and works on the same machine and token: claude mcp add linear -- npx -y mcp-remote@0.1.38 https://mcp.linear.app/mcp --header "Authorization:Bearer ${LINEAR_API_KEY}"

🤖 Investigated and reported with Claude Code

View original on GitHub ↗