MCP 连不上?按固定顺序定位故障
指南针对连接失败、服务已列出却无工具等问题,提供 Claude Code、Claude Desktop 和 Cursor 的排查顺序。作者用最小 stdio MCP 服务复现 Claude Code 错误,其他客户端说明依据官方文档,并强调标准输出必须留给协议通信。
AI 深度解读
MCP server 显示红点、连不上,或者已经列出来却没有工具,问题未必出在模型。作者通过主动破坏一个本地测试服务,整理出一套排查顺序:先确认配置和授权,再检查进程、环境变量,最后处理认证、超时与日志。
这篇最有用的结论是:Connected 只能证明连接建立,不能证明凭据有效,更不能证明工具能完成任务。 对经常给 Claude Code、Claude Desktop、Cursor 接 MCP 的程序员来说,比记住几个修复命令更有价值的,是知道每一步究竟验证了什么。
先确认客户端读到了什么,再检查服务怎么启动
作者的实测环境是 macOS、Claude Code 2.1.303 和 Node v24.13.0。他们写了一个最小 stdio MCP server,通过标准输入接收按行分隔的 JSON-RPC,处理 initialize、tools/list、tools/call,提供一个 echo 工具。协议消息走 stdout,日志只走 stderr。
这个测试服务的作用是控制变量:故意写错命令、去掉凭据、破坏 JSON,再看客户端实际报什么。
项目级服务通过 claude mcp add --scope project 添加,配置写进当前目录的 .mcp.json。服务命令前要放 --,否则服务自己的参数可能被 Claude Code 接走。作者漏掉它时,服务参数 --port 3000 就触发了 unknown option '--port'。
配置写进去,也不代表进程已经运行。作者首次执行 claude mcp list,看到的是 Pending approval,此时 Claude Code 根本没有启动服务。即使提前在 .claude/settings.local.json 写了批准配置,未受信任的工作区仍然要先经过信任确认。
正常处理是在对应目录运行 claude,完成授权。作者为了非交互测试,把 CLAUDE_CONFIG_DIR 指向临时目录,先用 enabledMcpjsonServers 按名称批准,后续才使用 enableAllProjectMcpServers。后者会批准所有仓库里的项目服务,不适合为了消除提示就写进真实用户配置。
服务完全不在列表里时,先查文件位置、JSON 语法和配置诊断。原文特别指出,Claude Code 不读取 ~/.claude/mcp.json 或 ~/.claude/.mcp.json;用户级配置应通过 claude mcp add --scope user 添加。
为什么终端能跑,客户端却连不上
第二步是把配置中的命令和参数原样拿到终端执行。健康的 stdio 服务可能启动后安静等待输入,没有欢迎语并不代表卡死。
如果报 ENOENT,先找可执行文件。作者把命令故意写成 nodejs-missing,客户端明确提示无法在 $PATH 中找到它。这是查找失败,sudo 不能修好命令名称。
终端和 GUI 应用也未必拥有同样的环境。原文引用的 MCP 文档指出,stdio 服务继承的环境变量有限且依平台变化,工作目录也可能不是项目目录,例如 macOS 上可能是 /。因此命令和文件参数尽量使用绝对路径,所需变量通过服务的 env 配置传入。不要默认 Dock 启动的应用一定读取了 ~/.zshrc。
更隐蔽的是变量引用没有展开。作者用下面的配置要求服务必须有 token,同时把 token 写成变量引用:
claude mcp add-json --scope project with-token \
'{"type":"stdio","command":"/usr/local/bin/node","args":["<dir>/echo-server.mjs"],"env":{"REQUIRE_TOKEN":"1","ECHO_TOKEN":"${ECHO_TOKEN}"}}'
关键看 ECHO_TOKEN 对应的 "${ECHO_TOKEN}",以及外层单引号:单引号阻止 shell 提前替换变量。<dir> 是作者临时目录的占位符,使用时需要换成实际绝对路径。
当 ECHO_TOKEN 未设置时,结果居然仍是 Connected,只是诊断区提示 Missing environment variables: ECHO_TOKEN。日志显示,服务收到的是未经展开的 ${ECHO_TOKEN} 字符串。
原因并不神秘:测试服务只检查值是否为空,这段字符串当然不为空。真实 API 若拿它当凭据,就会认证失败。作者在同一 shell 设置 ECHO_TOKEN=FAKE_TOKEN_FOR_DOCS 后,警告和对应日志才消失。
共享配置因此需要同时解决两件事:文件里不保存秘密,每个人的运行环境又确实能提供秘密。Claude Code 使用 ${VAR},Cursor 使用 ${env:VAR}。如果执行 claude mcp add --env API_TOKEN="$API_TOKEN",shell 会先展开真实值;原文指出,local 或 user scope 下它会被保存到私有 ~/.claude.json,不要把这个写法直接搬到共享配置。
也不要复用 Claude Code 自身的 ANTHROPIC_API_KEY 给远程 MCP。按原文引用的文档,这类凭据变量在远程服务的 url 和 headers 中会读为空。
根据错误分层处理,最后用工具结果验收
作者的几个实验,把常见故障分得很清楚。
CONNECTION_CLOSED 只说明连接关闭。测试中真正的原因是设置了 REQUIRE_TOKEN=1,却没提供 ECHO_TOKEN,服务启动即退出。claude mcp get needs-token 也没有给出更具体的原因。需要手动复现,或运行 claude --debug-file ./debug.log mcp list,从服务 stderr 中找到缺少变量的提示。调试文件应按敏感文件处理,用完删除。
JSON 语法错误则发生得更早。作者只在 .mcp.json 末尾多加一个逗号,整个文件的服务都消失了。但配置项错误可能只影响一项:远程条目有 url、没有 type 时,该条目被跳过,其他服务仍保留。诊断提示可补 "type": "http",或按实际服务使用 "sse"、"ws"。
远程服务要另外区分认证与连通性。Needs authentication 表示服务可达,但还需要登录或 token;401 通常是没有有效凭据,403 通常是权限不足。这部分来自文档,作者没有实测远程 HTTP 或 OAuth。
超时也要区分下载慢与服务不回应。作者用一个持续存活、却不处理 MCP 请求的进程模拟后者:
claude mcp add --scope project silent -- /usr/local/bin/node -e "setTimeout(() => {}, 60000)"
MCP_TIMEOUT=3000 claude mcp list
关键是第二行的 MCP_TIMEOUT=3000:这次报错明确指出等待了 3000ms。未设置时,作者日志里的连接超时是 30000ms。首次 npx 下载包较慢,可以先手动运行让包进入缓存,或使用 MCP_TIMEOUT=60000 claude;服务始终不回答握手,延长等待也解决不了。
stdout 污染同样不能忽略。作者在启动前输出一行 banner,这个版本的 Claude Code 仍然连接成功。但这只是单一客户端对一行启动输出的表现,不能据此认定协议流可以混入日志。
验收时,先看 claude mcp list 是否连接且没有诊断警告,再用 claude mcp get <name> 核对实际生效的作用域、命令和环境。文档还提供会话内 /mcp,用于查看工具和进行 OAuth 登录,不过作者没有实测交互会话。
最后让它执行一次只需要一个只读工具的任务,并检查真实数据是否返回。 到这一步,才验证了服务确实能用。
我的判断
我认同这套顺序。它把「配置没读到」「进程没启动」「握手没完成」「凭据不对」拆开,避免围着同一个红点反复重装。对维护 Node MCP server 的团队,最小测试服务也很有参考价值:先排除第三方服务的复杂依赖,判断客户端链路是否正常。
但它不是跨客户端的完整验证报告。作者只跑了 macOS 上的 Claude Code 2.1.303,本地服务版本为 0.0.1,握手代码回退到 2025-06-18 协议,并以客户端的 legacy 模式连接。原文提到,2026-07-28 协议新增了必需的逐请求字段和错误码,这些失败路径没有覆盖。
Claude Desktop、Cursor、Windows、远程 HTTP、OAuth 和交互 /mcp 的部分都来自文档。原文还提到 Windows 下用 cmd /c npx 的社区修复方式,但没有找到官方文档支持。不能把这些内容都当成作者跑通的结论。
与直接查看日志相比,固定顺序更适合缩小范围;如果怀疑服务实现本身,原文引用的官方调试文档推荐先用 MCP Inspector 独立测试,但作者没有运行它。文章也没有提供排查耗时的对照数据,因此「更省时间」仍是有依据的经验判断。
能拿来做什么
- 给前端或全栈仓库添加 MCP 时,把准确命令、绝对路径、变量名称和授权步骤写进接入说明,并从服务 README 核对包名与 URL。
- 调试 Node stdio 服务时,先手动运行相同命令和环境,再看 stderr;所有运行日志都写入 stderr,让 stdout 专门传输协议消息。
- 让 AI 协助配置时,只提供变量名,秘密值自行填入本地环境。原文的小样本评估中,未使用其 skill 的 3 次回答全部复述了用户粘贴的密码;已经贴入对话的凭据应撤销并重建。
- 把验收标准写成「连接成功、诊断无警告、只读工具返回真实结果」。每次只改一个因素,修复后重新验证对应环节。