anthropics / anthropics/claude-code

[BUG] Streamable HTTP MCP tool call still times out ("The operation timed out") at ~6min despite per-server timeout, CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT=0, and a requestTimeout=0 server

Đang mở
#93,143 2 bình luận 0 reaction 0 người được giao Xem trên GitHub
area:mcp bug has repro platform:linux
Ngôn ngữ chính
Python
Star
145k
Fork
23.1k
Chỉ số merge pull request
Chỉ số pull request đang chờ

Mô tả

### Preflight Checklist

- [x] I have searched [existing issues](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug) 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?

A Streamable HTTP MCP server's `tools/call` genuinely needs to stay open for several minutes (in our case, waiting on a human to answer a question rendered from the tool call) errors out with:

```
is_error: true
content: "The operation timed out."
```

at a consistently narrow ~352-363 second window (measured 3 separate times: 352.x, 363.1, 362.5 -- an 11-second spread), even though every documented mechanism to raise or disable that timeout was applied at the same time:

1. The server's own entry `timeout` field in `--mcp-config`'s `mcpServers` set to `86400000` (24h).
2. `CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT=0` set as an environment variable on the `claude` process (confirmed via a debug print that the child actually receives `0`, not empty/unset).
3. (Ruled out, not the cause) the connecting server's own `http.Server.requestTimeout` set to `0`, in case a local server-side default was the real culprit instead of the CLI -- it wasn't; the timing didn't change.

The MCP server itself stays healthy and reachable the entire time (verified independently -- our production bridge never errors, closes, or resets the connection; a companion reproduction below confirms it in isolation too), so this is the `claude` client giving up on its own `tools/call`, not a transport failure.

### What Should Happen?

With the per-server `timeout` set to 24h and/or `CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT=0` set, the `tools/call` should not abort at ~6 minutes -- it should either honor the 24h ceiling, or (per the `=0` docs, "disables the check entirely") not idle-timeout at all, for as long as the server stays connected and doesn't itself return an error.

### Error Messages/Logs

```shell
Captured from the real (non-minimal) case, verbatim tool_result content:

is_error= true "The operation timed out."

And from the minimal reproduction's stream-json output, the equivalent tool_result block:

{"type":"tool_result","is_error":true,"content":[{"type":"text","text":"The operation timed out."}]}
```

### Steps to Reproduce

Minimal, self-contained reproduction (no dependency on any specific project -- a bare Streamable HTTP MCP server whose one tool deliberately never responds):

```javascript
import { createServer } from "node:http";
import { spawn } from "node:child_process";

const TOOL = "wait_forever";
const server = createServer((req, res) => {
if (req.method !== "POST") return res.writeHead(405).end();
let body = "";
req.on("data", (c) => (body += c));
req.on("end", () => {
const msg = JSON.parse(body);
if (msg.method === "initialize") {
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify({
jsonrpc: "2.0", id: msg.id,
result: { protocolVersion: "2025-06-18", capabilities: { tools: {} }, serverInfo: { name: "repro", version: "1.0.0" } },
}));
} else if (msg.method === "tools/list") {
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify({
jsonrpc: "2.0", id: msg.id,
result: { tools: [{ name: TOOL, description: "Never returns", inputSchema: { type: "object", properties: {} } }] },
}));
} else if (msg.method === "tools/call") {
// Deliberately never respond -- simulates a tool genuinely waiting (e.g. on a human).
} else if (msg.id === undefined) {
res.writeHead(202).end();
} else {
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify({ jsonrpc: "2.0", id: msg.id, result: {} }));
}
});
});
server.requestTimeout = 0; // rule out the *reproduction's own* server as the cause
server.listen(0, "127.0.0.1", () => {
const port = server.address().port;
const mcpConfig = JSON.stringify({
mcpServers: { repro: { type: "http", url: `http://127.0.0.1:${port}/mcp`, timeout: 86400000 } },
});
const env = { ...process.env, CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT: "0" };
const start = Date.now();
const child = spawn("claude", [
"-p", `Call the ${TOOL} tool right now, then report exactly what tool_result you get back, verbatim.`,
"--output-format", "stream-json", "--verbose",
"--mcp-config", mcpConfig,
"--allowedTools", `mcp__repro__${TOOL}`,
"--strict-mcp-config",
], { env });
let buf = "";
child.stdout.on("data", (d) => (buf += d));
child.on("close", () => {
console.log("elapsed seconds:", (Date.now() - start) / 1000);
console.log(buf);
server.close();
});
});
```

1. Save the script above as `repro.mjs`.
2. Run `node repro.mjs`.
3. Wait about 6 minutes.
4. Observe stdout: `elapsed seconds: ~352-365`, and the captured `stream-json` output contains a `tool_result` block with `is_error: true` and content `"The operation timed out."`.

Real-world context this was found in: an open-source Claude Code relay (`ultron`, https://github.com/wilmacedo/ultron) that exposes an MCP tool (`present_choice`) to let the model ask the human a closed multiple-choice question and block on the answer. Any question a human takes longer than ~6 minutes to notice/answer degrades the picker into a timeout error -- the model recovers gracefully by falling back to plain text, but the intended UI never gets a chance to work for a slow response.

### Claude Model

None

### Is this a regression?

I don't know

### Last Working Version

_No response_

### Claude Code Version

2.1.266 (Claude Code)

### Platform

Anthropic API

### Operating System

Ubuntu/Debian Linux

### Terminal/Shell

Non-interactive/CI environment

### Additional Information

- Three separate live measurements, each a fresh process: 352.x s (real production case, "The operation timed out" from a genuine multi-minute human wait), 363.1 s (isolated repro with the per-server `timeout` field set to 24h), 362.5 s (isolated repro with `CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT=0` added on top), 357.8 s (the minimal standalone reproduction included above, run independently). All four within a 11-second band -- reads like one fixed, undocumented internal timeout, not jitter.
- Related but NOT a duplicate: #50289 ("`.mcp.json` per-server `timeout` field no longer honored for HTTP MCP tool calls since 2.1.113"), closed/completed. Same general area (per-server HTTP timeout config being silently ignored), but that report's observed ceiling was ~60s, ours is ~360s, and it doesn't cover `CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT` (which post-dates that report) failing to disable the check.
- `#16837` ("Claude code does not obey values of MCP_TIMEOUT longer than 60 seconds") is a long-open issue in the same recurring theme (documented MCP timeout config being ignored) but a different specific variable than either of the two we tested.
- Transport is `"type": "http"` (Streamable HTTP, single POST per JSON-RPC call), not SSE -- worth noting since some prior reports in this area are SSE-specific.
- The CLI itself emits `tool_progress` heartbeat events for the pending call at `elapsed_time_seconds: 300` and `330` (visible in `--output-format stream-json`) shortly before the timeout fires -- so the call is not being treated as silent/idle by the CLI's own instrumentation, yet it still aborts around 350-365s. That reads more like a fixed internal wall-clock ceiling than a true idle-detection timeout, which would be consistent with `CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT=0` having no effect

Hướng dẫn đóng góp

Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này

Hướng nghiên cứu

Start with the self-contained repro.mjs and run node repro.mjs, then inspect the Streamable HTTP tools/call timeout path and the handling of timeout: 86400000 and CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT=0. Compare the tool_progress events with the final tool_result; done means the call remains open beyond the observed ~6-minute ceiling while the server stays connected.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
javascript, node.js
Lĩnh vực
api, cli
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
52/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.