[BUG] .mcp.json per-server `timeout` field no longer honored for HTTP MCP tool calls since 2.1.113 (native binary transition)

Status Fixed / completed
Reported on v2.1.113
Maintainer reply None cached
Activity 6 comments · opened Apr 18, 2026 · closed May 13, 2026

Summary

Since 2.1.113 (the release that moved the CLI to a native Claude Code binary), the project-level .mcp.json per-server timeout field appears to no longer be honored for HTTP MCP transport. Long-running tool calls that used to complete successfully in 2.1.107 now time out at ~60s, regardless of the timeout value configured.

This makes HTTP MCP servers that expose legitimately long-running tools (agentic workflows, batch briefings, long SSE streams) unusable from Claude Code.

Regression Evidence

Same .mcp.json (unmodified since 2026-04-13), same HTTP MCP server, same tool (dpc_execute briefing mode):

| Claude Code version | Result | Evidence |
|---|---|---|
| 2.1.107 (2026-04-14 session) | ✅ 36 long tool calls in one session, max elapsed 188s | Session JSONL log shows elapsed_seconds: 188, 181, 181, 180, 180, 172, 172, 167, 167, 167, 166, 166, 147, ... for mcp__<server>__dpc_execute with mode="briefing" |
| 2.1.114 (2026-04-18, today) | ❌ Consistent "The operation timed out" at ~60s | Multiple retries — all identical tool calls that previously succeeded now fail |

Environment:

  • Install: ~/.local/bin/claude~/.local/share/claude/versions/2.1.114 (Mach-O 64-bit arm64, macOS 25.3.0)
  • Transport: HTTP MCP server (internal)
  • .mcp.json (project-level):

``json
"my-server": {
"type": "http",
"url": "http://...",
"headers": { "x-api-key": "..." },
"timeout": 3600000
}
``

  • Tool call: single invocation, not concurrent

Hypothesis

The 2.1.113 CHANGELOG mentions two MCP-relevant changes:

- Changed the CLI to spawn a native Claude Code binary (via a per-platform optional dependency) instead of bundled JavaScript - Fixed MCP concurrent-call timeout handling where a message for one tool call could silently disarm another call's watchdog

The native binary transition likely dropped the code path that reads mcpServers[<name>].timeout from .mcp.json for HTTP transport, falling back to a hardcoded ~60s default. The concurrent-call timeout fix may also have unified timeout handling in a way that overrides the per-server override.

Reproduction

  1. On Claude Code 2.1.113+, configure an HTTP MCP server in project-level .mcp.json with "timeout": 3600000 (1 hour).
  2. Invoke an MCP tool whose backend takes >60s to stream the first complete response payload (e.g., an agentic workflow that collects SSE events before returning).
  3. Expected: Tool call completes within the configured timeout budget (or server-side timeout, whichever is shorter).
  4. Actual: "The operation timed out" at ~60s wall-clock, regardless of the timeout field.

Control (same machine, downgrade to 2.1.107 via ln -sf .../versions/2.1.107 ~/.local/bin/claude, restart session): same tool call completes in 100-188s successfully.

Impact

  • HTTP MCP servers that front any long-running agent system (Claude Agent SDK, LangGraph, Celery-backed workers) are blocked from Claude Code tool invocation since 2.1.113.
  • The only workarounds discovered in my project are (a) downgrade to ≤2.1.112, or (b) route all work through a separate HTTP script that bypasses Claude Code's MCP client entirely. Neither is acceptable for interactive sessions.
  • Related prior issues: #22542 (configurable MCP tool timeout — previously marked "not planned") assumed 60s was always hardcoded. This report shows it was not always hardcoded — .mcp.json's timeout field worked in 2.1.107 and earlier. That field regressed in 2.1.113.

Ask

  1. Confirm whether .mcp.json.mcpServers[].timeout is intended to be honored by the native binary for HTTP transport.
  2. If yes → restore the code path that was dropped in 2.1.113.
  3. If no → document the removal in the CHANGELOG and provide an alternative (environment variable, CLI flag, or per-call parameter). The silent behavior change is the main problem — users with working configs don't discover the regression until a tool times out in production.

Environment

  • OS: macOS 15 (Darwin 25.3.0, arm64)
  • Claude Code: 2.1.114
  • Shell: zsh
  • MCP transport: HTTP (streaming SSE)

View original on GitHub ↗

6 Comments

m13v · 4 months ago

hit the same 60s ceiling after bumping from 2.1.107 shipping two MCP servers for macos automation. the concurrent-call watchdog fix in 2.1.113 didn't just unify timeout handling, it stopped plumbing per-server config into the HTTP transport layer. stdio still honors the field in my tests, which is why nobody caught it. workaround that unblocked me was splitting the long tool into a kickoff plus poll pair. kickoff returns a job id in under a second, poll streams partials. uglier API but survives the 60s hardcode until they restore the config read.

0xbrainkid · 4 months ago

The timeout field in .mcp.json no longer being honored for HTTP MCP transport after the 2.1.113 native binary transition is a precise regression with clear evidence: 36 tool calls succeeding at up to 188 seconds in 2.1.107, all timing out at ~60 seconds in 2.1.114 with identical configuration.

This is a critical regression for any MCP server that exposes legitimately long-running tools (agentic workflows, batch processing, long-running SSE streams). A hard 60-second limit makes these tools completely unusable.

The root cause: The 2.1.113 native binary transition changed how the HTTP MCP client is implemented. The Node.js-based implementation in 2.1.107 respected the timeout field from .mcp.json when configuring the HTTP client. The native implementation either:

  1. Has a hardcoded 60-second timeout that ignores the config field
  2. Reads the timeout field but applies it as seconds instead of milliseconds (your value of 3600000 ms = 1 hour would become effectively 0 seconds if misinterpreted)
  3. Applies the timeout at a different layer that does not read .mcp.json per-server config

The 3600000 → 60s hint: 60 seconds is exactly 3600000 / 60000 — if your timeout value (in milliseconds) is being divided by 1000 to convert to seconds, and then the seconds value is being treated as milliseconds, you'd end up with 3600ms = 3.6s, which is not quite 60. But if the timeout field is being read as minutes (3600000 minutes = enormous) and falling back to a default of 60s, that would match.

The regression is documented across the 2.1.107 → 2.1.113 native binary transition — any HTTP MCP timeout configuration that was working before 2.1.113 should be re-tested and reported if broken.

johnathanneals-dev · 4 months ago

Adding a Windows + VSCode-native data point to this regression, plus a UX-framing observation that may be worth surfacing alongside the underlying timeout fix.

Windows reproducer (HTTP MCP transport, 2.1.120)

  • OS: Windows 11 Home 10.0.26200
  • Harness: Claude Code, VSCode native extension build, version 2.1.120
  • MCP transport: HTTP (self-hosted MCP server)
  • .mcp.json per-server timeout: configured, not honored

Same regression class as the OP report. The 60s ceiling reproduces on the Windows + VSCode native build, so the dropped code path is platform-agnostic — the native binary transition affects all HTTP MCP transports regardless of host OS.

Symptom path: HTTP timeout meets upstream load

The OP framing is "tool legitimately takes >60s and the config-set timeout is ignored." A second symptom path produces the same outcome:

  1. The MCP server's response handler invokes a synchronous local LLM call (in this setup, advisory contradiction-detection on every capture).
  2. Under normal GPU-resident load, the call returns in 1-2 seconds.
  3. Under upstream resource pressure (parallel GPU-heavy workload causes the LLM to be evicted from GPU to CPU), the same call takes 5-30 seconds.
  4. Response time crosses the harness's hardcoded ~60s ceiling.
  5. The harness fires a "MCP tools have disconnected" system-reminder to the model, and the model falls back to a documented REST-API procedure.

The MCP server itself is fine throughout: pod restart count 0/0 across multi-day uptime, no EPIPE/ECONNRESET/SSE-end events in server logs, sibling Claude Code sessions on the same machine connected to the same MCP server stay connected. Only the affected session is told the transport dropped.

UX-framing observation (separate from the timeout fix)

Even with the timeout fix landing, the current "MCP tools have disconnected" wording misleads the model in a slow-but-up case. Two distinct conditions produce the same system-reminder:

  • (A) Server is genuinely unreachable (network-class, server crashed, etc.)
  • (B) Server is up and reachable; a single response exceeded the harness timeout under upstream load

The model's optimal remediation differs between the two. For (A), fall through to a fallback transport is correct. For (B), retry with backoff (or wait longer) is correct, and "disconnected" framing actively misleads.

Suggestion (orthogonal to the timeout-config fix on this issue): differentiate the system-reminder by underlying cause. If the TCP connection is still open and prior responses succeeded recently, frame as "MCP response delayed; retrying" rather than "MCP disconnected; switching to fallback." If retry succeeds, no "disconnect" language is surfaced at all.

This is a small UX win that complements the timeout-config restoration — even with the configured timeout honored, transient slowdowns within the budget shouldn't read as disconnects.

Companion issues from the same investigation

Surfacing for triage — related observations from the same multi-day investigation:

  • #53734 — auto-memory resolver walks up to ancestor-encoded project directory instead of cwd-encoded path (separate root cause; same Windows environment).
  • #53761 — Write/Edit tools return misleading EBADF on Windows Controlled Folder Access protected paths (separate root cause; same Windows environment).

All three observations from the same machine over a several-day span; filed as separate issues per "one bug per issue" triage best practice.

cleask · 4 months ago

Adding evidence that this regression isn't HTTP-only — STDIO transport is also affected in v2.1.119 (the cowork SDK version shipped inside Claude Desktop's cowork mode). The timeout field in .mcp.json for STDIO entries is similarly ignored, with the same hard 60s ceiling.

I built a small STDIO MCP server that fires both spec-valid heartbeat signals every 10s during long calls — notifications/progress (with a synthetic progressToken, since the cowork client never sends _meta.progressToken) and notifications/message (after declaring capabilities.logging: {} server-side). Both reach the wire successfully, but neither resets the client's request timer:

[12:47:16] tools/call name=custom_prompt _meta=undefined progressToken=undefined
[12:47:26] Heartbeat #1 at 10s
[12:48:16] Heartbeat #6 at 60s ← cowork UI returns "timed out after 60s" here
[12:48:36] Heartbeat #8 at 80s ← server still actively generating; result discarded

Worth flagging: no notifications/cancelled is sent when the client gives up. The MCP server keeps generating, completes its response, and the result is silently dropped. From the server's perspective the work succeeded; from the user’s perspective they got an error and lost the output.

Related closed issues that would also be addressed by fixing this regression more broadly: #22542, #17662, #52441.

Honoring the timeout field for all transports (HTTP, STDIO, SSE) would close every duplicate this has accumulated. Right now the practical impact is that local-LLM offload via STDIO MCP is unusable for any prompt requiring more than 60s of inference, even though the server is fully functional and willing to keep working.

danielford831 · 4 months ago

Nice to be vindicated that im not going insane with this -- is there any known workaround for this at the moment?

github-actions[bot] · 1 month ago

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.