Claude Code MCP Call Times Out (or Hangs) Repeatedly

MCP tool calls in Claude Code time out or hang while a direct curl works. Usually stdio stdout pollution, the wrong startup vs tool timeout, a never-sent initialize, or a proxy that buffers SSE. Fixes verified June 2026.

You configured an MCP server (filesystem, GitHub, or a custom one) in Claude Code. A direct curl to its endpoint returns fine and the server’s startup logs look healthy, but every tool call from Claude Code either hangs or fails. Most of the time the server is not really stuck. Claude Code is waiting for a reply that never arrives in a form it recognizes, or it gave up on a budget you did not know existed.

Fastest fix first: if you wrote the server yourself and it uses stdio transport, the single most common cause is the server printing a non-JSON line to stdout (a banner, a console.log, a print()). Under stdio, stdout is reserved for protocol messages only. Route every log line to stderr and restart. If the server is HTTP/SSE, the most common cause is a reverse proxy buffering the stream so the response never reaches Claude Code; the second is hitting a timeout you can raise. The rest of this page is the ordered triage.

First: which “timeout” are you actually hitting?

Claude Code has several independent budgets, and the article-old advice of “a 30-second limit” is wrong as of June 2026. Match your symptom to the right knob before changing anything.

SymptomBudget you hitDefault (June 2026)How to change
Server never connects at all; not listed in /mcpStartup / spawn timeoutMCP_TIMEOUT env var (often used as MCP_TIMEOUT=10000)MCP_TIMEOUT=30000 claude
Server connects, but alwaysLoad server stalls launchConnect timeout5 secondsreduce upfront-loaded servers
A single tool call hangs then errorsTool execution timeoutMCP_TOOL_TIMEOUT env var, or per-server timeout in .mcp.json; unset default is very large (about 28 hours)add "timeout": 600000 to the server entry
HTTP/SSE first byte never arrivesPer-request first-byte budget60-second minimum for HTTP/SSEfix the proxy; raising config below this floor has no effect
Underlying MCP SDK client error -32001SDK request timeout60s in most client SDKsserver-side, or send progress notifications

The error you see depends on which budget tripped. A blown SDK-level request shows MCP error -32001: Request timed out. A startup failure shows the server as ✗ failed in the /mcp panel. A polluted stdio stream often surfaces as a generic -32000 parse failure or a silent Connection closed.

The two timeouts people confuse most: MCP_TIMEOUT governs startup (how long Claude Code waits for the spawned process to come up). MCP_TOOL_TIMEOUT (or the per-server timeout field in .mcp.json, in milliseconds) governs each tool call. Setting the wrong one is why “I increased the timeout and nothing changed” is so common.

Common causes

Ordered by hit rate, highest first.

1. A stdio server writes non-protocol bytes to stdout

This is the number-one cause for self-built stdio servers. The MCP stdio transport sends one JSON-RPC message per line, newline-delimited, and messages must not contain embedded newlines. The spec is explicit: the server must not write anything to stdout that is not a valid MCP message. A single stray console.log, print(), a startup banner, or a progress bar on stdout corrupts the stream, and the call hangs or dies with a parse error.

How to judge: run the server by hand and watch its raw output. Every line on stdout must be valid JSON-RPC. Any human-readable text there is the bug. (Note: MCP stdio does not use Content-Length headers; that framing belongs to the Language Server Protocol, not MCP.)

2. The server never sends the initialize response

Claude Code begins every session with an initialize request and waits for the matching result before any tool call can proceed. If your handler never replies, every later call eventually times out.

How to judge: the server’s own stderr log should show an inbound initialize, then an outbound result. If the inbound arrives but no result goes back, your handler is wrong or threw silently.

3. Wrong JSON-RPC id pairing

JSON-RPC pairs requests and responses by id. If the server invents its own id instead of echoing the request’s, Claude Code waits forever for the original id.

How to judge: log every inbound id and every outbound id. They must match exactly (and JSON 1 is not the string "1").

4. A reverse proxy or firewall buffers the HTTP/SSE stream

If the MCP server sits behind nginx, Cloudflare, or a load balancer that buffers responses or strips text/event-stream, the streamed reply never reaches Claude Code even though the server sent it. This is the top cause for remote/HTTP servers.

How to judge: curl --no-buffer the same endpoint and watch for incremental output. If curl hangs the same way Claude Code does, the proxy is the problem, not the client. On nginx, you need proxy_buffering off; and proxy_read_timeout raised for the MCP location.

5. Tool schema is invalid, so Claude Code never registers the tool

If tools/list returns malformed JSON Schema, Claude Code can drop the tool. A later call then has no real target and stalls.

How to judge: run claude --debug (or claude --mcp-debug) and look for schema-validation errors or a tool count of zero next to the server in /mcp.

6. Wrong command, stale path, or the process exits on launch

The command/args in your config may launch the wrong binary or a stale path. Claude Code spawns it, the process exits immediately, and you see Connection closed or spawn ... ENOENT.

How to judge: claude mcp get <name> to confirm the exact command, then ps aux | grep -i mcp while Claude Code runs. The server process should be alive.

7. You raised the timeout but set the wrong variable or scope

If you set MCP_TIMEOUT expecting tool calls to wait longer, nothing changes, because that is the startup budget. Likewise a timeout below 1000 ms in .mcp.json is ignored and falls through to the default.

How to judge: confirm which budget you are hitting using the table above, then set MCP_TOOL_TIMEOUT or the per-server timeout field accordingly.

Before you start

  • Have the MCP server’s own logs accessible.
  • Know whether the transport is stdio or HTTP (SSE is deprecated as of early 2026; use streamable HTTP for new servers).
  • Be able to curl the server independently of Claude Code.
  • Save your config (.mcp.json or ~/.claude.json) before tweaking.

Information to collect

  • Claude Code version: claude --version (timeout and reconnect behavior changed across 2.1.x).
  • The server config block: claude mcp get <name>.
  • The server’s own version and stderr during the hang.
  • For HTTP: a curl --no-buffer -v capture.
  • For stdio: the spawned process’s raw stdout and stderr.
  • The MCP debug log: claude --debug 2>&1 | grep -i mcp.

Step-by-step fix

Step 1: Check the /mcp panel and server status

Inside Claude Code, run /mcp. It shows each server’s connection state and the tool count next to it. A server flagged ✗ failed or showing zero tools tells you whether this is a connection problem (Steps 2-3) or a per-call problem (Steps 5-7). HTTP/SSE servers auto-reconnect with exponential backoff (up to five attempts); stdio servers do not reconnect, so a dead stdio process stays dead until you fix the command.

Step 2: Confirm Claude Code actually launched a stdio server

ps aux | grep -i mcp

You should see the server spawned as a child of Claude Code. If it is absent, run claude mcp get <name> and verify the command/args. A stale path or missing binary produces Connection closed or spawn ... ENOENT.

Step 3: Read the MCP debug log

claude --debug 2>&1 | grep -i mcp

Look for the initialize request and its response, the tools/list call, and any error lines. The first failure point is usually obvious here.

Step 4: For stdio, prove stdout is clean

The server may write only JSON-RPC to stdout; all logs go to stderr. Capture the two streams separately:

node my-mcp-server.js 1>/tmp/mcp.stdout 2>/tmp/mcp.stderr

Then send it a real initialize and inspect /tmp/mcp.stdout. Every line must be valid JSON. If you see a banner, a log line, or a progress bar there, that is your timeout. The fix in Node is to replace every console.log with console.error; in Python, replace print(...) with print(..., file=sys.stderr) or a logger that targets stderr.

Step 5: For HTTP, test the endpoint directly

curl --no-buffer -v -X POST https://mcp.example.com/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'

The Accept header listing both application/json and text/event-stream is required by the streamable HTTP spec. If curl also hangs or returns nothing, the problem is server- or proxy-side. If curl returns the InitializeResult instantly but Claude Code still hangs, the proxy is likely buffering the streamed branch (Step 6).

Step 6: Fix the proxy buffering (HTTP/SSE only)

If the server is behind a proxy, disable response buffering for the MCP path and raise the read timeout. On nginx:

location /mcp {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_buffering off;
    proxy_read_timeout 600s;
}

On Cloudflare, ensure the route is not cached and that streaming is allowed. Remember the per-request first-byte budget has a 60-second minimum for HTTP/SSE, so a config value below that has no effect.

Step 7: Validate JSON-RPC id pairing and tool schema

Log every inbound and outbound id in the server and confirm they match. Then pull the tool list and validate the schema:

curl -X POST https://mcp.example.com/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

Run the returned inputSchema through a JSON Schema validator and fix any errors.

Step 8: Set the right timeout for genuinely slow tools

If a tool legitimately needs longer than the default per-call budget, raise it in the right place. Add a per-server timeout (milliseconds) to that server’s entry in .mcp.json:

{
  "mcpServers": {
    "slow-db": {
      "type": "stdio",
      "command": "node",
      "args": ["server.js"],
      "timeout": 600000
    }
  }
}

That sets a ten-minute wall-clock limit for this server’s tool calls and overrides MCP_TOOL_TIMEOUT for it. A value below 1000 is ignored. The cleaner pattern for very long jobs is to have the tool return a job id immediately and let Claude poll, rather than holding the call open. For startup-slow servers, raise MCP_TIMEOUT instead: MCP_TIMEOUT=30000 claude.

How to confirm it’s fixed

  • /mcp lists the server as connected with a non-zero tool count.
  • A trivial tool call (list, ping, status) returns in well under a second.
  • The server’s stderr shows paired request/response lines with matching ids, and its stdout contains only JSON-RPC.
  • claude --debug shows the tool registered and called successfully.
  • Repeated calls do not regress after several minutes of use.

Long-term prevention

  • Send all server logs to stderr by default; never to stdout under stdio. Add a CI check that greps the server’s stdout for any non-JSON line.
  • Echo the JSON-RPC id verbatim from request to response.
  • Prefer streamable HTTP over the deprecated SSE transport for new remote servers.
  • Pin the MCP server version in your config so a schema change does not surprise you.
  • Add an integration test that runs the server in stdio mode and completes a real initialize + tools/list round trip.
  • Test new servers with the official MCP Inspector before wiring them into Claude Code (see FAQ).

Common pitfalls

  • Using print() / console.log for debug in a stdio server, which lands on stdout and breaks the stream.
  • Raising MCP_TIMEOUT and expecting tool calls to wait longer; that variable only affects startup.
  • Setting a .mcp.json timeout below 1000, which is ignored entirely.
  • Forgetting an HTTP MCP behind nginx or Cloudflare needs buffering disabled.
  • Configuring the same server twice (once local, once project) and connecting to the wrong instance; precedence is local, then project, then user.
  • Writing two responses for one request id; Claude Code consumes only the first.

FAQ

  • What is the default MCP tool timeout in Claude Code? As of June 2026 there is no tight 30-second wall on tool calls. With MCP_TOOL_TIMEOUT unset and no per-server timeout, the effective limit is very large (about 28 hours). The 60-second figure you may have seen is the underlying SDK request timeout (MCP error -32001) and the HTTP/SSE first-byte minimum, not a hard Claude Code tool wall.
  • How do I increase the timeout? For a slow tool, add "timeout": 600000 (ms) to that server’s entry in .mcp.json. For a slow startup, run MCP_TIMEOUT=30000 claude. Do not mix them up; they control different phases.
  • Why does curl work but Claude Code does not? For stdio it is almost always non-JSON bytes on stdout. For HTTP it is almost always a proxy buffering the stream or a missing Accept: text/event-stream path. Capture both wire formats and compare.
  • Is SSE still supported? Yes, but SSE is deprecated as of early 2026 in favor of streamable HTTP. Existing SSE servers still connect via claude mcp add --transport sse; use --transport http for anything new.
  • Can I debug MCP without Claude Code? Yes. Run the official MCP Inspector: npx @modelcontextprotocol/inspector node my-mcp-server.js. The UI opens at http://localhost:6274 and exercises initialize, tools/list, and tool calls over stdio, SSE, or streamable HTTP.
  • My HTTP server keeps showing as offline mid-session. Claude Code auto-reconnects HTTP/SSE servers with exponential backoff (up to five attempts). If the proxy keeps dropping the SSE stream, fix the proxy timeout; reconnect loops are a symptom, not the cause.
  • Will a failed call retry automatically? Connection-level failures get bounded reconnection. A timed-out tool call does not silently rerun; re-prompt once the underlying timeout or transport issue is fixed.

Tags: #Claude Code #mcp #Troubleshooting #Debug