工具协议:MCP、权限与副作用
模型把 patient_id 从 p1 写成 p2 时,最不该发生的事是工具“按 schema 成功”并读取了另一个人的记录。工具调用把模型输出变成现实动作,也把错误半径从文字扩大到系统。
一个安全工具不是“有 JSON schema 的函数”,而是带身份、授权范围、风险、副作用、幂等和审计语义的能力。
MCP 的来龙去脉
Section titled “MCP 的来龙去脉”过去每个 Agent 框架有自己的 tool wrapper。Model Context Protocol(MCP)用 host、client、server 架构、JSON-RPC、生命周期和能力协商标准化 resources、prompts、tools 的连接。S24 2025-06-18 规范明确初始化、协议版本和 transport;安全最佳实践讨论 confused deputy、token passthrough 等风险。S25
MCP 解决互联,不替代业务授权。服务暴露 write_fhir_resource,模型看到 schema 并不表示当前用户、患者、场景有权调用。
原理:能力与权限分离
Section titled “原理:能力与权限分离”MCP capability 表示“服务支持什么”;ToolManifest 表示“在本系统、对当前主体、当前任务允许什么”。策略决策输入包括 user/agent identity、patient scope、purpose、tool risk、arguments、task state、approval 和时间。OPA 可把策略决策从业务代码分离。S26
工具分级:
- L0 纯计算,无数据访问。
- L1 只读公开数据。
- L2 只读患者数据,需 patient/purpose scope。
- L3 可逆写入,需显式人审和幂等。
- L4 高风险/不可逆动作,默认不向模型暴露。
任何工具结果都视为不可信数据,不能覆盖系统指令。token 不传给模型,server 也不应透明转发客户端 token。
| 方案 | 解决 | 不解决 |
|---|---|---|
| OpenAPI function calling | schema 与 HTTP 绑定 | 会话、资源和协议能力 |
| MCP | 可组合工具/资源协议 S24 | 医疗风险与授权策略 |
| OPA | policy-as-code S26 | 身份系统与工具实现 |
| 自研 ToolManifest | 场景风险、幂等、人审 | 需要与协议/执行器集成 |
先让合法 JSON 也因为 scope 不匹配被拒绝
Section titled “先让合法 JSON 也因为 scope 不匹配被拒绝”python3 labs/run_lab.py --lab 19实验对四次调用做策略判定:公开试验查询通过;同患者只读 FHIR 在 scope 内通过;跨患者读取拒绝;没有 approval 的写入拒绝。先只改角色再运行一次,确认它不会自动放行,purpose 和 task state 仍必须匹配。这就是能力发现与业务授权分开的实际含义。
贯穿实例:FHIR 工具从描述到执行
Section titled “贯穿实例:FHIR 工具从描述到执行”MCP server 可声明 read_fhir_observations 的 input/output schema;host 在初始化时发现 tool capability。S24 MedAgent Forge 在注册时又包一层 ToolManifest:owner、risk=L2、read-only、required scopes、允许 purpose、最大 date range/result count、timeout、retry、redaction 和审计字段。模型只能看到任务允许的工具子集。
调用到达 gateway 后,先验证 schema,再从可信会话取 user/service identity(不接受模型自报),把 TaskContext、patient scope、purpose 和 manifest 送到 policy decision。通过后,gateway 才兑换/使用服务端 token 调 FHIR。响应先做大小与类型限制、敏感字段最小化,再作为不可信 tool data 返回 runtime。
模型生成 patient_id=p2 而 TaskContext 是 p1,服务端拒绝并记录 policy reason;不能让模型通过“我获得了用户许可”的自然语言改变 scope。错误结果是结构化 authorization_denied,orchestrator 不应换工具绕过。
MCP 解决与不解决的问题
Section titled “MCP 解决与不解决的问题”MCP 统一连接、能力协商、resources/prompts/tools 和 transport/lifecycle,降低每个集成重复包装。S24 它不决定临床工具风险、不提供医院身份、不知道审批业务、不保证 server 可信。安全最佳实践提醒 token passthrough 和 confused deputy,S25 意味着 server 不能拿到不属于它 audience 的 token,也不能借 host 权限替任意客户端行动。
远程 server 与本地 stdio server 风险不同。stdio server 本质是本地进程/供应链,可能读文件和环境;远程 server 涉及 OAuth、网络、会话和服务端信任。两者都应 allowlist、固定版本和最小运行权限。生产禁止运行用户随意添加的未知 MCP server。
ToolManifest 设计细节
Section titled “ToolManifest 设计细节”input schema 只是开始,还要有 semantic constraints:date range 上限、patient id 必须等于 scope、URL/路径 allowlist、枚举和资源配额。output schema 定义最大大小、provenance、错误类型。side_effect 声明 none / internal-reversible / external;risk 决定是否隐藏、需何种 approval。
幂等字段说明 key 构成和重复调用响应;compensation 说明能否撤销;data classification 标 PHI/公开;observability 规定可记录/必须脱敏。owner/on-call 和 deprecation date 让工具可运营,不沦为无人负责的函数。
策略决策与执行分离
Section titled “策略决策与执行分离”OPA 等 policy engine 输入 JSON context,输出 allow、reason、obligations(如 mask fields、need approval)。S26 执行器必须落实 obligations;只拿 boolean 可能丢掉脱敏要求。策略代码版本进入 trace 和 task snapshot,改变高风险 policy 需要 review/tests。
RBAC 适合“协调员可使用筛选”,ABAC 再限制 patient/purpose/time/tool。授权默认 deny,未知属性不放行。emergency/break-glass 若存在,使用独立流程、强审计和事后复核,不通过 prompt 暗号触发。
工具结果也是攻击面
Section titled “工具结果也是攻击面”FHIR narrative、试验 PDF、网页搜索结果都可能包含恶意指令。工具返回 envelope 明确 content_is_untrusted=true、source 和 data;runtime 不把其拼进 system prompt。输出大小、MIME、嵌套深度和 URL 受限,防资源耗尽与 SSRF。任何从 tool result 产生的后续高风险 action仍重新授权。
Approval UX
Section titled “Approval UX”审批页面显示 tool、目标患者/资源、字段 diff、来源 task、理由、风险、可撤销性和幂等键。批准绑定 arguments hash 与过期时间;任何参数变化让 approval 失效。批量批准必须明确范围,不能使用“总是允许”作为默认。
contract tests
Section titled “contract tests”每个 tool 用官方/模拟 server 测 schema、范围、分页、超时、429、错误映射、输出脱敏、幂等和 policy。MCP protocol version 协商失败应清楚报错,不静默降级到未知行为。安全测试覆盖 token audience、重定向、恶意 server、路径/URL 和大响应。
工具注册与退役流程
Section titled “工具注册与退役流程”owner 提交 manifest、threat model、contract tests 和数据流图;安全/领域审核后进入 dev allowlist,再以特定 Agent/purpose 小范围发布。指标观察错误、拒绝和成本;schema 变更用新版本并双栈;退役先从发现列表移除,等待任务 drain,撤销凭证并保留历史解析元数据。
把 read_fhir 的 date range 去掉上限,构造一次合法 scope 内的大规模读取;你会看到“鉴权通过”仍可造成过度披露。再添加最大范围/result 和 purpose obligation。安全不只是能不能调用,还包括参数和返回的最小化。
工具目录的可读性
Section titled “工具目录的可读性”给开发者的目录展示 schema/version/owner/SLO;给模型只展示允许且简洁的描述;给审核者展示风险、数据和审批。三个视图来自同一 manifest,避免描述漂移。工具名表达动作和对象,读写分离,错误 code 稳定;不要依赖模型从长 prose 猜副作用。
工具可用性变化时 runtime 得到 temporarily_unavailable 与 retry-after,不让模型把空返回解释成无证据。监控按 tool/version 看成功、拒绝、时延和结果量,异常增长可能是 prompt injection 或回归信号。
MCP 解决连接和能力协商,不接管医院身份、患者范围或审批业务。任何把 schema 当成授权证明的实现,都把最关键的判断交错了层。
- 工具描述不是安全边界,执行前必须服务端鉴权。
- 过度宽泛的 read scope 同样危险,可造成批量泄露。
- 人工确认弹窗若隐藏真实参数,会沦为点击仪式。
- MCP server 是供应链入口,需要版本固定、签名/来源和隔离。
资料与延伸阅读
Section titled “资料与延伸阅读”以下参考资料按“协议、委托风险、策略执行、攻击分类”的顺序阅读最顺手。它们共同构成参考资料,不替具体系统完成授权审计。