mcp-audit-tap 面向 stdio MCP 服务,以约百行、零依赖的 Python 代码记录通信日志,帮助检查第三方服务实际传递的内容。作者称演示仓库及命令已在发布前运行验证。
本文最初于 2026 年 10 月 20 日发布在 webofmike.com。发布前,演示仓库及其中的每一条命令都已实际运行验证。
10 月 19 日,一篇介绍“免费运行在 Cloudflare 上的 WhatsApp MCP server”的 Show HN 帖子,在不到 16 小时内获得了 124 分和 69 条评论。但这 69 条评论,没有一条回答最关键的问题:这东西到底发送了什么?大家争论的是免费套餐是否真的免费(一位评论者引用 README,指出扩大规模需要付费的 Cloudflare 套餐),运行它是否有被 WhatsApp 封号的风险(好几个人根据亲身经历给出了肯定答案),以及把端到端加密的消息解密后上传给 LLM 是否涉及隐私问题(确实涉及,尤其是对话中那些没有同意这样做的人)。
我开发 mcp-audit-tap,就是为了回答这个问题,而且适用于任何使用 stdio 的 MCP server,不只针对这一个。它只有大约 100 行 Python,没有依赖,给你的是日志,而不是观点。
同一天,HN 上另一篇提问“如果不运行自己的 LLM proxy,你怎么知道究竟发生了什么”的帖子,获得了 213 分和 154 条评论。排名第一的回复反对运行 proxy,主张直接使用编码 Agent 自身提供的 OpenTelemetry traces。两个讨论从相反的方向,绕着同一个缺口打转:一边是一个并非你编写的 server,要求访问你的私人信息;另一边是一个你没有完整接入观测能力的 client,要求你信任它。面对“你怎么知道究竟发生了什么”这个问题,两边诚实的答案都是:你不知道,除非有什么东西在监看通信链路。
MCP 很容易让人跳过这一步。server 的 README 描述的是它应该做什么。它的源码,如果你读过,描述的是它能够做什么。但这两者都无法告诉你:刚刚执行的那次请求里,Agent 实际传入了什么参数、server 实际发送了什么,以及 Agent 实际收到了什么响应。这是流量监听工具的工作。而 MCP 的 stdio 传输方式,也就是 server 和 client 通过 stdin、stdout 交换以换行分隔的 JSON-RPC 消息,让构建这样的工具变得简单得有些出乎意料。
audit_tap.py 会将真正的 MCP server 作为子进程启动。你的 client 与 tap 通信,而不是直接与 server 通信;tap 原样转发双方的每一行消息,同时检查消息内容:
MCP client <-- stdio --> audit_tap.py <-- stdio --> the real MCP server
|
v
audit.log (JSONL)
对于 tools/call 请求,日志会记录工具名称和参数。对应的结果通过 JSON-RPC id 匹配,再用一小组正则表达式进行扫描,并将匹配项写入日志。项目附带了一个玩具 server,用来模拟个人消息工具,从外部看,它的行为类似 WhatsApp MCP server。不加任何选项,让它通过 tap 运行,会产生以下日志:
{"ts": "...", "direction": "client->server", "event": "tools/call request", "tool": "read_latest_messages", "arguments": {}}
{"ts": "...", "direction": "server->client", "event": "tools/call result", "tool": "read_latest_messages", "flags": ["phone", "email"], "preview": "Jordan: Running late, call me at 555-867-5309 if plans change..."}
client 收到的响应,与没有 tap 时完全相同。没有任何内容被修改。变化在于,现在文件里多了一行记录,说明刚刚有一个电话号码和一个电子邮件地址从 server 发出,以及它们由哪个工具返回、发生在什么时候。
加上 --redact,同一次调用就会多产生第三行日志,响应内容也会发生变化:
{"ts": "...", "direction": "tap", "event": "redacted before forwarding", "tool": "read_latest_messages", "flags": ["phone", "email"]}
client 收到的文本现在会显示 call me at [REDACTED:phone],而不是真实号码。从审计工具到策略执行节点,差的就这么多:在一个 60 行的类中加入一个 if self.do_redact 分支。我没有先写这个分支。我先写了日志功能,运行起来,等日志告诉我确实有值得脱敏的内容后,才加入脱敏功能。
这个顺序比代码更重要。听到“某个工具正在泄露 PII”时,人们的本能反应是拿出一份屏蔽清单。但在查看真实流量之前写出的屏蔽清单,只是猜测,而 MCP 工具返回的结果足够多样,猜测总会漏掉一些东西。audit_tap.py 中的 PII_PATTERNS 就是一个普通的 dict,将名称映射到编译后的正则表达式。我发布时只带了两个匹配规则:电话号码和电子邮件地址,因为演示用的固定测试数据里就包含这两类信息。扩展它的踏实做法,是先让 tap 跑在你实际使用的 server 前面,阅读日志,再针对那些出乎意料的内容添加规则。
演示项目附带了一个玩具 server,让 README 中的操作可以独立跑通,但 tap 并不知道、也不关心自己包裹的是什么。如果你的 MCP client 配置目前直接启动 server:
{"command": "python3", "args": ["-m", "some_mcp_server"]}
将它改为启动 tap:
{"command": "python3", "args": ["audit_tap.py", "--log", "audit.log", "--", "python3", "-m", "some_mcp_server"]}
所有消息仍然会在两个方向上正常传递。唯一新增的东西,是 audit.log 会不断记录:当 Agent 调用这个 server 时,它实际上做了什么。你可以先阅读这些记录,再决定这个 server 是否值得你承担 HN 评论者争论的封号风险,或者是否值得围绕它建立一套真正的策略。
除了 Python 3.9+,不需要其他依赖。
git clone https://github.com/themsquared/mcp-audit-tap.git
cd mcp-audit-tap
python3 test_harness.py -- python3 demo_server.py
这会直接运行玩具 server,没有任何东西监看通信,让你先看到基准响应。然后,让同一次调用通过 tap:
python3 test_harness.py -- python3 audit_tap.py --log audit.log -- python3 demo_server.py
cat audit.log
再开启脱敏运行:
python3 test_harness.py -- python3 audit_tap.py --redact --log audit.log -- python3 demo_server.py
test_harness.py 是我写的一个最小 MCP client,让整套演示不需要安装 Claude Desktop 或其他真正的 Agent 宿主程序就能运行。它会发送 initialize、tools/list,以及一次 tools/call,足以让你在一分钟内看到 tap 从头到尾发挥作用。
演示中的两个规则,一个匹配美式电话号码格式,一个匹配电子邮件地址,刻意选择了最简单的情况。正则表达式匹配的是形式,不是含义。它不会标记“我母亲的娘家姓是 Alvarez”这样的消息,也不会标记一段解码后是照片的 base64 内容。它同样应付不了刻意规避的格式:没有分隔符的 5558675309,可以绕过针对 555-867-5309 编写的规则;如果 server 有意规避,还可以把一个号码拆到同一结果的两个内容块里,躲过简单的扫描器。
这些都不是跳过 tap 的理由,而是应该阅读 audit.log、不能盲信一份干净报告的理由。日志会保留流经内容的完整预览,无论有没有被标记,人或其他进程都可以据此检查手写规则漏掉的东西。没有标记,不代表没有内容漏过去;它只代表没有内容匹配目前写出的规则。tap 保证提供的,是通信链路上实际传输内容的记录,而这正是 10 月 19 日那两个 HN 讨论串都没有的东西。
tap 不是 gateway。它没有身份认证,没有速率限制,也没有任何类似策略语言的东西,这是有意为之。agentgateway 的 MCP guardrails 运行在真正的 gateway 内部,能够在协议层看到解析后的 tools/call 请求和结果,并使用 CEL 表达式,在工具执行之前直接拒绝调用,而不是依靠存放正则表达式的 Python dict。如果你需要在流量层使用真正的规则引擎进行模式拦截,《WAF for LLM and MCP traffic》介绍了如何将 Coraza 和 OWASP CRS 放在 prompt 与工具参数之前。
但这些都不是起点。起点是弄清楚:一个并非你编写的 server,实际正在发送什么。而那个围绕“是否该信任免费的 WhatsApp 集成”展开、共有 69 条评论的 HN 讨论串,始终没能做到这一点。代码位于 github.com/themsquared/mcp-audit-tap。
它是一个位于 MCP client 和 server 之间、通过 stdio 工作的进程,原样转发每一条 JSON-RPC 消息,同时为每次 tools/call 请求和结果写入结构化日志。它默认只观察;除非你明确开启脱敏,否则不会阻止或修改流量。它的目的,是在做出任何策略决定之前,先让实际情况变得可见。
让它运行在 tap 后面,而不是直接运行。mcp-audit-tap 会将真正的 server 作为子进程启动,在它和 client 之间转发 stdin/stdout,并在每次工具调用及其结果经过时,将内容记录到 JSONL 文件中,其中也包含电话号码、电子邮件地址等模式的匹配标记。
相关 HN 讨论提出了切实的问题:多位评论者报告,使用类似的 WhatsApp 桥接工具曾导致账号被封;而且,这个工具会在将内容上传给 LLM 之前,解密端到端加密的内容。加入 audit tap,并不能消除这两种风险。tap 增加的是一份记录,准确说明哪些内容离开了你的机器,让你不必只凭 README 的说法来判断。
audit tap 用于观察单个 server 的流量,也可以选择进行脱敏,但不提供身份认证、路由或策略语言。像 agentgateway 这样的 MCP gateway 位于多个 server 前面,对调用方进行身份认证,并且可以根据 CEL 规则,在工具运行之前直接拒绝 tools/call。通过 tap,你可以先弄清楚 server 实际发送了什么,再决定是否需要在此基础上加入 gateway 的强制执行能力。
本文的规范版本位于 webofmike.com/mcp-audit-tap/,可供机器读取的 Markdown 版本位于 webofmike.com/mcp-audit-tap/index.md。
如需进一步采取行动,可以考虑屏蔽此人和/或举报滥用行为。