给你的 MCP 装一台 Wireshark / Wireshark for Your MCP
你现在运行 mcp-snoop 技能。上游工具 mcpsnoop(MIT · Go · pre-1.0)把自己塞在 AI 客户端和 MCP server 之间,透明代理 stdio 或 HTTP,让每一帧 JSON-RPC 都在你的终端里可见、可搜索、可 replay。这门课交付的不是一份文档,而是一次真实抓到的 MCP session 快照 —— 你自己客户端到自己 MCP server 的握手、tools 契约、每一次 tools/call 的往返,全部躺在 jsonl 里,能翻页、能过滤、能重放到隔离拷贝的 server 上复现问题。
前置
- 一台已装好并登录任一 AI IDE:Claude Code / Claude Desktop / Cursor / Codex CLI。它们的 LLM 走各自登录,本课不引入任何新 provider key。
- 安装路径三选一(不需要开发者账号):
go install github.com/kerlenton/mcpsnoop/cmd/mcpsnoop@latest(Go ≥ 1.22,把$GOBIN或$GOPATH/bin加进PATH)brew tap kerlenton/mcpsnoop && brew install mcpsnoop(macOS)- 到 https://github.com/kerlenton/mcpsnoop/releases 下载对应平台的预编译 tarball
- 如需从零跑一段 sample traffic:Node ≥ 20(用来
npx -y @modelcontextprotocol/server-everything起官方公开 sample MCP server;这台 server 是 MCP 官方专门写来调 client 的,工具在客户端里没有"原生等价物",模型必须真的走 MCP 才能调用到)。 - 已有自家 MCP server 的用户可跳过 sample。
装完先自检
mcpsnoop -version # 应打 mcpsnoop v0.x.y
mcpsnoop --help # 看 shim / TUI / http / demo 四种入口
心智模型(30 秒理解)
- shim 模式
mcpsnoop -- <server-command>:把它塞进客户端的 MCP server 配置里替代原来的command,它会 fork 真 server + 双向复制 stdio + 抓每一帧。 - TUI 模式
mcpsnoop(无参):一个 hub,显示所有 shim 实时上来的流量,支持/filter、enterinspect、ccapabilities、rreplay。 - http 模式
mcpsnoop http --target <url>:给 HTTP 传输 MCP server 做透明代理,同一套抓包体验。 - demo 模式
mcpsnoop demo:不需要客户端、不需要 server,播放一段脚本演示,用来判断 TUI 是否起得来。 - 持久化:shim 会把每次 session 落到
~/.local/state/mcpsnoop/sessions/<label>-<pid>.jsonl(可用XDG_STATE_HOME或MCPSNOOP_HOME覆盖,但别改 —— 默认路径是 shim 和 TUI 自动 rendezvous 的约定)。
工作流程 —— 抓你自己的 session
- 包 mcpsnoop 到客户端 MCP 配置。以 Claude Code 为例:把原来
改成claude mcp add my-server -- node build/index.js或直接改写claude mcp add my-server -- mcpsnoop -- node build/index.jsclaude_desktop_config.json/ Cursor 的mcp.json:其它 MCP server 先不要动,专治哪一台包哪一台,方便隔离。{ "mcpServers": { "my-server": { "command": "mcpsnoop", "args": ["--", "node", "build/index.js"] } } } - 在一个终端跑 TUI:
mcpsnoop。看到Waiting for MCP traffic…说明 hub 起来了。 - 重启 AI 客户端(Claude Code / Cursor 都是 session 开始时才加载 MCP server;不重启就抓不到 handshake)。TUI 顶部会立刻出现
my-server session+initialize+notifications/initialized+tools/list三行 handshake。 - 触发一次真实交互:在客户端里发一句会调到这个 server 的话。TUI 里应看到
tools/call请求 + 对应 response;耗时超过阈值的会打SLOW标。 - 调查:
/输入 filter:method:tools/call、tool:echo、status:slow、dir:s2c kind:req(server-initiated 请求,比如roots/list/sampling/createMessage)。enter展开任意帧,看 pretty-printed JSON;c打开 capabilities inspector,看客户端 ↔ server 协商出来的tools/prompts/resources/roots/sampling/elicitation各自是谁 declared / supported。r对某一帧触发 replay:mcpsnoop 会拉起 server 的隔离拷贝,把原帧重发一遍,让你在不打扰当前生产 session 的前提下复现问题。
- dump 证据:定位到
~/.local/state/mcpsnoop/sessions/(ls -lt取最新的.jsonl),另存到项目里,例如./mcpsnoop-session.jsonl。再用 asciinema 或 GIF 录制器录一段 20–40 秒 TUI 演示。
从零跑一段 sample session(没有自家 MCP server 时)
Claude Code 用户:
claude mcp add everything -- mcpsnoop -- npx -y @modelcontextprotocol/server-everything
启动 mcpsnoop(TUI)→ 新开 一个 claude 子会话 → 在里面说:
Use the everything MCP server: echo "hello", get-sum 40+2, then trigger-long-running-operation.
TUI 里会依次出现:initialize → notifications/initialized → tools/list(16 个工具)→ 3 条 tools/call(echo OK / get-sum OK / trigger-long-running-operation ~5s 被标 SLOW,中间伴随若干 notifications/progress)→ 期间可能还有 server→client 方向的 roots/list 请求。
如果客户端环境不好嵌套子会话,用官方 mcpsnoop demo 播放脚本演示 + 手动往 shim stdin 灌 JSON-RPC 帧也算真实抓包(帧是真实 stdio 交互,不是 mock):
python3 - <<'PY'
# 手动发六帧到 sample server,落到 ~/.local/state/mcpsnoop/sessions/*.jsonl
import json, subprocess
p = subprocess.Popen(["mcpsnoop","--label","server-everything","--",
"npx","-y","@modelcontextprotocol/server-everything"],
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
def send(m): p.stdin.write((json.dumps(m)+"\n").encode()); p.stdin.flush()
def recv(): return json.loads(p.stdout.readline())
send({"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2024-11-05",
"clientInfo":{"name":"driver","version":"0"},
"capabilities":{"roots":{"listChanged":True}}}}); recv()
send({"jsonrpc":"2.0","method":"notifications/initialized"})
send({"jsonrpc":"2.0","id":2,"method":"tools/list"}); recv()
send({"jsonrpc":"2.0","id":3,"method":"tools/call",
"params":{"name":"echo","arguments":{"message":"hello"}}}); recv()
send({"jsonrpc":"2.0","id":4,"method":"tools/call",
"params":{"name":"get-sum","arguments":{"a":40,"b":2}}}); recv()
send({"jsonrpc":"2.0","id":5,"method":"tools/call",
"params":{"name":"trigger-long-running-operation",
"arguments":{"duration":5,"steps":5}}})
# drain remaining frames (progress notifs + LRO result) until EOF
while p.stdout.readable():
line = p.stdout.readline()
if not line: break
p.stdin.close(); p.wait(timeout=5)
PY
生成诊断报告
抓完帧写一份 mcp-diag.md,四节:
- Handshake capabilities —— 从
initializerequest/response 里 dumpprotocolVersion和capabilities。列客户端和 server 各自 declared vs. supported,找出对不上的字段(例如客户端支持sampling但 server 没写,就意味着 server 不能主动求模型辅助)。 - Tools 契约 —— 从
tools/listresponse 里 dump 那个"不被调用"的工具的name / description / inputSchema。常见结论:description太模糊(改成动词开头,例:"Send a bilingual notification to Slack")、inputSchema.required少列、schema 里enum里带非法字符。 - 一次代表性的
tools/call—— 贴完整 request 和 response。如果 response 是isError:true,附上 server stderr 那几行;如果 hang PENDING,用rreplay 一次,附上 replay 的观测。 - 结论与修复建议 —— 一两句话说清"客户端根本没决定调 / 客户端决定调但 schema 报错 / 调对了但 server 端崩了",再给 1–2 条改动建议。
常见坑
- 客户端不 hot-reload MCP —— 改完
mcpServers后必须重启客户端;不重启 TUI 就抓不到 handshake。 Waiting for MCP traffic…一直不动 —— 你只启动了 TUI 但没重启客户端;或者客户端 spawn shim 时PATH里没 mcpsnoop(客户端环境可能不继承你的 shell PATH,用绝对路径/opt/homebrew/bin/mcpsnoop/~/go/bin/mcpsnoop)。- 多个 shim 抢同一个 label ——
-labelflag 显式区分(mcpsnoop -label my-server-canary -- ...)。 - 不要用
MCPSNOOP_HOME隔离 shim 和 TUI —— 两边都读同一份路径才能自动 rendezvous;上游 README 明说"Don't setMCPSNOOP_HOME"。 - 上游是 pre-1.0 —— TUI 键位、subcommand、
MCPSNOOP_HOME语义都可能改。以上游 README +docs/DEMO.md当前 HEAD 为准,mcpsnoop --help是唯一权威。
产出物
~/.local/state/mcpsnoop/sessions/<label>-<pid>.jsonl—— 每一帧 JSON-RPC 的原始字节 + 时间戳 + 方向,可任意搜索、可 replay。./mcpsnoop-tui.cast/.gif—— 一段 20–40s TUI 演示。./mcp-diag.md—— 上面四节诊断报告。
学习完成后
告诉用户:
我已经学会了 mcp-snoop。把 mcpsnoop 塞在你客户端和 MCP server 之间,抓一段真实 session:handshake、tools/list、每一次 tools/call 的往返(含 SLOW 标记和 progress notifications),再挑代表性帧生成一份 mcp-diag.md,回答"为什么这个工具没被调 / 为什么这次调用卡住"这类问题。全程本地跑,不接任何 provider API key。