Claude Code MCP 调用反复超时(或卡住)

Claude Code 里的 MCP 工具调用超时或卡死,可是直接 curl 又能通。多半是 stdio 往 stdout 打了脏数据、搞错了启动超时和工具超时、initialize 没回、或者代理把 SSE 缓冲住了。修复方法 2026 年 6 月已核实。

你在 Claude Code 里配了一个 MCP server(filesystem、GitHub、或者自己写的)。直接 curl 它的 endpoint 一切正常、启动日志也健康,但 Claude Code 发出的每次工具调用不是卡住就是报错。多数情况下 server 并没有真挂——是 Claude Code 在等一个永远不会以它能识别的形式到来的回复,或者它在一个你根本不知道存在的预算上放弃了。

最快的修复: 如果 server 是你自己写的、用的是 stdio transport,最常见的原因就是 server 往 stdout 打了一行非 JSON 的东西(banner、console.logprint())。stdio 下 stdout 只能放协议消息。把所有 log 改到 stderr、重启就行。如果是 HTTP/SSE,最常见的是反代把流缓冲住了、响应根本传不到 Claude Code;其次是撞上了某个可以调高的超时。本文接下来是按命中率排好的排查顺序。

先搞清楚:你撞的到底是哪个「超时」?

Claude Code 有好几个互相独立的预算,老文章那句「30 秒上限」截至 2026 年 6 月已经不对了。动手改之前,先把你的现象对到正确的旋钮上。

现象撞到的预算默认值(2026 年 6 月)怎么改
Server 根本连不上、/mcp 里不显示启动 / spawn 超时MCP_TIMEOUT 环境变量(常见写法 MCP_TIMEOUT=10000MCP_TIMEOUT=30000 claude
Server 连上了、但 alwaysLoad 的 server 卡住启动连接超时5 秒减少需要前置加载的 server
单次工具调用卡住后报错工具执行超时MCP_TOOL_TIMEOUT 环境变量、或 .mcp.json 里 per-server 的 timeout;不设时默认极大(约 28 小时)在 server 条目里加 "timeout": 600000
HTTP/SSE 首字节一直不来单次请求首字节预算HTTP/SSE 最低 60 秒修代理;把配置调到这个下限以下没用
底层 MCP SDK 报 -32001SDK 请求超时多数 client SDK 是 60 秒server 端处理,或发 progress notification

你看到什么报错取决于撞了哪个预算。SDK 层请求超时显示 MCP error -32001: Request timed out;启动失败会在 /mcp 面板里显示成 ✗ failed;stdio 流被污染常常表现为通用的 -32000 解析失败、或者静默的 Connection closed

最容易被搞混的两个超时:MCP_TIMEOUT 管的是启动(Claude Code 等 spawn 出来的进程起来等多久);MCP_TOOL_TIMEOUT(或 .mcp.json 里 per-server 的 timeout 字段,单位毫秒)管的是每次工具调用。设错了那个,就是「我调大了 timeout 怎么没用」的根源。

常见原因

按命中率从高到低。

1. stdio server 往 stdout 写了非协议字节

这是自己写的 stdio server 的头号原因。MCP 的 stdio transport 是一行一条 JSON-RPC 消息、按换行分隔,而且消息里不能含换行。规范说得很死:server 不能stdout 写任何不是合法 MCP 消息的东西。一句多余的 console.logprint()、启动 banner、或者 stdout 上的进度条,都会把流搞坏,调用就卡住或者直接挂掉报解析错误。

怎么判断: 手动把 server 跑起来、看它原始输出。stdout 上每一行都必须是合法 JSON-RPC。出现任何给人看的文字就是 bug。(注意:MCP stdio 不用 Content-Length 头;那种帧是 Language Server Protocol 的,不是 MCP。)

2. Server 从来不回 initialize 响应

Claude Code 每次会话开头都会发 initialize 请求、然后等对应的 result,之后才能跑任何工具调用。你的 handler 一直不回,后面每次调用最终都会超时。

怎么判断: server 自己的 stderr 日志应该能看到入向 initialize、然后是出向的 result。入向到了但 result 没回去,就是 handler 写错了、或者静默抛了异常。

3. JSON-RPC id 配对不对

JSON-RPC 是按 id 配对请求和响应的。Server 自己造一个 id、而不是 echo 请求里那个,Claude Code 就会一直等原来那个 id

怎么判断: 把每个入向 id 和出向 id 都 log 出来、确认完全对得上(而且 JSON 的 1 不等于字符串 "1")。

4. 反代或防火墙把 HTTP/SSE 流缓冲住了

MCP server 在 nginx、Cloudflare 或负载均衡后面、对方缓冲响应或者把 text/event-stream 剥掉了,那么即便 server 发了,流式回复也到不了 Claude Code。这是远程/HTTP server 的头号原因。

怎么判断:curl --no-buffer 打同一个 endpoint、看有没有分块输出。curl 也和 Claude Code 一样卡,那就是代理的问题,不是客户端。在 nginx 上,MCP 那个 location 需要 proxy_buffering off; 并把 proxy_read_timeout 调高。

5. Tool schema 非法、Claude Code 根本没注册这个工具

tools/list 返回的 JSON Schema 写坏了,Claude Code 会把这个 tool 丢掉。后续调用就没有真正的目标、于是卡住。

怎么判断:claude --debug(或 claude --mcp-debug),找 schema 校验报错,或者 /mcp 里这个 server 旁边的 tool 数量是 0。

6. command 不对、路径过期、或进程一启动就退

配置里的 command/args 启动了错的二进制、或者用了过期路径。Claude Code 起了它、进程立刻退,你就看到 Connection closedspawn ... ENOENT

怎么判断: claude mcp get <name> 确认确切的 command,然后 Claude Code 跑着的时候 ps aux | grep -i mcp,server 进程应该活着。

7. 你调了超时,但变量或作用域搞错了

你设了 MCP_TIMEOUT 想让工具调用等久点,结果没变化——因为那是启动预算。同理,.mcp.jsontimeout 低于 1000 毫秒会被忽略、落回默认值。

怎么判断: 先用上面的表确认你撞的是哪个预算,再相应地设 MCP_TOOL_TIMEOUT 或 per-server 的 timeout 字段。

开始前

  • 准备好 MCP server 自己的日志。
  • 知道 transport 是 stdio 还是 HTTP(SSE 截至 2026 年初已废弃;新 server 用 streamable HTTP)。
  • 能在 Claude Code 之外独立 curl 这个 server。
  • 改配置(.mcp.json~/.claude.json)前先备份。

需要收集的信息

  • Claude Code 版本:claude --version(2.1.x 期间超时和重连行为变过)。
  • Server 配置块:claude mcp get <name>
  • Server 自己的版本号 + 挂的时候的 stderr
  • HTTP 的话:curl --no-buffer -v 的抓取。
  • Stdio 的话:spawn 出来那个进程的原始 stdoutstderr
  • MCP debug 日志:claude --debug 2>&1 | grep -i mcp

一步一步修复

Step 1:看 /mcp 面板和 server 状态

在 Claude Code 里跑 /mcp。它会显示每个 server 的连接状态、旁边带 tool 数量。某个 server 标了 ✗ failed 或者 tool 数量是 0,就说明这是连接问题(Step 2-3)还是 per-call 问题(Step 5-7)。HTTP/SSE server 会按指数退避自动重连(最多五次);stdio server 不会重连,所以挂掉的 stdio 进程会一直是死的,直到你修好 command。

Step 2:确认 Claude Code 真的把 stdio server 起来了

ps aux | grep -i mcp

应该能看到 server 作为 Claude Code 的子进程被 spawn 出来。没有的话,跑 claude mcp get <name> 核对 command/args。路径过期或二进制缺失会产生 Connection closedspawn ... ENOENT

Step 3:看 MCP debug 日志

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

initialize 请求和它的响应、tools/list 调用、以及任何 error 行。第一个失败点通常一眼就能看出来。

Step 4:stdio 情况下,证明 stdout 是干净的

server 只能stdout 写 JSON-RPC,所有 log 都走 stderr。把两条流分开抓:

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

然后给它发一个真的 initialize、检查 /tmp/mcp.stdout。每一行都得是合法 JSON。看到 banner、log 行或进度条,那就是你超时的根源。Node 里的修法是把每个 console.log 换成 console.error;Python 里把 print(...) 换成 print(..., file=sys.stderr)、或者用一个输出到 stderr 的 logger。

Step 5:HTTP 情况下,直接测 endpoint

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"}}}'

Accept 头同时列出 application/jsontext/event-stream 是 streamable HTTP 规范要求的。curl 也卡或者啥都不返回,那问题在 server 或代理端。curl 秒回 InitializeResult 但 Claude Code 还卡,那多半是代理把流式那一支缓冲住了(Step 6)。

Step 6:修代理缓冲(仅 HTTP/SSE)

如果 server 在代理后面,把 MCP 路径的响应缓冲关掉、读超时调高。nginx 上:

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

Cloudflare 上,确保这条路由不被缓存、并且允许流式。记住 HTTP/SSE 的单次请求首字节预算最低 60 秒,所以配置值低于这个数没有任何作用。

Step 7:校验 JSON-RPC id 配对和 tool schema

在 server 里把每个入向、出向 id 都 log 一下、确认对得上。然后拉 tool list、校验 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"}'

把返回的 inputSchema 过一遍 JSON Schema validator、把报的错都修了。

Step 8:给真正慢的工具设对超时

如果某个工具确实需要比默认 per-call 预算更久,那就在对的地方调高它。给 .mcp.json 里那个 server 的条目加一个 per-server timeout(毫秒):

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

这给这个 server 的工具调用设了十分钟的墙钟上限、并覆盖它的 MCP_TOOL_TIMEOUT。低于 1000 的值会被忽略。对于特别长的任务,更干净的做法是让工具立刻返回一个 job id、让 Claude 轮询,而不是把调用一直挂着。对于启动慢的 server,改的是 MCP_TIMEOUTMCP_TIMEOUT=30000 claude

怎么验证修好了

  • /mcp 里这个 server 显示已连接、tool 数量非 0。
  • 一次简单的工具调用(list、ping、status)远远不到一秒就回结果。
  • server 的 stderr 里能看到 id 配对的请求/响应行,它的 stdout 里只有 JSON-RPC。
  • claude --debug 显示工具注册成功、调用也成功。
  • 反复调用几分钟后行为不回退。

长期预防

  • server 日志默认全走 stderr;stdio 下永远不往 stdout 写。加个 CI 检查,grep server 的 stdout 看有没有非 JSON 行。
  • JSON-RPC id 原样从请求 echo 到响应里。
  • 新的远程 server 优先用 streamable HTTP,别用已废弃的 SSE transport。
  • 配置里把 MCP server 版本钉死,免得 schema 突然变。
  • 加集成测试:stdio 模式起 server、真的跑一遍完整的 initialize + tools/list 往返。
  • 新 server 在接进 Claude Code 之前,先用官方 MCP Inspector 测一遍(见常见问答)。

容易踩的坑

  • stdio server 里用 print() / console.log 调试,输出落 stdout、流就坏了。
  • 调高 MCP_TIMEOUT 还指望工具调用等久点;那个变量只影响启动。
  • .mcp.json 里把 timeout 设成低于 1000,会被整个忽略。
  • HTTP MCP 在 nginx 或 Cloudflare 后面忘了关缓冲。
  • 同一个 server 配了两份(一份 local、一份 project),连到了错的那份;优先级是 local、然后 project、再 user。
  • 同一个 request id 写了两份响应,Claude Code 只消费第一份。

常见问答

  • Claude Code 里 MCP 工具默认超时是多少? 截至 2026 年 6 月,工具调用没有 30 秒的硬墙。MCP_TOOL_TIMEOUT 不设、也没有 per-server timeout 时,有效上限非常大(约 28 小时)。你可能见过的 60 秒,是底层 SDK 的请求超时(MCP error -32001)和 HTTP/SSE 首字节最低值,不是 Claude Code 的工具硬墙。
  • 怎么调大超时? 慢工具就在 .mcp.json 里那个 server 条目加 "timeout": 600000(毫秒)。启动慢就 MCP_TIMEOUT=30000 claude。别搞混,这俩管的是不同阶段。
  • 为啥 curl 能通 Claude Code 不通? stdio 的话几乎都是 stdout 上有非 JSON 字节。HTTP 的话几乎都是代理把流缓冲住了、或者缺了 Accept: text/event-stream 那一支。把两边 wire 格式抓下来比一下。
  • SSE 还支持吗? 还支持,但 SSE 截至 2026 年初已废弃、推荐用 streamable HTTP。现有的 SSE server 仍能用 claude mcp add --transport sse 连上;新东西用 --transport http
  • 不开 Claude Code 能调试 MCP 吗? 能。跑官方 MCP Inspector:npx @modelcontextprotocol/inspector node my-mcp-server.js。UI 在 http://localhost:6274 打开,能用 stdio、SSE 或 streamable HTTP 跑一遍 initializetools/list 和工具调用。
  • 我的 HTTP server 会话中途老显示离线。 Claude Code 会按指数退避自动重连 HTTP/SSE server(最多五次)。代理老把 SSE 流掐断,就去修代理超时;重连循环是症状、不是根因。
  • 失败的调用会自动重试吗? 连接层失败会做有限次重连。超时的工具调用不会静默重跑;等底层超时或 transport 问题修好后,再重发一次 prompt。

相关

标签: #Claude Code #mcp #排查 #排查