本教程教你用官方调试工具 MCP Inspector,把"写一个 MCP 工具"和"在 Claude Code 里跑通它"之间的黑箱彻底打开。学完后你能:独立启动 Inspector 连接任意 MCP 服务器、在可视化界面里逐个调用工具并观察通知流、读懂 stderr 与日志通知的区别、导出配置一键接入 Claude Code 验证。适合已经写过或准备写第一个 MCP 服务器的开发者——尤其是那些"服务器接进 Claude Code 后莫名报错、却完全不知道哪一步出问题"的人。
fastmcp,或 TypeScript 的 @modelcontextprotocol/sdk)。本教程用 Python 演示,但方法与语言无关很多人写完 MCP 服务器后,直接把它塞进 claude_desktop_config.json 或 Claude Code 的 MCP 配置里,然后在聊天框里问:"帮我调用一下那个工具"。如果没反应,就陷入了盲调试——你既看不到握手是否成功,也看不到工具列表是否被识别,更看不到调用时传了什么参数、服务端报了什么错。
官方文档对这一点的态度非常明确,把 Inspector 列为调试工具体系的第一项,原话是:"This should be your first stop"(这应该是你的第一站)。
MCP 的调试工具栈分三层,Inspector 占据最底层也最关键的位置:
| 层级 | 工具 | 解决什么问题 |
|---|---|---|
| 交互式测试 | MCP Inspector | 连服务器、调工具、看通知流——全程可视化 |
| 结构化日志 | Server logging(stderr / notifications/message) |
服务器自己暴露内部状态 |
| 客户端开发工具 | 客户端的日志面板(如 Claude Desktop 日志) | 看客户端这一侧发生了什么 |
关键认知是:在把服务器交给 Claude Code 之前,先用 Inspector 把它彻底跑通。一旦 Inspector 里能正常调用、返回正确、日志清晰,再接入客户端时 90% 的问题就不存在了。
Inspector 不是一个单体程序,而是两个进程协同工作。理解这个架构,后面所有的端口、认证、传输问题都能对号入座。
flowchart LR
subgraph Browser["浏览器"]
UI["MCPI Client UI<br/>React 应用"]
end
subgraph Local["本地进程"]
Proxy["MCPP Proxy Server<br/>Node.js :6277"]
Server["你的 MCP Server<br/>stdio 进程"]
end
UI -- "HTTP / WebSocket" --> Proxy
Proxy -- "stdio 管道<br/>(spawn 子进程)" --> Server
Server -. "stderr 日志 +<br/>notifications/message" .-> Proxy
Proxy -. "通知流转发" .-> UI
style UI fill:#e1f5ff,stroke:#0288d1
style Proxy fill:#fff3e0,stroke:#ef6c00
style Server fill:#e8f5e9,stroke:#388e3c
两个组件各司其职:
http://localhost:6274。你在里面点按钮、填参数、看返回值。6277。它同时是"客户端"(连你的 MCP 服务器)和"HTTP 服务器"(给浏览器 UI 提供数据),是浏览器和服务器之间的协议桥。小知识:
6274和6277这两个端口不是随便选的,它们来自手机的 T9 九宫格键盘——6274对应字母MCPI,6277对应MCPP,是个好记的助记符。
注意:这里的 Proxy 不是抓包用的网络代理,它不会拦截流量,它的作用是让"只能讲 stdio 的服务器"也能被"只会发 HTTP 的浏览器"操控。
# 只启动 Inspector UI,稍后在界面里手动填服务器命令
npx @modelcontextprotocol/inspector
浏览器会自动打开 http://localhost:6274。如果你要直接调试一个本地的 MCP 服务器,把启动命令追加在后面:
# 调试一个构建产物为 build/index.js 的 TS 服务器
npx @modelcontextprotocol/inspector node build/index.js
# 调试一个 Python 服务器
npx @modelcontextprotocol/inspector python temp_tool.py
需要给服务器传环境变量或参数时,用 -e 和 --:
# 只传环境变量
npx @modelcontextprotocol/inspector -e API_KEY=sk-xxx -e DEBUG=true python temp_tool.py
# 同时传环境变量和参数,用 -- 分隔 Inspector 自己的标志
npx @modelcontextprotocol/inspector -e API_KEY=sk-xxx -- python temp_tool.py --port 8080
docker run --rm \
-p 127.0.0.1:6274:6274 \
-p 127.0.0.1:6277:6277 \
-e HOST=0.0.0.0 \
-e MCP_AUTO_OPEN_ENABLED=false \
ghcr.io/modelcontextprotocol/inspector:latest
注意 127.0.0.1: 前缀——这是有意只绑定本地回环,不暴露到局域网(见后文安全章节)。
如果默认端口被占用:
CLIENT_PORT=8080 SERVER_PORT=9000 npx @modelcontextprotocol/inspector python temp_tool.py
预期结果:终端打印一行 🔑 Session token: <一长串十六进制>,并给出一个带 token 的 URL,浏览器自动打开。这就是你的调试入口。
光看文档没用,我们来调试一个真实的小项目。这个服务器做温度换算,里面故意埋了一个常见 bug——空列表除零。
# temp_tool.py —— 温度换算 MCP 服务器(故意留了一个 bug)
import sys
from fastmcp import FastMCP
mcp = FastMCP("temp-converter")
@mcp.tool()
def celsius_to_fahrenheit(celsius: float) -> str:
"""把摄氏度换算成华氏度"""
# 关键:调试日志写到 stderr,而不是 stdout
print(f"[debug] 收到输入 celsius={celsius}", file=sys.stderr)
fahrenheit = celsius * 9 / 5 + 32
return f"{celsius}°C = {fahrenheit:.1f}°F"
@mcp.tool()
def average_temps(temps: list[float]) -> str:
"""计算多个温度的平均值"""
total = sum(temps)
avg = total / len(temps) # bug:temps 为空列表时会抛 ZeroDivisionError
return f"平均温度 {avg:.1f}°C"
if __name__ == "__main__":
mcp.run(transport="stdio")
npx @modelcontextprotocol/inspector python temp_tool.py
浏览器打开后,左侧已经自动填好了 Command=python、Args=temp_tool.py。点击 Connect,Inspector 通过 Proxy 拉起你的服务器进程并完成 MCP 握手。
连接成功后,顶部会看到三个标签页,对应 MCP 的三原语:
celsius_to_fahrenheit 和 average_temps。如果这里为空,说明服务器没正确注册工具——这是最常见的"接进去没反应"的根因。点击 celsius_to_fahrenheit,右侧弹出参数表单(由 JSON Schema 自动生成),填入 celsius: 25,点 Run Tool:
预期返回:25°C = 77.0°F
同时,下方的 Notifications 面板会刷出一条 [debug] 收到输入 celsius=25——这就是你那条 print(..., file=sys.stderr) 被捕获的证据。
现在调用 average_temps,把 temps 留空(或填 []),点 Run。UI 会直接展示一个错误响应,而 Notification 面板里能看到服务端的 traceback。这就是 Inspector 的核心价值:在接入 Claude Code 之前,你已经定位到了"空列表除零"这个 bug,而不是等到用户在聊天里踩坑。
修复很简单:
if not temps:
return "错误:温度列表不能为空"
avg = total / len(temps)
调试 MCP 服务器,本质是"让服务器说话"。但怎么说、往哪说有严格规矩,踩错了会让整个协议崩溃。
MCP 的 stdio 传输,把标准输出(stdout)整个当成了协议消息通道。这意味着:
⚠️ 本地 MCP 服务器绝不能往 stdout 打印任何日志,否则会污染协议、导致客户端解析失败、握手直接断开。
这是新手最常犯的错误。一个无辜的 print("server started")(默认走 stdout)就足以让 Claude Code 报"服务器未响应"。正确做法是写 stderr:
# 错误:会破坏协议
print("server started")
# 正确:stderr 是安全的
print("server started", file=sys.stderr)
import sys # 别忘了导入
官方文档对此专门用黄色警告框强调。记住这条规则,能帮你省掉几个小时的盲调。
notifications/message除了 stderr,MCP 还定义了一条正式的日志通知通道,适用于所有传输方式(不只 stdio):
@mcp.tool()
async def my_tool(ctx) -> str:
await ctx.session.send_log_message(
level="info",
data="开始处理请求",
)
return "done"
这条消息会作为 notifications/message 推送给客户端(包括 Inspector),和 stderr 日志的区别在于:
| 维度 | stderr 日志 | notifications/message |
|---|---|---|
| 适用传输 | 仅 stdio(被客户端捕获) | 所有传输 |
| 语义 | 非正式调试输出 | 协议正式日志 |
| 日志级别 | 无结构 | RFC 5424 八级(debug→emergency) |
| 客户端可调级别 | 否 | 是,运行时用 logging/setLevel |
MCP 定义了八个 RFC 5424 严重级别(debug、info、notice、warning、error、critical、alert、emergency)。客户端可以在运行时通过 logging/setLevel 请求动态调整最低级别——这意味着用户能在不重启服务器的前提下,把日志从"只看 error"切换到"全量 debug"。Inspector 的 UI 里就有这个级别开关,调高后能看到服务器内部每一步的细粒度通知。
实战建议:stderr 用于开发期的临时调试(最快),notifications/message 用于生产期的正式日志(结构化、可被任何客户端消费)。
Inspector 真正的设计巧思在于——它不只是个调试器,还是接入客户端的跳板。调通之后,你不用手写 JSON 配置。
Inspector UI 里有两个导出按钮(配置好服务器后出现):
mcp.json。mcp.json,含 default-server。stdio 服务器导出长这样:
{
"mcpServers": {
"default-server": {
"command": "python",
"args": ["temp_tool.py", "--debug"],
"env": {
"API_KEY": "your-api-key",
"DEBUG": "true"
}
}
}
}
把这段粘进 Claude Code 的项目配置(.mcp.json)或用户配置后,在 Claude Code 里验证:
# 查看所有 MCP 服务器连接状态
/mcp
/mcp 会列出已连接的服务器及其工具数。如果你的 temp-converter 显示 connected 且工具数为 2,闭环就完成了。
整个开发闭环如下:
flowchart TD
A["写/改 MCP 服务器代码"] --> B["npx inspector 启动调试"]
B --> C["Inspector UI 测试工具"]
C --> D{"调用成功?"}
D -- "失败/有bug" --> E["看 Notification 与 stderr 日志"]
E --> A
D -- "成功" --> F["点 Server Entry 导出 JSON"]
F --> G["粘进 Claude Code 的 .mcp.json"]
G --> H["/mcp 验证已连接"]
H --> I["在对话里实际调用"]
I --> J{"符合预期?"}
J -- "否" --> A
J -- "是" --> K["✅ 闭环完成"]
style A fill:#e8f5e9,stroke:#388e3c
style K fill:#c8e6c9,stroke:#2e7d32
style D fill:#fff9c4,stroke:#f9a825
style J fill:#fff9c4,stroke:#f9a825
| 现象 | 根因 | 解决 |
|---|---|---|
| Inspector 连接后 Tools 为空 | 服务器没注册工具,或握手失败 | 看 Notification 面板的初始化日志;确认 @mcp.tool() 装饰器写对 |
| 客户端报"服务器未响应" | 日志打到 stdout 污染了协议 | 全部改用 print(..., file=sys.stderr) |
| 长任务超时被取消 | 默认请求超时 60 秒 | 调大 MCP_REQUEST_MAX_TOTAL_TIMEOUT,或配合进度通知 |
| 工具能跑但读不到文件 | 工作目录未定义(macOS 上是 /) |
配置里一律用绝对路径,别依赖 cwd |
| Inspector 报 401/认证失败 | 新版默认开启 session token 认证 | 用终端打印的 ?MCP_PROXY_AUTH_TOKEN=xxx 那个 URL 打开 |
| Streamable HTTP 服务器看不到 stderr | HTTP 传输不捕获 stderr | 改用 notifications/message 或 curl 抓包 |
超时相关的配置项(在 Inspector 的 Configuration 面板里改):
| 配置项 | 默认 | 含义 |
|---|---|---|
MCP_SERVER_REQUEST_TIMEOUT |
300000ms | 单次请求客户端超时 |
MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS |
true | 收到进度通知就重置超时 |
MCP_REQUEST_MAX_TOTAL_TIMEOUT |
60000ms | 配合进度通知的总超时上限 |
MCP_AUTO_OPEN_ENABLED |
true | 启动时是否自动开浏览器 |
Inspector 默认绑定了多层安全防护,理解它们能避免把开发机变成靶机:
1. 默认 localhost 绑定 + session token 认证。 新版 Inspector 启动时生成随机 token,必须带在 URL 里才能访问 UI。终端里那一行 🔑 Session token: 不是装饰,是认证凭据。
2. CVE-2025-49596——一个真实的 RCE 漏洞。 安全公司 Oligo 曾披露 Inspector 存在远程代码执行漏洞:攻击者只要诱导你访问一个恶意网页(甚至恶意广告),就能通过浏览器反向打穿你的 Inspector Proxy,进而在你机器上执行任意命令。所以官方强烈警告:
🚨
DANGEROUSLY_OMIT_AUTH=true(关闭认证)极其危险——不只是暴露到公网时危险,即便只在本机,访问恶意网站也可能导致远程入侵。除非你完全理解风险,否则不要关闭它。
3. DNS 重绑定防护。 Inspector 校验请求的 Origin 头,默认只允许来自客户端端口(6274)的请求。需要放行额外来源时:
ALLOWED_ORIGINS=http://localhost:6274,http://localhost:8000 npm start
实战守则:永远别关认证、永远别 HOST=0.0.0.0 绑公网、别在生产机常驻 Inspector。它是开发期工具,用完即关。
https://modelcontextprotocol.io/docs/tools/debugging —— 本文日志章节的权威来源,含 stdio/HTTP 差异详解https://modelcontextprotocol.io/docs/tools/inspector —— 官方使用指南https://github.com/modelcontextprotocol/inspector —— 源码、架构图、最新配置项https://www.oligo.security/blog/critical-rce-vulnerability-in-anthropic-mcp-inspector-cve-2025-49596 —— RCE 漏洞技术细节https://modelcontextprotocol.io/specification/latest/server/utilities/logging —— 八级日志与 setLevel 的规范定义| 维度 | 评估 |
|---|---|
| 适合的训练营类型 | AI 应用开发营、MCP/智能体工具开发营、企业内部"自建工具"实操营 |
| 学员基础 | 已完成至少一个 MCP 服务器 Hello World,懂 Python/TS 基础 |
| 建议教学时长 | 1.5–2 小时(含动手调 bug 环节) |
| 最佳教学顺序 | 放在"造第一个 MCP 工具"之后、"接入客户端"之前,作为承上启下的调试课 |
| 核心收获 | 学员能独立排查"工具接进去没反应"类问题,建立"先 Inspector 后客户端"的工程习惯 |
| 可拓展 | 可与 Hooks、Skills 课程联动——把调通的工具再用 Skill 封装触发 |
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
