
mcpsnoop v0.13.0
Wireshark for MCP。一个透明代理,实时在终端中显示AI客户端与MCP服务器之间的每一次实际工具调用。
面向 MCP 的 Wireshark。 一个透明代理,可在终端中实时显示你的 AI 客户端与 MCP 服务器之间的每一次真实工具调用。
问题所在
官方 MCP Inspector 以自身客户端身份连接,因此它永远看不到你的客户端(Cursor、Claude Code、Codex)实际发送给服务器的内容。而且,任何等待请求到达的工具都无法显示模型从未发起过的调用,或使用了错误参数的调用。当工具被静默跳过、能力不匹配,或调用只是挂起时,你只能翻查日志并不断猜测。
mcpsnoop 取而代之,置身于真实数据路径之中。 用它包装你的服务器命令,即可在真实客户端与服务器通信时,实时观察每一个 JSON-RPC 帧。
在 CI 中使用
本页面同时也是 mcpsnoop GitHub Action 的列表页,因此这里涵盖了其全部内容。它会检查捕获的会话,将每一条发现作为代码扫描警报提交,并根据你所设定的门禁条件使任务失败。```yaml permissions: security-events: write contents: read
steps:
- uses: kerlenton/[email protected] with: session: artifacts/session.jsonl
固定你想要的版本。最新版本在
[发布页面](https://github.com/kerlenton/mcpsnoop/releases)上。所有输入、
退出码的含义,以及如何在不使用该 action 的情况下进行配置,都记录在
下方的 [GitHub Action](#the-github-action) 部分。
## 快速开始
无需任何配置,立即查看效果。```bash
mcpsnoop demo
要实际使用它,请将你的服务器包装在客户端的 MCP 配置中。```json { "mcpServers": { "my-server": { "command": "mcpsnoop", "args": ["--", "node", "build/index.js"] } } }
`--` 之后的内容就是通常启动服务器的命令。将其替换为你已经在使用的命令,例如 `python server.py`、`npx -y @scope/server`,或编译后的二进制文件。
在 Claude Desktop 上,你不必手动进行该编辑。```bash
mcpsnoop wrap my-server # route my-server through mcpsnoop
mcpsnoop unwrap my-server # put it back
wrap 会找到 claude_desktop_config.json,首次运行时将其复制为
claude_desktop_config.json.mcpsnoop.bak,并且只重写那一个
服务器的条目,因此你的格式和其他所有服务器都不会被改动。
在重写后的条目内部,键会按字母顺序排列回来。unwrap
会恢复该文件,并在没有任何服务器仍处于包装状态时删除备份。
执行任一操作后请重启 Claude Desktop,因为 MCP 服务器只在
启动时启动一次。
然后像往常一样使用你的客户端并打开 UI。```bash mcpsnoop
无需记住任何标志、套接字路径或启动顺序。垫片与界面会自动找到彼此,界面会从磁盘回填过往会话。
对于可流式 HTTP 服务器,可将 mcpsnoop 作为反向代理运行。```bash
mcpsnoop http --target http://localhost:3000/mcp --listen :7000
每条响应的 HTTP 状态都会显示在流中,因此即使响应本身不携带任何 JSON-RPC 消息,它仍然是一个可见的帧,而不是什么都没有:401 质询、因 Origin 被拒绝而返回的 403、确认通知的 202,以及目标完全无法访问时的 502。401 的 WWW-Authenticate 头会原样保留并显示在检查器中,因为它指明了认证方案以及下一步要访问的资源元数据。在 TUI 中可使用 status:401 按状态过滤,或使用 status:err 按任何失败过滤。4xx 或 5xx 均计为错误,因此默认的 mcpsnoop check 运行会因这些状态而失败。
没有自己的服务器?可针对已发布的测试服务器实际试用,由你自己的客户端驱动。若要在会话结束后进行检查,请参阅从日志中回顾过往会话。
配置文件
如果你在项目中复用相同的 shim 标志,可将它们放入当前工作目录下的 .mcpsnoop.toml 文件中。```toml
label = "filesystem"
trace-file = "trace.jsonl"
redact-secrets = true
redact-key = "token,authorization"
redact-value = "sk-[A-Za-z0-9]+"
redact-path = "$.params.arguments.password"
no-trace = false
在单独的行上重复 `redact-key`、`redact-value` 和 `redact-path`,以添加多个同类项。
这些就是它支持的全部键。
该文件只在当前工作目录中查找,不会在父目录中查找。
显式的命令行标志会覆盖配置文件中的值。
## 命令
| 命令 | 作用 |
|---|---|
| `mcpsnoop -- <server>` | 将 stdio 服务器包装为透明垫片 |
| `mcpsnoop` | 打开实时 TUI |
| `mcpsnoop http --target <url>` | 代理流式 HTTP 服务器 |
| `mcpsnoop export` | 将会话渲染为 json、html、text、har 或 otlp |
| `mcpsnoop check` | 在错误、无效帧、警告、路由不匹配、挂起调用、延迟结果或延迟预算上使 CI 失败 |
| `mcpsnoop baseline` | 检查、接受或重置受信任的工具定义 |
| `mcpsnoop diff` | 比较两次捕获会话中的工具和调用 |
| `mcpsnoop open` | 在 TUI 中打开已保存的会话 |
| `mcpsnoop inventory` | 列出本机上所有通过 mcpsnoop 运行过的服务器 |
| `mcpsnoop stats` | 将每个存储的捕获折叠为每个服务器和工具一行 |
| `mcpsnoop prune` | 删除早于截止时间的已保存会话日志 |
| `mcpsnoop wrap <server>` | 将 Claude Desktop 的一个服务器路由到 mcpsnoop |
| `mcpsnoop unwrap <server>` | 将该服务器的条目恢复原样 |
| `mcpsnoop remote <user@host>` | 打印 SSH 隧道命令 |
| `mcpsnoop demo` | 播放脚本化会话 |
运行 `mcpsnoop help` 查看完整列表,或运行 `mcpsnoop help <command>` 查看某个命令的标志。
## 对比
| | MCP Inspector | mcpsnoop |
|---|:---:|:---:|
| 能看到你真实的客户端和服务器流量 | 否 | 是 |
| 标记挂起调用和流错误 | 否 | 是 |
| 标记污染流的杂散输出 | 否 | 是 |
| 标记格式错误的 JSON-RPC 帧 | 否 | 是 |
| 在批准后检测工具定义漂移 | 否 | 是 |
| 交互式终端 UI | 否 | 是 |
| 零配置,无标志或顺序要求 | 否 | 是 |
| 能力检查器 | 部分 | 是 |
| 重放捕获的调用 | 否 | 是,支持 stdio 和 HTTP |
| 会话导出(json / html / text / otlp) | 否 | 是 |
| 单一二进制,无运行时依赖 | 否 | 是 |
## 安装
### npm
无需 Go 工具链。大多数 MCP 服务器是用 Node 或 Python 编写的,因此这是最快的入门方式。```bash
npx mcpsnoop -- node build/index.js
npm 包本身不包含任何代码。六个平台包各自携带一个构建版本,npm 会安装与你机器匹配的那一个,因此安装时无需下载任何内容,也无需在代理中解除封锁。若想保留它而不是每次运行时都重新获取,可使用 npm i -g mcpsnoop。
Go```bash
go install github.com/kerlenton/mcpsnoop/cmd/mcpsnoop@latest
### Homebrew```bash
brew install mcpsnoop
每个平台的预编译二进制文件都在 Releases 页面上。
Shell 补全
mcpsnoop 附带 bash、zsh、fish 和 PowerShell 的补全。运行
mcpsnoop completion <shell> --help 查看设置步骤,其中涵盖启用
补全以及适用于你操作系统的安装路径。
工作原理
mcpsnoop 在一个二进制文件中扮演两个角色。mcpsnoop -- <server> 是你的客户端启动的透明
垫片,逐字节转发数据,同时将每个帧的副本发送到中枢。不带参数的 mcpsnoop 就是该中枢及其实时 TUI。它们
通过一个众所周知的套接字和磁盘日志配对,因此两者都不需要先启动。
中枢默认加载最新的 100 个已保存会话,在保持启动工作
有界的同时不删除较旧的跟踪记录。使用 mcpsnoop --history-limit N 选择
其他限制,或使用 mcpsnoop --history-limit 0 加载完整历史记录。较旧的
会话仍可通过 mcpsnoop open <session-id> 和
mcpsnoop export <session-id> 访问。
历史记录限制约束了加载的会话数量。在会话内部,实时 TUI 受到双重约束,因为一个持续监视话痨服务器的中枢否则会不断增长直到被终止。它最多保留 64 MiB 的帧体,最先释放 最旧的,并且最多保留 200,000 个帧,超过该数量后完全丢弃最旧的。 第一个约束是大负载捕获会遇到的情况,第二个约束则是 长串小通知流会遇到的情况。
这两个约束都不会改变答案。帧体已被释放的帧会保留其行、
其判定及其在时间线中的位置,其检查器会显示帧体已消失
而不是显示空帧。被完全丢弃的帧会先将其工具调用的统计信息
计入运行总计,因此工具摘要以及服务器在上下文中消耗你的内容
会描述会话所做的每一次调用,而不仅仅是最近的调用。流页脚显示磁盘上还有多少较旧的帧
,而 r 会拒绝一个其参数不再持有的帧,而不是
重放其他内容。
mcpsnoop open <session-id> 读取日志并完整保留所有内容,而从
TUI 导出也会读取日志,因此两者都不受约束。check、export 和
diff 有意构建一个无界存储,因为一个在大型捕获上少报的闸门
比一个使用内存的闸门更糟糕。
历史记录限制约束了加载的内容。mcpsnoop prune 约束了保留的内容。
它会删除早于截止时间的已保存会话日志,并且绝不会自行运行。```bash
mcpsnoop prune --older-than 30d --dry-run # list what would go, remove nothing
mcpsnoop prune --older-than 30d # delete after confirming
mcpsnoop prune --older-than 72h --yes # skip the prompt in a script
`--older-than` 是必需的(没有默认值会删除任何内容),并且接受像 `30d` 这样的天数计数或像 `72h` 这样的 Go 持续时间。工具基线保持不变,因为基线以服务器标签而非会话为键。
由于它位于实际管道中,而不是像 Inspector 那样位于旁边,因此它能看到您的真实客户端和服务器之间所说的确切内容,无论服务器是用什么语言编写的。
## 按键绑定
| 按键 | 操作 | | 按键 | 操作 |
|---|---|---|---|---|
| `enter` | 检查 / 深入 | | `/` | 过滤 |
| `esc` | 返回 | | `:` | 命令 |
| `j` / `k` | 移动 | | `r` / `R` | 重放 / 编辑并重放 |
| `g` / `G` | 顶部 / 底部 | | `c` | 能力 |
| `ctrl-f` / `ctrl-b` | 翻页 | | `s` | 工具摘要 |
| `p` | 暂停 | | `y` | 复制 |
| `shift`+`<key>` | 按列排序 | | `e` | 导出 |
| `ctrl-d` | 删除会话 | | `f` | 跟随 |
| `?` | 帮助 | | | |
在应用中按 `?` 可查看完整列表。
## 过滤流
在会话中按 `/`,并组合以空格分隔的标记,采用 AND 逻辑。纯文本匹配方法、工具、ID 和负载。
| 标记 | 过滤依据 | 示例 |
|---|---|---|
| `tool:` | 工具名称 | `tool:search` |
| `method:` | JSON-RPC 方法 | `method:tools/call` |
| `id:` | 请求 ID,以及任何延续它的重试 | `id:7` |
| `task:` | 任务 ID | `task:01J...` |
| `dir:` | 方向(`c2s`、`s2c`) | `dir:s2c` |
| `kind:` | 帧类型(`req`、`resp`、`notify`、`stderr`、`invalid`) | `kind:invalid` |
| `status:` | 调用结果(`ok`、`error`、`cancel`、`late`、`cancelled`、`pending`、`bad`、`warn`、`mismatch`,或像 `401` 这样的 HTTP 状态) | `status:error` |