ClaudeMap

·MCP 服务器

MCP 服务器调试实战手册——如何用 MCP Inspector 驱动服务器、各 Host 把日志写在哪里、占据大多数真实 bug 的失败模式,以及把服务器搬到生产环境时会发生什么变化。

MCP 服务器调试:Inspector、日志,以及那些最坑的失败模式

一个 MCP 服务器是一个常驻进程,通过管道或 socket 承载 JSON-RPC 通信——这意味着当出问题时,故障几乎从不在你以为的地方。一个服务器可能干净启动、声明了自己的工具,却返回不了任何有用的东西,因为某个环境变量为空,因为 stderr 的内容泄漏进了协议流,或者因为某个权限提示在一个你已经关掉的窗口里弹了出来。本指南是 MCP 服务器调试的实战手册,涵盖 MCP Inspector、各 Host 把日志写在哪里、占真实 bug 大头的失败模式,以及把服务器从笔记本搬到生产环境时会发生什么变化。

从 Inspector 开始

对 MCP 而言,最好的单一调试工具是官方的 MCP Inspector——一个浏览器 UI,直接和服务器说协议,中间没有任何 Host。你可以对任何服务器命令启动它:

npx @modelcontextprotocol/inspector node path/to/server.js

Inspector 会打开一个本地网页。在那里你可以:

  • 查看连接握手过程和服务器声明的能力。
  • 列出服务器的工具、资源和提示模板,以及它们完整的 JSON schema。
  • 用手敲的参数调用一个工具,检查原始响应。
  • 逐条查看 Inspector 和服务器之间的 JSON-RPC 流量。

这是反馈最快的循环。如果一个工具在 Inspector 里能用、在 Claude Desktop 里不行,问题在 Host 配置或环境,不在你的服务器。如果在 Inspector 里也失败,你就可以直接迭代服务器代码,不用每次改动都等应用完整重启。

Inspector 适用于任何 stdio 服务器。对于 HTTP 服务器,把它的 URL 传给 Inspector 即可。它也是在你决定集成某个服务器之前,先摸清它的好办法——你能在写任何配置之前就了解工具名和参数形状。

读懂 Host 已经在写的日志

当服务器在某个 Host 里运行时,Host 会捕获它的输出。值得知道的两份日志:

Claude Desktop 在 macOS 上按服务器各写一个日志文件,路径是 ~/Library/Logs/Claude/mcp-server-<名字>.log。Windows 上对应的是 %USERPROFILE%\AppData\Roaming\Claude\logs\。你的服务器写到 stdout 或 stderr 的任何东西都会落到这里,和 Host 自己的协议消息交织在一起。在复现 bug 时实时跟踪这个文件:

tail -n 100 -f ~/Library/Logs/Claude/mcp-server-filesystem.log

Claude Code 通过 /mcp 命令交互式地展示服务器状态——列出每个已配置的服务器、它的连接状态、它注册了哪些工具。要看更深的细节,它会和其他会话输出一起写日志;通常 /mcp 面板就足够判断一个服务器到底有没有连上。

一个常见做法是:开发期间给自己的服务器加结构化日志,发布时再剥掉。把每行日志写成一个带时间戳和级别的单一 JSON 对象——这让交织的日志比自由格式文本好扫得多。

占据大多数 bug 的失败模式

当你排除了「服务器根本没跑起来」之后,几乎所有剩余的 MCP bug 都能归到下面这几类里。

服务器启动即崩

Host 拉起进程,进程立刻退出,服务器显示为失败。原因几乎总能在日志里看到:一个未处理的异常、缺失的依赖、或者入口文件里的语法错误。修复办法是在终端里运行你配置里那条完全相同的命令:

node /absolute/path/to/server.js

如果它在这里非零退出,在 Host 里也会非零退出。先在独立环境修好。

连接打开了但没有工具出现

服务器活着,但 Host 显示零个工具。两个常见嫌疑:服务器从没注册过工具(你忘了在 server.connect(...) 之前调用 server.tool(...)),或者服务器说的是 Host 听不懂的协议版本。Inspector 会立刻告诉你答案——如果它列出了工具,注册代码就是好的,问题在版本协商。如果你怀疑不匹配,钉死 SDK 版本并查一下 Host 支持的协议版本。

工具出现了但什么也不返回

模型决定调用某个工具,却拿到空响应或错误响应。这时工具处理器本身是头号嫌疑。在每个处理器开头打印收到的参数——你会惊讶地发现,schema 校验通过了,但参数形状并不是处理器假设的那个,这种情况有多常见。把错误以规范的 MCP content 形式返回,而不是抛异常:

server.tool("get_user", { id: z.string() }, async ({ id }) => {
  const user = await db.findUser(id);
  if (!user) {
    return {
      isError: true,
      content: [{ type: "text", text: `No user with id ${id}` }],
    };
  }
  return { content: [{ type: "text", text: JSON.stringify(user) }] };
});

返回 isError: true 以结构化方式告诉模型这次调用失败了,让模型能优雅恢复。相比之下,抛出未处理异常通常只会以一句泛泛的「工具失败」呈现给模型,没有任何细节。

stderr 泄漏进协议

这是最微妙的一个。在 stdio 传输上,stdout 是协议通道,stderr 是日志通道。如果你的服务器往 console.log(写到 stdout)而不是 console.error 写了任何东西,这些字节就会污染 JSON-RPC 流,Host 看到的是格式错乱的消息。症状是间歇性的协议错误,而你一去掉日志它就消失。经验法则:在 stdio 服务器里,每条诊断都走 console.error,绝不走 console.log。HTTP 服务器则没有这个约束。

权限和提示

某些 Host 会在工具第一次被调用时弹出权限提示。如果你拒绝了,或者它在没法展示的上下文里触发(无头 CI 运行、后台 Agent),工具就什么也不做。在 Claude Code 里,检查你当前会话的权限模式,必要时显式授予工具。在自动化环境里,在配置里预先批准工具,这样就不需要交互式提示。

一套行之有效的调试流程

当服务器行为异常时,从最简单的可复现场景向外排查:

  1. 独立运行服务器命令。 把配置里的 commandargs 完整粘贴到终端。它能启动并保活吗?
  2. 用 Inspector 驱动它。 连上 Inspector,用 Host 当初用的同样参数调用失败的工具。返回对吗?
  3. 查看 Host 日志。 在 Host 里复现故障,读 mcp-server-<名字>.log。服务器打印了什么?
  4. 核实环境。 在服务器启动期间打印 process.env,跟你预期的对比。缺失 token 是「终端里能跑、Host 里失败」最常见的原因。
  5. 收窄协议。 如果你怀疑是版本或能力不匹配,把 Inspector 报告的与 Host 看到的做对比。

这个顺序很重要。如果服务器根本没启动,却跳过这一步直接去读 Host 日志,是浪费时间。

生产环境注意事项

把 MCP 服务器从笔记本搬到共享环境,调试故事会变样。

传输从 stdio 换成 HTTP。 生产里你通常把服务器作为远端 HTTP+SSE 或 streamable-HTTP 端点运行,而不是子进程。这意味着你失去了按进程的日志文件,却多了网络故障、超时和认证要操心。从第一天起就加上健康检查和结构化日志。

并发。 本地 stdio 服务器服务一个用户;远端 HTTP 服务器可能服务很多。确保你的处理器是无状态的,或者任何共享状态都受到保护。数据库连接池、内存缓存、限流器,都得在并发访问下安全。

认证和授权。 远端服务器需要先认证 Host 才能信任它的请求,它暴露的工具也得尊重按用户的权限。别发布一个让任何调用方都能跑任何工具的远端 MCP 服务器——这相当于在生产环境里把数据库写用户留在配置里。

可观测性。 规模化时,记录每一次工具调用:工具名、参数(脱敏后的)、延迟、结果。这是当服务器被许多会话共享时,唯一能回答「今天的 Agent 怎么这么慢?」的办法。

版本管理。 显式钉死服务器和 SDK 版本。MCP 还在成熟中,一个传递依赖的升级可能悄悄改变协议行为。把 @modelcontextprotocol/sdk 的版本当成数据库驱动版本一样对待——有意识地升级,而不是不小心升级。

常见问题

什么是 MCP Inspector,什么时候该用它?

MCP Inspector 是一个官方浏览器 UI,中间没有 Host,直接和服务器说 MCP 协议。把它作为调试的第一步:对服务器命令启动它,你就能列出工具、用带类型的参数调用它们、查看原始的 JSON-RPC 流量。如果某个工具在 Inspector 里能用、在 Host 里失败,问题在 Host 配置或环境,不在你的服务器。

为什么我的 MCP 工具出现了,却什么有用信息也不返回?

服务器连上了也注册了工具,但工具处理器本身失败了。在每个处理器开头打印收到的参数,确认你代码假设的形状和模型实际发送的一致,并把错误以 isError: true 的结构化 MCP content 返回,而不是抛异常。未处理的异常通常只会以一句没有细节的泛泛失败呈现给模型。

Claude Desktop 把 MCP 服务器日志写在哪里?

macOS 上,Claude Desktop 按服务器各写一个日志文件,路径是 ~/Library/Logs/Claude/mcp-server-<名字>.log,里面包含服务器写到 stdout 和 stderr 的所有内容,交织着 Host 自己的协议消息。Windows 上对应的是 %USERPROFILE%\AppData\Roaming\Claude\logs\。复现 bug 时实时跟踪这个文件。

我能用 console.log 调试 stdio MCP 服务器吗?

不能——在 stdio 传输上,stdout 是 JSON-RPC 协议通道。你写到 console.log 的任何东西都会污染协议流,导致间歇性的「消息格式错乱」错误。把每条诊断都改走 console.error(stderr),那才是指定的日志通道。HTTP 服务器则不受这个约束。

官方参考资料

本文基于截至 2026 年 7 月的公开信息,相关 API 可能演进。