MCP 选中工具之后,到安全执行之间还缺什么?
一张面向生产环境的职责地图:MCP 与工具服务负责什么,Agent Runtime 负责什么,共享执行控制负责什么,企业身份与审批系统负责什么,以及为什么业务系统仍然必须保留最终授权。
一个 Agent 已经通过 MCP 发现了工具,生成了符合 inputSchema 的参数,并成功发出一次 tools/call。
这时,这项业务操作算完成了吗?
如果工具只是查询天气、读取本地文件,答案可能接近“是”。但只要工具开始创建退款、修改库存、停用员工账号、批量发送通知或触发生产部署,一次协议调用成功就只代表:请求已经沿着一条技术通道到达某个工具服务。
它还不能单独证明:
- 这次行动代表的是正确的人;
- 这个人此刻有权操作这条业务数据;
- 高风险参数已经得到有效审批;
- 网络重试不会造成重复扣款或重复退款;
- 超时之后到底执行了还是没有执行;
- 部分失败时应该重试、补偿还是人工介入;
- 事后留下的记录足以回答“谁以什么身份做了什么”;
- 最终业务状态仍然满足系统自己的约束。
因此,企业真正面对的不是“MCP 能不能调用接口”,而是另一个更完整的问题:
从工具被选中,到真实业务后果被安全地形成,中间还需要哪些责任?这些责任分别应该由谁承担?
这篇文章不把所有问题都塞给 MCP,也不试图再创造一个万能中间层。它只做一件事:把这条执行链上容易混在一起的责任拆开。
一、MCP 调用成功,和业务操作安全完成,是两个事件
MCP 已经解决了工具发现、结构化参数、调用协议、结果返回等重要问题。对于 HTTP 传输,它还有独立的授权规范,用来说明受保护的 MCP Server 如何作为 OAuth 资源服务器工作,客户端如何获取访问令牌,以及令牌为什么必须绑定正确的资源受众。官方安全实践也覆盖了最小权限、会话绑定、凭证保护、沙箱和逐次授权等问题。
所以这里讨论的缺口,并不是因为“MCP 没有安全设计”。
真正的区别在于,协议成功和业务完成回答的是两组不同的问题:
协议成功:
一个格式正确的请求,是否被送达给了一个允许当前客户端访问的 MCP Server?
业务完成:
正确的主体,是否在正确的业务状态下,对正确的对象执行了一次正确的行动,
并且审批、幂等、失败恢复和责任证据都与这次行动保持一致?
协议可以承载行动,但协议本身不知道:
- 订单
ORD-9001现在是否还允许退款; - 员工
E-1024是否属于门店S-18; - 这笔订单此前是否已经在另一个入口完成过退款;
- 退款金额是否超过当前剩余可退金额;
- 这个审批是否批准的正是即将执行的那组参数。
这些事实更靠近业务领域,而且会随时间变化。它们不能只靠工具描述、模型推理或一次 OAuth 鉴权来推断。
二、先把执行链拆成五种责任
生产链路最容易犯的错误,是把所有组件统称为“Agent 平台”“MCP 网关”或“工具服务”,然后默认这个盒子会自然承担全部安全责任。
更可验证的做法,是先区分五种语义责任:
| 层次 | 主要责任 | 不能被误认为 |
|---|---|---|
| 工具协议与 MCP Server | 工具发现、Schema、协议调用、协议授权和工具执行接口 | 每一条业务对象的最终授权者 |
| Agent Runtime / 编排器 | 理解任务、选择工具、生成参数、维护工作流状态、呈现人机交互 | 可信业务身份的来源,或最终权限系统 |
| 共享执行控制 | 限定可触达能力、装配可信上下文、解释可移植治理意图、绑定审批快照、协调派发、请求去重、记录统一执行状态 | 企业身份提供者、审批权威、事务协调器或全部业务规则的拥有者 |
| 企业身份、策略与审批系统 | 解析可信身份、执行组织策略、判断谁有资格批准某类行动 | 真实业务操作本身 |
| 业务系统 | 强制租户隔离、对象级授权、当前状态校验、业务不变量、事务提交和最终执行决定 | 只要上游说“已审批”就盲目信任的被动后端 |
这五种责任不等于必须部署五个产品。同一个服务可以承担其中多项责任,一个组织也可以把它们拆到多个系统。
关键在于:安全边界应该按照责任来判断,而不是按照进程数量来判断。
把两个责任放进同一个进程,不会让它们自动变成同一件事;把它们拆成多个服务,也不会自动带来安全性。
三、“权限”至少包含三种不同问题
很多 Agent 权限管理的讨论之所以混乱,是因为大家都在说“有没有权限”,但实际说的是三种完全不同的决定。
1. 当前客户端能不能访问这个 MCP Server?
这是协议授权问题。OAuth Scope、Token Audience、过期时间、受保护资源元数据等属于这一层。
2. 当前 Agent 场景应该看见哪些能力?
一个售后 Agent 可能需要 order.read 和 refund.request.create,却永远不应该看见 employee.delete 或 tenant.config.update。
这是 Reach,能力触达范围。它在模型选工具、生成参数之前,就缩小了最大可达攻击面。
3. 当前主体此刻能不能对这个对象执行这项操作?
这是最终业务 Authority:
- 操作人属于哪个租户、门店或组织;
- 订单当前是否可退款;
- 金额是否在剩余可退范围内;
- 是否已经存在另一笔成功退款;
- 当前组织策略是否允许这项操作。
只有业务系统,或者拥有同等新鲜业务上下文的权威系统,才能可靠回答这些问题。
三者是互补关系:
MCP 协议授权
-> 当前客户端能否访问这个 Server?
能力 Reach
-> 当前 Agent 场景能否触达这个操作?
业务 Authority
-> 当前主体此刻能否执行这一次具体行动?
一个有效的 MCP Token 不是订单退款授权;一份工具白名单不能替代租户隔离;业务系统保留最终授权,也不意味着可以把所有危险工具都无差别暴露给模型。
四、审批、幂等、重试、审计和回滚分别应该放在哪里?
这些问题都不是加一个字段或挂一个中间件就能完整解决的。它们是端到端属性:有自然的主要责任方,但必须穿过整条执行链才成立。
1. Agent 工具调用审批
Agent Runtime 往往最适合展示确认或审批界面;共享执行层可以暂停派发,并把审批决定绑定到一份规范化请求;企业审批系统决定谁有资格批准;业务系统在真正写入前仍然要再次执行最终授权。
一份有意义的审批绑定,至少应覆盖:
可信主体 + 能力标识 + 规范化参数 + 任务身份
如果审批之后参数发生变化,原审批不能静默授权新请求。
因此,“弹过确认框”不等于已经建立审批边界。审批证据必须和将要执行的内容绑定,验证方还必须知道:这份证据究竟由谁签发、谁有资格签发。
2. Agent 重复执行与幂等
Runtime 应在重试过程中保持稳定的请求身份;共享执行层可以对重复提交去重并保存任务状态;业务系统则必须在业务域内执行最终唯一性约束。
以退款为例,共享执行层可以阻止同一个执行请求被派发两次。但只有订单或支付系统才能保证:同一笔业务退款不会从网页后台、定时任务和 Agent 三条不同路径被重复创建。
所以“中间层做了幂等”不能替代数据库唯一约束、业务幂等键或领域状态检查。
3. 重试、超时与部分失败
写操作不能看到 HTTP 超时就盲目重试,因为“没有收到响应”不代表“对方没有执行”。
一个有状态任务至少应该区分:
accepted -> awaiting_approval -> queued -> running -> succeeded
\-> rejected
\-> failed
调用方在超时后应该查询或恢复同一个任务身份,而不是重新创造一笔新的业务请求。重试策略可以由执行协调层负责,但重试是否安全,最终仍依赖业务边界上的幂等约束。
4. Agent 审计日志与可验证证据
普通应用日志主要回答运维问题;审计证据要回答责任问题:
- 谁发起了行动;
- Agent 代表的是哪个可信主体;
- 哪项能力和哪些参数被评估;
- 哪条策略或哪次审批影响了执行;
- 谁在什么时间派发了什么;
- 业务系统最终接受还是拒绝了什么。
如果同一个可能被攻陷的 Runtime,既能声称“审批已经发生”,又能书写唯一的审计记录,那么这份记录本质上仍是自述。
更高保证等级下,可能还需要独立签名的审批证据、只追加存储、外部时间基准,或者由业务边界重新验证证据链。
共享执行层可以统一记录格式和任务关联,但不能因为把一条记录命名为“审计日志”,就让自己的声明自动获得独立可信性。
5. 回滚、补偿与恢复
回滚不是任意工具都天然具备的通用能力。
有些动作可逆,有些动作需要显式补偿,有些动作根本无法撤销。真正的恢复语义属于业务操作本身,或者属于理解业务流程的 Workflow / Saga 层。
共享执行层可以保存执行状态、暴露失败,并触发业务系统已经明确定义的补偿路径;但它不能给几个互不相关的 API 标一个 atomic: true,就凭空制造事务原子性。
五、一条最小的生产执行路径
以“Agent 帮用户发起订单退款”为例,一条可以被审查的路径大致如下:
1. 用户提出退款要求。
2. Agent Runtime 选择 refund.create,并提出参数。
3. MCP Client 使用协议授权调用可信 Server。
4. 执行控制检查当前路由能否触达 refund.create。
5. 可信主体从模型参数之外的可信上下文中解析。
6. 请求 Schema 与治理条件被评估。
7. 如需审批,任务暂停在精确参数快照上。
8. 同一任务携带可验证审批证据恢复执行。
9. 派发使用稳定的幂等身份。
10. 业务系统重新校验主体、租户、订单状态、金额和业务不变量。
11. 业务系统提交或拒绝这次操作。
12. 结果和证据链都关联到同一个任务身份。
这里不存在一个“做完所有安全”的万能节点。模型提出行动,协议承载行动,Runtime 协调行动,企业系统建立身份和策略信任,业务系统授权并提交最终后果。
六、每个 MCP 应用都需要独立控制面吗?
不需要。
单用户本地工具、只读能力,或者只有一个 Runtime 和少量低后果操作的应用,完全可以把这些控制放在 MCP Server 或宿主应用内部。为了概念完整而额外部署新服务,只会增加运维成本,并不必然提高安全性。
当以下情况逐渐出现时,共享执行控制才更有价值:
- 多个 Agent 框架或 MCP Client 在调用同一批业务系统;
- 多个 MCP Server 正在重复实现审批、幂等和审计;
- 企业希望不同入口共享同一份能力白名单;
- 任务需要跨人工审批、本地执行器或进程重启暂停与恢复;
- 执行状态必须在 Runtime 退出后继续存在;
- 不同工具的治理证据需要统一比较;
- 业务凭证不应该散落到每一个 Agent 应用中。
真正的判断标准不是“我们有没有使用 Agent”,而是:
同一组跨工具执行保证,是否已经在足够多的地方被重复实现,以至于建立共享边界反而能减少不一致和事故半径?
即便答案是“需要共享控制层”,它也应该保持克制:协调和执行公共控制,不把自己扩张为全部身份、全部策略、全部事务和全部业务真相的来源。
七、架构必须明确写出的失败语义
与其笼统地说“支持 Agent 治理”,不如明确这些失败会发生什么:
| 失败情况 | 保守处理 |
|---|---|
| 可信主体缺失或无法验证 | 不派发要求主体的操作 |
| 工具元数据或治理声明不可信 | 不根据它提升权限或放宽限制 |
| 审批证据和精确请求不匹配 | 拒绝执行或重新审批 |
| 超时后执行状态未知 | 查询同一任务,不盲目创建新写请求 |
| 可重试写操作没有幂等身份 | 拒绝自动重试 |
| 业务授权失败 | 返回最终拒绝;上游审批不能覆盖业务权限 |
| 审计存储不可用 | 按操作等级执行明确的 fail-open / fail-closed 策略,不能假装证据已经落盘 |
| 没有定义补偿动作 | 报告不可逆或需要人工恢复,不能宣称支持回滚 |
这些语义才使不同实现之间可以被比较、被测试,也使责任边界在事故发生时不至于消失。
八、ACC 与 BailingHub 在这张图中的位置
披露:我维护两个探索这一问题的开源项目。
Agent Capability Contract(ACC) 尝试用可移植、操作级声明表达能力 Reach 与治理意图。它不执行工具,不定义企业身份,也不替代业务系统的最终授权。
BailingHub(百灵中枢) 尝试实现一个可自托管的共享执行控制面,在业务工具调用周围处理能力触达、可信主体、审批绑定、任务状态、派发和追踪。它是一个具体实现,不是唯一答案;业务系统仍然拥有最终 Authority。
这张职责地图并不依赖任何一个项目。组织可以把同样的边界实现在 MCP Server、Agent 平台、API 网关、工作流引擎、共享控制面,或者它们的组合中。
真正重要的是,系统能够不含糊地回答:
- 哪个组件控制工具 Reach?
- 可信主体来自哪里?
- 谁判断需要人工审批,谁有资格批准?
- 审批授权的究竟是哪一份精确请求?
- 重试如何绑定稳定幂等身份?
- 谁记录证据,谁能独立验证?
- 哪个系统执行最终对象级和业务状态授权?
- 执行部分完成、状态未知、被拒绝或不可逆时会发生什么?
MCP 让工具互操作变得现实。从“工具被选中”到“业务后果被安全形成”,仍然是一项必须沿整条链路共同构造的端到端属性。
参考资料
更多推荐


所有评论(0)