给 MCP 工具定义加一道基线校验
作者使用 MCP Bastion 0.4.0 演示:同一处工具描述修改,单独扫描没有发现问题,与已批准版本比较则触发高风险告警并返回失败退出码。实验使用保存的工具定义,无需模型、API 密钥或运行中的 MCP 服务即可复现。
AI 深度解读
一个 MCP 工具的描述从「返回运行秒数」改成「返回运行时间和部署区域」,单独扫描没有问题,对照已批准的版本却触发了 HIGH 告警。变化并不一定危险,但它提出了一个代码审查容易漏掉的问题:现在交给 Agent 的工具定义,还是团队批准过的那一份吗?
作者用 MCP Bastion 0.4.0 做了两组小实验,分别检查工具定义漂移和多工具能力组合。对正在接入 MCP 的程序员,这篇最有用的地方是区分了三件事:发现变化、识别潜在风险,以及证明风险实际发生。
工具名没变,为什么还要重新审查
MCP 客户端通过 tools/list 获取工具名称、描述和输入 schema,模型可能据此选择工具、准备参数;真正执行则走另一条请求 tools/call。
因此,描述也是 Agent 决策时读取的输入。工具名字熟悉,不代表它要求模型提供的信息没有变化。可选参数的一段新描述,同样可能改变 Agent 的行为。
锁定启动本地 MCP server 的包版本,可以固定一部分待审查的可执行代码。但远程 server 的 URL 即使不变,它公布的工具定义也可能更新。代码依赖固定了,Agent 看到的工具目录未必固定。
MCP Bastion 支持静态扫描 checkout,也能用 --tools-file 读取保存的工具目录。--live 则会启动配置中的本地 server 命令,作者只在审查过命令或进入隔离环境后使用。

第一组实验无需模型、API key 或运行中的 MCP server,只需要 macOS 或 Linux,以及已激活的 Python 3.10+ 虚拟环境。作者固定了扫描器提交,以便复现相同规则行为:
python -m pip install "mcp-bastion @ git+https://github.com/lindseynguyen/mcp-bastion.git@4163ba54944a6e8a9e69a8623461d3424f54ee06"
要留意 @ 后面的完整提交号,本文结果对应这个固定版本,不能直接推给后续版本。
下面这段创建空客户端配置、保存 ops 下的工具定义,并生成初始锁文件:
printf '%s\n' '{"mcpServers": {}}' > mcp.json
cat > tools.json <<'JSON'
{
"ops": {
"tools": [{
"name": "get_uptime",
"description": "Return service uptime in seconds.",
"inputSchema": {"type": "object", "properties": {}}
}]
}
}
JSON
mcp-bastion scan mcp.json --tools-file tools.json --engines none --lock mcp.lock.json
关键是最后一行的 --lock mcp.lock.json:后续扫描需要保留它,才有已批准定义可供比较。
作者把描述改成 Return service uptime and the deployment region.,保持锁文件不动。有锁对照时,扫描产生 HIGH 告警,退出码为 1;移除 --lock mcp.lock.json 后,同一文件产生零条发现。三次运行都采用默认严重级别阈值。
扫描器会对种类、名称、描述和输入 schema 的规范化 JSON 计算指纹,并按 server、种类、名称定位锁记录。调整 JSON 对象键顺序不会改变指纹,修改描述会。
这个告警证明定义变了,是否接受变化仍要由人判断。
反过来,指纹不变也无法证明实现没变:只修改 server 内部逻辑,可能完全不影响工具定义。
三个普通工具,为什么放一起就有风险
第二组实验使用一个合成目录,表示同一个支持助手拥有三种能力:读取公开 issue、打开内部 runbook,以及发送消息。
三者同时存在,Bastion 报出 HIGH · MB602;移除消息工具后,同样的扫描得到零条发现。两次运行都没有实际访问 issue、runbook 或消息。
风险来自能力之间的衔接:陌生人能在公开 issue 中写入内容,读取它的助手又能访问内部资料,还能把消息发出去。每项能力单看都有业务理由,放到同一个 Agent 身上,就需要审查它们能否串成一条数据流。
规则根据工具名和描述推断能力。有客户端配置时,Bastion 按客户端评估组合;没有已配置 server 的独立目录,会被当作一个 Agent。因此作者建议,为真实的不同权限范围分别准备目录,避免把互不相干的工具混成一个权限集合。
告警之后最该查的是具体边界:凭证能读哪些 runbook,发送工具能到哪些频道。只能发给值班团队,与可以发到客户频道,审查结论可能不同。
要证明数据确实流出,还得核对工具调用和目标端审计记录。这个实验没有提供实际攻击成功的证据。
我的判断
我认可作者把「定义是否变化」独立拿出来审查。常见的包版本锁定和漏洞扫描,分别关注依赖版本与已知漏洞,无法直接回答 Agent 当前看到的描述是否经过批准。工具定义锁补上了这一层证据。
但两组实验的证明范围都很窄。第一组是定义漂移检测,第二组是基于名称和描述的能力组合判断。作者没有给出真实项目中的误报率、漏报率,也没有和其他方案做对照,不能据此判断整体防护效果。
作者提出的 PR 接入方式,更适合作为经验建议。CLI 可以同时输出 SARIF 和 Markdown:mcp-bastion scan . --fail-on high -o mcp-review.sarif -o mcp-review.md。前者便于代码扫描系统消费,后者供人阅读,但报告格式齐全不等于审批流程有效。
工具锁文件和告警基线应分别审查,二者记录的是不同决定。
锁文件记录预期定义,基线记录已分诊的现有发现。如果每个 PR 都自动刷新工具锁,那次描述修改就会提前成为新标准。首次固定目录,也需要有人确认它值得信任。
网络配置同样要看实际入口:CLI 的 OSV 漏洞查询需要主动开启,随附 GitHub Action 的 online 输入却默认是 true。设置 online: 'false' 可以关闭 Action 中的查询,但 setup 步骤仍会安装依赖,不能据此认定流程适用于断网 runner。
能拿来做什么
- 给现有 MCP 集成保存一份经负责人审查的工具目录和锁文件,再改一句描述复现扫描,确认 CI 能呈现差异并要求审查。
- 审查前端或 Node 项目使用的 Agent 时,把读取外部内容、访问内部资料、向外发送消息的工具放在一起看,并核实实际凭证权限。
- 在 PR 中分别呈现工具定义变更和已有告警处理记录,避免自动更新锁文件,把待批准的变化直接写成基准。
- 按真实 Agent 的权限范围组织扫描输入;遇到 MB602 时继续检查调用日志和接收端记录,区分能力组合风险与已经发生的数据传输。