你在 Claude Code 里配了一个 MCP server(filesystem、GitHub、或者自己写的)。直接 curl 它的 endpoint 一切正常、启动日志也健康,但 Claude Code 发出的每次工具调用不是卡住就是报错。多数情况下 server 并没有真挂——是 Claude Code 在等一个永远不会以它能识别的形式到来的回复,或者它在一个你根本不知道存在的预算上放弃了。
最快的修复: 如果 server 是你自己写的、用的是 stdio transport,最常见的原因就是 server 往 stdout 打了一行非 JSON 的东西(banner、console.log、print())。stdio 下 stdout 只能放协议消息。把所有 log 改到 stderr、重启就行。如果是 HTTP/SSE,最常见的是反代把流缓冲住了、响应根本传不到 Claude Code;其次是撞上了某个可以调高的超时。本文接下来是按命中率排好的排查顺序。
先搞清楚:你撞的到底是哪个「超时」?
Claude Code 有好几个互相独立的预算,老文章那句「30 秒上限」截至 2026 年 6 月已经不对了。动手改之前,先把你的现象对到正确的旋钮上。
| 现象 | 撞到的预算 | 默认值(2026 年 6 月) | 怎么改 |
|---|---|---|---|
Server 根本连不上、/mcp 里不显示 | 启动 / spawn 超时 | MCP_TIMEOUT 环境变量(常见写法 MCP_TIMEOUT=10000) | MCP_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 报 -32001 | SDK 请求超时 | 多数 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.log、print()、启动 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 closed 或 spawn ... ENOENT。
怎么判断: claude mcp get <name> 确认确切的 command,然后 Claude Code 跑着的时候 ps aux | grep -i mcp,server 进程应该活着。
7. 你调了超时,但变量或作用域搞错了
你设了 MCP_TIMEOUT 想让工具调用等久点,结果没变化——因为那是启动预算。同理,.mcp.json 里 timeout 低于 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 出来那个进程的原始
stdout和stderr。 - 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 closed 或 spawn ... 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/json 和 text/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_TIMEOUT:MCP_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-servertimeout时,有效上限非常大(约 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 跑一遍initialize、tools/list和工具调用。 - 我的 HTTP server 会话中途老显示离线。 Claude Code 会按指数退避自动重连 HTTP/SSE server(最多五次)。代理老把 SSE 流掐断,就去修代理超时;重连循环是症状、不是根因。
- 失败的调用会自动重试吗? 连接层失败会做有限次重连。超时的工具调用不会静默重跑;等底层超时或 transport 问题修好后,再重发一次 prompt。
相关
- Claude Code 工具执行卡住
- Claude Code settings.json 不生效
- Claude Code subagent 结果回传不到主会话
- Claude Code hook 莫名拦下 Edit
- Claude Code 半执行后卡住
- Claude Code 输出被上下文截断
标签: #Claude Code #mcp #排查 #排查