RSS

第 8 章 / 共 12 章

它没反应的时候:调试与故障定位

约 10 分钟 更新于

8.1 调试 MCP server 的难处:你看不见现场

普通 Web 服务出问题,你有请求日志、有响应码、有浏览器开发者工具。MCP server 出问题,你面对的往往是一句「这个 server 没有工具」或者对话里模型轻描淡写的一句「工具调用失败了」。

难处在于中间隔了太多层:宿主应用、它内部的 client、子进程、协议流、你的代码。任何一层断了,症状都长得差不多。

所以调试的第一原则是缩短链路:先不要在 Claude Code 里调,先用 Inspector 把 server 单独连起来。Inspector 直连你的 server,中间没有模型、没有宿主策略、没有权限系统。它工作,说明问题在宿主那边;它不工作,问题就在你的代码里。

第8章:它没反应的时候:调试与故障定位
图 8.1:五个常见症状各自对应一条最短的排查路径。找对路径比逐行读代码快得多。

8.2 Inspector:一个包,三种用法

MCP Inspector 是官方的调试客户端。要注意的是它在 2026-07-28 规范发布时出了 v2,是一套全新的代码库——网上大量中文教程写的还是 v1(只有一个网页界面、用 MCP_PROXY_AUTH_TOKEN 那套),照着抄会对不上。写这份教程时的版本是 2.5.0,要求 Node 22.19.0 以上。

一个包,三种客户端形态:

npx @modelcontextprotocol/inspector          # 网页界面(默认)
npx @modelcontextprotocol/inspector --cli    # 命令行,适合脚本与 CI
npx @modelcontextprotocol/inspector --tui    # 终端界面

连上你的本地 server:

npx @modelcontextprotocol/inspector uv run python server.py

或者用 SDK 自带的封装(等价,但会自动带上项目依赖):

uv run mcp dev server.py

连远程 server:

npx @modelcontextprotocol/inspector --server-url https://api.example.com/mcp --transport http

有一个坑官方专门强调过:模式旗标必须放在最前面,第一个不属于 Inspector 的参数之后,所有内容都会原样转发给你的 server。要给自己的 server 传参数,用 -- 分隔:

npx @modelcontextprotocol/inspector uv run python server.py -- --verbose

CLI 模式在自动化里很好用,比如把「工具列表没坏」写进 CI:

npx @modelcontextprotocol/inspector --cli uv run python server.py --method tools/list

8.3 各个标签页分别在看什么

网页界面里的标签不少,但排查时你只需要盯住其中三个。

标签什么时候出现你在这里看什么
Servers总是server 列表、连接状态、单个 server 的设置
Toolsserver 有 tools 能力浏览 schema、填参数、调用、看结果
Prompts / Resources对应能力存在列出、读取、预览
Protocol总是完整的 JSON-RPC 往返记录
Consolestdio serverserver 进程的 stderr
NetworkHTTP server原始 HTTP:状态码、头、body

加粗那两个是排查时的主力。Protocol 让你看见协议层到底发生了什么(第 3 章那些 JSON 就是从这里能看到的东西),Console 让你看见你的日志。注意 Network 和 Console 不会同时出现——一个是远程场景,一个是本地场景。

这几个监控类标签可以固定成右侧的常驻栏,一边点工具一边看协议流量,这是效率最高的调试姿势。

还有一个默认值陷阱要知道:Inspector 的 server 配置里有一项叫 Protocol Era,取值是 legacy / auto / modern,而默认是 legacy。也就是说,如果你想验证 2026-07-28 的行为(无握手、新的必填头、多轮往返),得手动把它切成 modern 或 auto,否则你测的是旧形态。

关于 token:网页界面背后的 Node 服务会启动子进程,所以接口是有鉴权的,token 每次启动随机生成,并且直接写在它打印出来的那条 URL 里。要打开那条完整 URL,不要凭记忆敲 localhost:6274——那样会因为没有 token 而连不上。想固定 token 用于脚本,设 MCP_INSPECTOR_API_TOKEN。

8.4 故障对照表

按症状查,每一行给出最可能的原因和最短的验证动作。

症状最可能的原因先做这一步
宿主里这个 server 一个工具都没有进程根本没起来(import 期就报错),常见于 @mcp.tool 忘了括号、依赖没装、路径不对在终端里手动把宿主配置里那条启动命令原样跑一遍,看报错
工具列表曾经有、现在没了有东西污染了 stdout(print、包装脚本的 echo)看 Console 标签或 server 的 stderr 日志;全局搜一遍 print(
Inspector 连不上打开的 URL 少了 token;或 Node 版本低于 22.19复制它启动时打印的那条完整 URL
Inspector 能调,宿主里不能调宿主传的环境变量不同、工作目录不同、用的是另一个 Python 环境在配置里改用绝对路径,并显式传 env
调用报错但看不到细节你抛的是普通异常,信息被 SDK 遮蔽了改抛 ToolError(见 6.4),或者去 stderr 看完整堆栈
调用很慢或超时每次调用都在重建连接/索引;或同步函数里做了长阻塞把准备工作挪进 lifespan(第 7 章);给工具设超时
返回内容被截断宿主对工具输出有上限在工具里主动分页并给出引导(见 6.6),不要指望宿主放宽
改了代码没生效宿主还连着旧进程重启宿主,或用它的重连命令

8.5 让日志真的帮到你

第 2.6 节讲过日志要走 stderr。这里补三条让日志真正有用的写法。

记录每次工具调用的入参摘要,但不要记录完整内容。

log.info("search_notes keyword=%r limit=%d", keyword[:50], limit)

截断是为了防止一次超长参数把日志刷爆,也是为了不把用户数据完整地写进磁盘。

密钥永远不进日志。 这条在第 11 章会再出现一次,因为它被违反的次数实在太多——有安全研究专门统计过,大量 MCP 环境把第三方长期 API key 明文存在本地文件里,权限还是所有用户可读。

出错时记录足够定位的上下文,但返回给模型的是另一句话。 第 6.4 节那个机制在这里体现得最清楚:

try:
    result = await upstream.fetch(query)
except UpstreamTimeout:
    log.exception("upstream timeout, query=%r", query)          # 给你自己看
    raise ToolError("上游服务超时,请稍后重试,或把 limit 调小。")   # 给模型看

8.6 练习与检查点