MCP 与工具接口层:标准化改变了什么,又打开了什么安全面
Model Context Protocol 的有趣之处不在于它标准化了 tool calling,而在于它标准化了一道信任边界。大多数 MCP 服务器根本不是远程的;它们是宿主派生的本地 stdio 子进程。本地也好,远程也好,每个工具现在都是一个你没有写过、也无法审计的端点,其描述、结果与行为都受控于某个模型没有任何理由信任的对象。
在 MCP 之前,工具管道是定制式的:每个 agent 都有自己的适配器、自己的 schema、自己的失败模式。MCP 把它变成了一个共享协议,随之而来的是一块共享的攻击面。其后的安全文献异常连贯,因为它们攻击的是同一个结构性事实:工具接口层必须把不可信数据喂给模型,同时又要防止这些数据变成指令。
协议标准化了什么
MCP 规范定义了宿主、客户端与服务器之间的 JSON-RPC 2.0 消息。宿主是 LLM 应用;服务器提供能力。服务器提供资源(上下文数据)、提示词(模板化工作流)与工具(模型可调用的函数)。客户端可以提供采样(服务器发起的 LLM 调用)、roots(文件系统边界)与引出。能力协商在连接时进行。
实践中的变化是:工具发现、描述与调用如今在整个生态里是统一且机器可读的。这与 Language Server Protocol 为编辑器做的事情如出一辙,收益与代价也相同:单一格式意味着单一类别的漏洞,而生态的成长速度超过了普通服务器作者的安全实践。
规范自身的安全姿态
规范的安全章节读起来像一份由深知自己无法执行它的人写出的威胁模型。其中三点论断要紧:
- 工具代表任意代码执行,必须谨慎对待。
- 除非服务器本身可信,否则工具描述与注解均为不可信。
- 宿主在调用工具前必须取得用户的明确同意;采样请求需要用户的明确批准,因为协议有意限制服务器对提示词的可见性。
然后是最关键的一点:协议无法在协议层面执行这些原则,实现留给每个宿主。因此 MCP 标准化了契约,却没有标准化安全模型。攻击文献正是落在这个缺口上。
精读:InjecAgent——经由工具结果的注入
InjecAgent(Zhan 等人,Findings of ACL 2024)让经由工具结果的间接提示注入变得可测量。该 benchmark 在 17 个用户工具与 62 个攻击者工具上包含 1,054 个测试用例,两种攻击意图:直接伤害用户与外泄私人数据。在 30 个被评测的 agent 中,ReAct 提示的 GPT-4——该 benchmark 于 March 2024 构建时可用的最强模型——有 24% 的情况会被攻破,而用一条 hacking 提示词强化注入内容后,成功率几乎翻倍。这个 24% 是对 2024 年模型的 2024 年测量;真正值得带走的是这个 benchmark,而不是这个数字。
值得借鉴的设计选择在于攻击所处的位置。攻击者的内容不在用户轮次里,而在工具结果里——一个网页、一封邮件或一个 API 响应,agent 必须把它作为工作的一部分来处理。这正是工具接口层核心的不对称性:结果既是 agent 的数据,也是攻击者的投递载体。
精读:MCPTox——投毒元数据本身
InjecAgent 经由结果发起攻击。MCPTox(Sun 等人,2025)经由工具定义发起攻击,完全不涉及执行:恶意指令被嵌入工具元数据。它建立在 45 个真实在线的 MCP 服务器与 353 个工具之上,在十个风险类别中生成 1,312 个恶意测试用例。在 20 个 agent 中,o1-mini 达到 72.8% 的攻击成功率,即便对对齐程度最高的模型,拒绝率也保持在 3% 以下。一个令人不安的发现是:能力更强的模型往往更容易中招,因为该攻击利用的是指令遵循。
实操演示来自 Invariant Labs 的工具投毒分析:一个被投毒的 add 工具,其描述让模型读取 ~/.cursor/mcp.json 和 ~/.ssh/id_rsa 并把内容作为参数传递;一次 rug pull——服务器在用户批准之后更改了工具描述;以及遮蔽(shadowing)——被投毒的描述重写了一个受信工具的行为,演示中 agent 把每封邮件都发到攻击者的地址,即使用户指定了不同的收件人。MCP 安全审计(Radosevich 和 Halloran)发现了同类后果——被胁迫的恶意代码执行、远程访问控制与凭据窃取——并发布了 MCPSafetyScanner,一个在部署前审计任意 MCP 服务器的 agentic 工具。
信任边界图示
每次工具调用都有三条不可信通道,全部位于模型的上下文之内:
tool description -> model calls tool -> server executes -> tool result -> next decision
^ ^ | ^
attacker's user approves? attacker's code attacker's data
instructions
描述通道在调用之前被投毒,结果通道在调用之后。执行通道是服务器自己的代码,可以随意做服务器权限允许的任何事。rug pull 意味着即便是已获批准的描述,也不会随时间保持稳定。
加固宿主:五条规则
- 把每个 MCP 服务器都当作不受信的主体。按服务器为工具与数据设置白名单,按服务器划定能力范围,服务器之间不做隐式共享。遮蔽只有在某个服务器的指令能修改对另一个服务器的行为时才起作用;按服务器划分的上下文边界会从结构上消灭它。
- 固定工具定义。在批准时对描述与 schema 取哈希,每次连接时做 diff,一旦发生变化就拒绝或重新批准。这直接回应了 rug pull。
- 向用户展示模型所见的描述,而不是摘要。Invariant 的演示之所以奏效,是因为用户看到的是 add two numbers,而模型看到的是 read my SSH keys。模型所见与用户所见之间的落差就是攻击。
- 特权工具要经明确的人工批准才能调用,且参数完整渲染,结果在重新进入上下文之前做净化:剥离标记、截断,并让 harness 策略无法从结果内容触达。批准只在罕见使用时才有效;每次调用都询问的 agent 会把用户训练成橡皮图章,同意门禁会在提示词量之下崩溃。门禁必须只为真正特权的工具保留。
- 以最小权限在隔离的运行时中运行服务器代码。执行侧正是我的沙箱化 agent 笔记的主题。
这些防御与我在 PiPlan.ai 使用的提交流程一一对应:变更先提案、后应用,审查面在提交时渲染模型的完整参数,而不是它们的摘要。固定即提案记录其基于哪个工具定义构建;diff 即提交时检查定义自提案以来是否未变;渲染即审查面本身。rug pull 恰好活在提案与提交之间的缺口里,这正是为什么 diff 必须发生在提交时,而不是只在安装时。
MCP 是一个好协议,却带着一个糟糕的默认信任模型。标准化的胜利是真实的,但它给生态带来的是统一的接口,而不是统一的安全。安全模型必须由各宿主各自构建,而诚实的态度是:这是一个执行问题,而不是协议问题——描述是不可信输入,结果也是不可信输入,唯一的防御是范围划定、透明度与门禁。
本文引用的来源链接均已抓取并核实。