工具大全
踩坑实录作者:Coocon2026年9月24日10 次阅读约 8 分钟阅读

Claude Code MCP server Failed to connect 怎么排查:CONNECTION_CLOSED、connection timed out after 30000ms、ENOENT 逐条实测

问题背景

给 Claude Code 挂上一个 MCP server 之后,claude mcp list 可能会显示这样一行:

✘ Failed to connect — CONNECTION_CLOSED: Connection closed

也可能是 ENOENTconnection timed out after 30000msECONNREFUSED,或者 claude -p 在启动时直接报 Error: Invalid MCP configuration:。这些报错都在说同一件事:「连不上」。但「连不上」本身不是原因,没法照着修。

这篇把「连不上」拆回成一个个可判定的具体原因:每种原因各自产生什么报错原文,看到报错后怎么在一分钟内判断是哪一种、怎么修。所有报错原文都来自 2026-09-24 在本机的真实运行,逐字照抄。

问题分析

MCP server 从配置到可用要走四步,每一步失败的报错都不一样:

  1. 读配置--mcp-config 文件能不能找到、JSON 是否合法、结构对不对
  2. 起进程 / 建连接:stdio server 要 spawn 一个命令(命令找不到就是 ENOENT);http/sse server 要连 URL(端口上没服务就是 ECONNREFUSED)
  3. 进程活下来:进程起来后如果立刻退出,Claude Code 只能看到管道断了,也就是 CONNECTION_CLOSED
  4. MCP 握手:进程活着,但必须在超时时间内回应 initialize,否则就是 CONNECT_TIMEOUT

难排查的是第 3 步和第 4 步:报错只说明失败发生在哪一步,不说明为什么失败。实验里最重要的发现也集中在这两步。

技术方案与选型

被测 server 全部自己写,放在实验目录的 stubs/ 下,共三个:

  • good-server.mjs:零依赖的最小 MCP stdio server,17 行,只实现 initialize / tools/list / tools/call,唯一的工具 echo 返回 LAB-ECHO:<text>
  • exit1-server.sh:往 stderr 打一行 lab-exit1: fatal: missing LAB_API_KEY, refusing to startexit 1
  • hang-server.mjs:往 stderr 打一行后什么也不回,300 秒后自己退出

排除项:为什么不装第三方 MCP server 来测。 真实 server 失败时掺着各自的业务逻辑、网络和依赖版本,报错属于哪一层说不清楚;而且 npx -y 会联网下载,实验无法复现,也有供应链风险。stub 让每个 case 只改变一个变量,LAB-ECHO: 前缀还能证明工具结果确实来自这个 server,不是模型编的。

排除项:为什么不在自己的真实配置上测。 本机 ~/.claude.json 里配了真实 server,一旦混进来就分不清是谁在报错,改坏了还会影响日常使用。所以每个 case 各用一个空的 CLAUDE_CONFIG_DIR(user 级 server 写在这个临时目录的 .claude.json 里);claude -p 一律加 --mcp-config <临时 json> --strict-mcp-config,只有验证 strict 作用的那一次故意不加。

两种观测方式

  • claude mcp list 用来看健康检查原文,不花钱,可以随便跑。每次都加 --debug-file 保存完整日志,并记录退出码和墙钟
  • claude -p 用来看模型侧的影响,总共只跑 8 次。每次都加 < /dev/null(不加会先白等 3 秒 stdin,延迟读数就废了),用 --output-format stream-json --verbose 同时拿到 init 里的 server 状态、工具列表,以及 result 里的 duration_ms / num_turns / total_cost_usd

环境:Claude Code 2.1.280(Mach-O 原生二进制)、macOS 26.3.1、Node v25.9.0;-p 实际使用的模型是 claude-opus-5-5[1m],走第三方 ANTHROPIC_BASE_URL

实测过程

Case 1:命令不存在

stdio server 的 command 指向 /nonexistent/mcp-serverclaude mcp list(退出码 0,墙钟 0.16s):

Checking MCP server health…

ghost: /nonexistent/mcp-server  - ✘ Failed to connect — ENOENT: ENOENT: no such file or directory, posix_spawn '/nonexistent/mcp-server'

claude -p 里,这个 server 和其它坏 server 放在同一组跑(见 case 7)。stderr 是空的,init 里只显示 'status': 'failed';debug 日志里是:

2026-09-24T02:06:21.642Z [DEBUG] MCP server "ghost": Connection failed after 3ms (ENOENT): ENOENT: no such file or directory, posix_spawn 'stdio'

同一个配置,mcp list 显示的是真实路径 posix_spawn '/nonexistent/mcp-server'-p 的日志里却是 posix_spawn 'stdio'-p 路径下,报错里看不到你写错的那个路径,要回到 mcp list 才看得到。

⚠️ 未测:只挂这一个 server、单独跑一次 claude -p 的退出码和耗时没有测到。那一次调用我忘了先生成配置文件,抓到的是「配置文件不存在」(见 case 5),而 8 次 -p 的预算已经用完。它在 -p 下的表现只有混合组里的数据:3ms 失败。

Case 2:依赖不在 PATH

模拟从 IDE、launchd 等「PATH 里没有 Homebrew」的环境启动 claude:env -i HOME=$HOME PATH=/usr/bin:/bin 运行 claude mcp list,只改 command 字段:

case command 结果 墙钟
02a node ✘ Failed to connect — ENOENT: Executable not found in $PATH: "node" 0.69s
02b npx ✘ Failed to connect — ENOENT: Executable not found in $PATH: "npx" 0.67s
02c /opt/homebrew/bin/node(解释器绝对路径) ✔ Connected 0.76s
02d stubs/good-server.mjs(脚本绝对路径,shebang #!/usr/bin/env node ✘ Failed to connect — CONNECTION_CLOSED: Connection closed 0.70s
02e node,配置里加 "env":{"PATH":"/opt/homebrew/bin:/usr/bin:/bin"} ✔ Connected 0.76s

PATH=/usr/bin:/bin 下四次 claude mcp list 的真实输出 图:在 env -i HOME=$HOME PATH=/usr/bin:/bin 下运行 claude mcp list 的输出(依次为 02a / 02c / 02d / 02e,用 tail -1 取自各 case 的 stdout.txt)。

02d 是最容易踩的:把脚本改成绝对路径,看起来修好了,其实只修好一半。脚本 shebang 里的 env node 仍然按同一个 PATH 去找 node。mcp list 只显示 CONNECTION_CLOSED,真因在 debug 日志里:

2026-09-24T02:04:56.121Z [ERROR] MCP server "good" Server stderr: env: node: No such file or directory

在同样的 PATH 下直接运行这个脚本,结果是 exit=127,stderr 为 env: node: No such file or directory

解法(02c、02e 两种实测都有效):command 写解释器的绝对路径;或者在该 server 的 env.PATH 里补上目录,配置里的 env.PATH 也会参与命令查找。

Case 3:server 启动后立刻退出

claude mcp list(退出码 0,墙钟 0.60s):

crashy: /Users/duoduo/4khz/magictools/tmp/2026-09-24-mcp-connect-lab/stubs/exit1-server.sh  -  Failed to connect — CONNECTION_CLOSED: Connection closed

报错里既没有退出码,也没有退出信号。加上 --debug-file 后才看到 server 自己写的那行 stderr:

2026-09-24T02:04:50.394Z [DEBUG] MCP server "crashy": Starting connection with timeout of 30000ms
2026-09-24T02:04:50.397Z [ERROR] MCP server "crashy" Server stderr: lab-exit1: fatal: missing LAB_API_KEY, refusing to start
2026-09-24T02:04:50.398Z [DEBUG] MCP server "crashy": Connection failed after 4ms (CONNECTION_CLOSED): Connection closed

Case 4:server 活着但不回握手

默认超时 30 秒,mcp list 墙钟 30.19s

hangy: /Users/duoduo/4khz/magictools/tmp/2026-09-24-mcp-connect-lab/stubs/hang-server.mjs  -  Failed to connect — MCP server "hangy" connection timed out after 30000ms
2026-09-24T02:04:56.854Z [DEBUG] MCP server "hangy": Starting connection with timeout of 30000ms
2026-09-24T02:05:26.859Z [DEBUG] MCP server "hangy": Connection timeout triggered after 30005ms (limit: 30000ms)
2026-09-24T02:05:26.865Z [DEBUG] MCP server "hangy": Connection failed after 30007ms (CONNECT_TIMEOUT): MCP server "hangy" connection timed out after 30000ms

MCP_TIMEOUT=5000 可以把超时缩短到 5 秒,墙钟 5.18s,报错变成 connection timed out after 5000ms,适合快速复现。

进程残留mcp list 超时退出后,stub 进程还活着,已经被 init 收养:

  PID  PPID ELAPSED COMMAND
72132     1   00:36 node /Users/duoduo/4khz/magictools/tmp/2026-09-24-mcp-connect-lab/stubs/hang-server.mjs

再跑一次 MCP_TIMEOUT=5000,残留变成两个(72132、72250)。stub 用的是 Node 默认的 SIGTERM 处理(收到就退出),它能活下来,说明 mcp list 退出前没有给它发 SIGTERM。作为对照,4 次 claude -p 跑完后都没有残留进程。

⚠️ 适用范围:我的 hang stub 不会在 stdin EOF 时退出。正常的 MCP server 一般在 stdin 关闭后会自己退出,所以「会留下孤儿进程」这个结论只适用于卡死、不理会 stdin EOF 的 server。正常 server 超时后会不会残留,这次没有测。

Case 5:--mcp-config 本身写错

三种情况都是退出码 1、stdout 为空、耗时 0.1 秒级,连 --debug-file 指定的日志都没生成,可见在读配置阶段就退出了,没走到发请求那一步:

JSON 语法错(尾逗号,0.14s):

Error: Invalid MCP configuration:
MCP config is not a valid JSON

顶层键写成 mcpServer(0.10s):

Error: Invalid MCP configuration:
mcpServers: Invalid input

文件路径不存在(0.11s,就是 case 1 里那次失误调用抓到的):

Error: Invalid MCP configuration:
MCP config file not found: /Users/duoduo/4khz/magictools/tmp/2026-09-24-mcp-connect-lab/configs/ghost-only.json

语法错的报错不给行号,用 jq . <file>python3 -m json.tool <file> 定位。

--strict-mcp-config 的作用:在隔离 config dir 的 .claude.json 里放了一个 user 级 server usercanary。加 strict 时,init 里只有 good;不加时,init 里是 usercanarysource: user)加 goodsource: dynamic),工具列表为 ['mcp__good__echo', 'mcp__usercanary__echo']。也就是说,不加 strict 时 --mcp-config叠加在已有配置上,不是替换。

另外,claude mcp list --help 里只有 -h 一个选项,它不接受 --mcp-config,只读已保存的配置。用 mcp list 查不到 --mcp-config 里的 server 是正常现象,不代表配置没生效。

Case 6:http / sse 打到死端口

deadhttp: http://127.0.0.1:9/mcp (HTTP) - ✘ Failed to connect — ECONNREFUSED: ECONNREFUSED: Unable to connect. Is the computer able to access the url?
deadsse: http://127.0.0.1:9/sse (SSE) - ✘ Failed to connect — SSE error: ECONNREFUSED: Unable to connect. Is the computer able to access the url?

两次墙钟都是 0.16s。在 claude -p 会话中,远程 transport 失败后会反复重连:两次运行中 deadhttpStarting connection 分别出现 4 次和 3 次,每次都立即 ECONNREFUSED。debug 日志还会打印 "HTTP_PROXY":"not set" 这类环境信息,排查代理问题时可以直接看。

Case 7:坏 server 对模型侧有什么影响

两组用同一个 prompt:调用 mcp__good__echo("ping")、计算 17*23、列出所有 mcp__ 工具,各跑 2 次。坏组在好 server 之外,再挂上 case 1/3/4/6 的五个坏 server。

run 墙钟 duration_ms num_turns total_cost_usd
good-1 5.36s 5056 2 0.2702058
good-2 3.90s 3591 2 0.0222042
bad-1 37.90s 7622 2 0.0222042
bad-2 34.52s 4238 2 0.0222042
中位数 good 4.63s 4323.5 2 0.1462050
中位数 bad 36.21s 5930 2 0.0222042

4 次的结果完全一致:LAB-ECHO:ping / 391 / mcp__good__echo。坏组 init 里 5 个坏 server 都是 'status': 'failed',工具列表里只有 mcp__good__echo任务照常完成,坏 server 只是没有贡献工具。

慢的原因在 debug 日志里:good 组从第一行日志到第一次 [API REQUEST] 约 0.2 秒,bad 组约 30.2 秒(02:06:21.489 → 02:06:51.699),正好等于 hangy 的 30 秒超时。claude -p 要等所有 server 连上或超时后,才发出第一个 API 请求。 而 ENOENT、exit 1、ECONNREFUSED 这几种失败都在 3~36ms 内结束,真正拖慢启动的只有不回握手的那一个。

⚠️ 未核实:good-1 的 $0.2702058 明显高于其它三次的 $0.0222042。我推测是这组工具定义第一次调用时的缓存写入,但没有读 usage 字段核实,所以成本中位数被它拉高,不代表「挂坏 server 更便宜」。能确认的只有:bad 两次与 good-2 的成本完全相同,连接失败的 server 没有增加 token。

Case 8:修复后链路真的通了

good 组 init 里是 {'name': 'good', 'status': 'connected'},模型实际调用了工具,拿到的 LAB-ECHO:ping 只可能来自 good-server.mjs

2026-09-24T02:06:21.661Z [DEBUG] MCP server "good": Successfully connected (transport: stdio) in 23ms
2026-09-24T02:06:55.788Z [DEBUG] MCP server "good": Calling MCP tool: echo
2026-09-24T02:06:55.795Z [DEBUG] MCP server "good": Tool 'echo' completed successfully in 7ms

PATH 类问题的修复对照见 case 2:02a 失败,02c / 02e 修好后都是 ✔ Connected

实践效果

同一句 CONNECTION_CLOSED,两个完全不同的原因

claude mcp list 与 debug 日志:exit 1 和 shebang 缺 node 输出同一句 Connection closed 图:case 3 与 case 2d 的 claude mcp list 输出(tail -1 stdout.txt)完全相同;对 --debug-file 日志 grep Server stderr 后才分出原因:一个缺环境变量,一个找不到 node。

这是整篇最有用的一条经验:看到 CONNECTION_CLOSED 时别去猜,先拿 stderr

claude --debug-file /tmp/mcp.log mcp list
grep 'Server stderr' /tmp/mcp.log

墙钟 36 秒,duration_ms 只报 5.9 秒

claude -p 挂一个不握手的 server 时的 debug 日志时间线与耗时读数 图:bad-1 的 claude -p debug 日志中,hangy 开始连接到超时正好 30 秒,第一次 API 请求紧跟在超时之后;下方是 bad-1 / good-1 的墙钟(meta.txt)与 stream-json 里的 duration_ms。

两组中位数:墙钟 36.21s vs 4.63sduration_ms 5930 vs 4323.5。如果只看 JSON 里的 duration_ms 做监控或压测,这 30 秒会完全漏掉。

速查表:看到哪句报错,先查什么

报错原文(关键片段) 原因 一分钟判定
ENOENT: no such file or directory, posix_spawn '<路径>'-p 日志里显示为 'stdio' command 路径不存在 ls -l <command>
ENOENT: Executable not found in $PATH: "node" / "npx" 启动 claude 的环境 PATH 里没有这个命令 看启动环境的 PATH;改用解释器绝对路径或配 env.PATH
CONNECTION_CLOSED: Connection closed 进程起来就退出了(exit 非 0、shebang 找不到解释器……) --debug-file 后 grep Server stderr;或把 command+args 复制到终端直接运行
connection timed out after 30000msCONNECT_TIMEOUT 进程活着但不回 initialize MCP_TIMEOUT=5000 快速复现;pgrep -fl 查残留
ECONNREFUSED: Unable to connect. Is the computer able to access the url?(SSE 前面多一个 SSE error: http/sse 端口上没有服务 curl -i <url>
MCP config is not a valid JSON --mcp-config 语法错(不给行号) jq . <file>
mcpServers: Invalid input 顶层键或结构写错 检查顶层是否为 mcpServers
MCP config file not found: <路径> --mcp-config 路径写错 ls <path>
没有报错,只是启动慢 30 秒 有一个 server 卡在握手 对比墙钟与 duration_ms;看日志里第一次 [API REQUEST] 的时间

踩坑

  • claude mcp list 连接失败也返回 0。 实验里所有失败 case 的退出码都是 0。CI 或脚本里做健康检查,要解析输出里的 ,不能看退出码。
  • 只把脚本改成绝对路径不够。 shebang 里的 env node 仍然走 PATH,结果从 ENOENT 变成更隐蔽的 CONNECTION_CLOSED。
  • 不回握手的 server 会拖住 claude -p 30 秒,而且 duration_ms 看不出来。做延迟实验一定要记墙钟。
  • mcp list 超时后可能留下孤儿进程(只在不理 stdin EOF 的 server 上验证过),调试完用 pgrep -fl <server 路径> 检查一遍。
  • 实验本身的坑:我把 env -i HOME=.. PATH=.. 整串放进一个带引号的变量传给脚本,结果整串成了一个 argv 元素,第一轮 5 个 case 全报 No such file or directory 白跑了;另外开跑前没检查配置文件是否都存在,浪费了一次 claude -p 预算,case 1 的单独 -p 读数也因此缺失。

相关阅读

相关文章

DeepSeek 标称 1M、Claude Code 只认 200K:两个都测了,真正卡住你的是第三个数

DeepSeek 标称 1M 上下文,而 Claude Code 接上去之后对同一个模型报告 contextWindow 200000。我把两边都测了。DeepSeek 的真实上限是 1,048,576——字面的 2 的 20 次方,不是一百万整——而且这个额度**含**你的 max_tokens 输出预算,用一组对照实验坐实。针插在第 0 位、上下文 1,039,744 token 时仍被准确捞出。Claude Code 则在远低于此处就本地拒绝,25 毫秒、零 API 调用,而且它卡的**根本不是 token**:闸门在约 48 万字符。喂高熵文本,478,000 字符顺利通过、真实 token 高达 309,567——比它刚刚自称的 20 万窗口还多 55%。而日常使用里这三个数你一个都碰不到,因为 Bash 输出超过整 30,000 字符就压根不进上下文。

claude-code长上下文+5
hands-on2026年9月20日6 min
97

拿 DeepSeek 当 Claude Code 后端:功能全通,但成本显示虚高 38 倍

DeepSeek 提供 Anthropic 格式端点,三个环境变量就能让 Claude Code 转去调它。我在真机上把整条链跑了一遍:本地工具(Read / Write / Bash / Glob / Edit / 子代理)全部正常且副作用真实发生,所以结论是能用。但没人测过的那部分是——Claude Code 按 Claude Sonnet 的价目给 DeepSeek 的 token 计费。十次相同调用,Claude Code 报告花了 $1.71,DeepSeek 账户实际只扣了 ¥0.32(≈$0.045),虚高 38 倍,这是余额差不是价目表推算。另外:官方文档关于未知模型名的说法是错的(会 400 而不是回落),v4-pro 默认返回 thinking 块导致小 max_tokens 看起来是空响应,以及一个看着像 DeepSeek 的锅其实不是的失败。

claude-codedeepseek+5
hands-on2026年9月20日7 min
93

服务、端口、证书全正常,VPN 却断了 4 小时:Tailscale 接管 DNS 后把翻墙机的解析清空了

一台跑 sing-box(VLESS-REALITY + Hysteria2)的洛杉矶 VPS,装上 Tailscale 第二天 VPN 全断。systemctl、端口、证书全部正常,根因藏在 /etc/resolv.conf:Tailscale 默认接管 DNS,tailnet 后台没配全局 nameserver,dhclient 续租时 resolv.conf 被读成空文件,tailscaled 从此对所有公网域名回 SERVFAIL,REALITY 握手连 www.apple.com 都解析不了。本文给出完整时间线、每一步的证据命令、三种修法,以及让 AI 助手(Claude Code)以后不再踩这个坑该写进 CLAUDE.md 的几条规则。

claude-code故障排查+8
pitfalls2026年9月17日5 min
132

CLAUDE.md 不是 README:给 Claude Code 写规则的 5 条硬规矩

很多人把 README 或架构文档整个贴进 CLAUDE.md,结果 Claude Code 该守的规矩不守、不该改的文件乱改。原因是 CLAUDE.md 不是文档,是每一轮对话都常驻上下文的指令。本文给 5 条硬规矩:只写代码里推不出来的东西、越短越管用、硬规则交给 hooks 不靠文字、按层级拆文件、被纠正后当场回写。附一份可直接抄的骨架。

claude-code提示词+4
developer2026年9月12日4 min
228