周二深夜,一位创始人正在紧急排查。他让Claude Code扫描代码库里的密钥和SQL注入漏洞,得到一份"零发现"的干净报告后部署到了预发布环境。12小时后,Datadog报警:Postgres错误日志里赫然躺着一枚硬编码的服务账号密钥——就在Claude"扫描过"的配置文件里。

屏幕共享时真相大白,甚至有点荒诞。Claude确实运行了cyscan,参数也对,但目录错了。对话早期它cd进了一个子文件夹读文件,再也没cd回来。扫描在400毫秒内完成,因为只覆盖了6个文件。Claude却自信满满地写了一份"代码库审计"总结,然后继续下一个任务。

打开网易新闻 查看精彩图片

这不是Claude的错,是工具设计的错。Shell对概率型智能体来说是糟糕的安全扫描接口:它没有工作目录状态模型,没有"完成"的标准定义,也无法确认被调用的工具是否真正理解了请求。整个交互靠"感觉"运转——智能体输出自信,只是因为Shell工具吐出了stdout,而stdout看起来像答案。

打开网易新闻 查看精彩图片

我在Cybrium做了两年,过去六个月最关键的技术决策就是:不再让用户通过Shell调用我们的扫描器。现在所有流量走MCP服务器。十个工具。类型化输入。结构化输出。没有工作目录漂移。

核心判断很简单:如果智能体通过Shell与安全工具对话,你搭建的系统里智能体的"自信"与扫描器的实际覆盖率是脱钩的。MCP通过显式、机器可检查、事后可审计的契约来修复这一点。这不是用户体验升级,而是"能审计的安全流水线"与"不能审计的安全流水线"之间的本质区别。

Shell工具到底给了什么?当Claude Code、Cursor或任何智能体执行cyscan --path . --format json时,实际情况是:智能体构造字符串→字符串进入Shell→Shell维护自己的工作目录、环境变量、退出码等状态,而智能体只能部分观察→扫描器运行,写入stdout,可能还有stderr,以退出码结束→智能体将所有内容作为一大块文本读回,再自行解析。

每一步都是智能体看不见的故障点。它不知道cyscan是不是它以为的二进制文件,还是某个别名,或是PATH上的不同版本。它不知道传入的路径是不是符号链接,有没有被Shell通配符展开,或者被截断。它不知道stderr里是否包含关键警告,还是只是噪音。它只知道:拿到了文本,该生成回复了。

打开网易新闻 查看精彩图片

MCP的替代方案是协议层面的重构。工具不再是"运行这个字符串",而是"调用这个函数"。输入有JSON Schema,输出也有。没有Shell解析,没有工作目录,没有环境变量继承。智能体精确知道它请求了什么,工具精确知道该做什么,两者之间的契约可以被验证、被记录、被重放。

迁移过程中我们发现,团队之前花了大量时间教智能体"如何正确调用扫描器"——处理相对路径、管理输出格式、解析退出码含义。这些本不该是智能体的工作。MCP把这部分复杂性收进了协议层,智能体只需要关心"要扫描什么"和"拿到结果后做什么"。

那位创始人的事故最终没有造成数据泄露,但代价是一整晚的紧急修复和一次信任崩塌。他后来问我:如果扫描器当时能返回"本次扫描仅覆盖6个文件",Claude会停下来吗?理论上会,但Shell接口没有"本次扫描覆盖范围"这个字段。stdout里可能有,也可能没有,取决于扫描器版本和参数组合。智能体没有可靠的方式来确认。

这正是我们放弃Shell的原因。安全扫描不是文本生成任务,是覆盖率的确定性问题。当智能体的自信与工具的实际行为之间隔着一层不透明的Shell,你得到的不是自动化,是自动化了的侥幸心理。