核心结论

MCP 在多 Agent 系统中的价值,是提供标准集成边界,而不是充当系统宪法。

2026-07-28 规范把协议核心改为无状态:请求自包含,协议级 initialize/initialized 握手被移除,Mcp-Session-Id 也被移除。这能简化路由和水平扩展,但不会让应用自动变成无状态,也不会自动保护委派,更不会消除授权需求。

生产系统应明确分成两个平面:

text
Agent / Host / MCP Client
          |
          | MCP 请求与结果
          v
MCP Server / 工具适配器
          |
          | 可信身份 + 策略决策
          v
业务服务 / 数据库 / 实际副作用

协议平面:互操作、Schema、请求生命周期
控制平面:身份、授权、审批、限额、状态、审计

即使一个任务经过多个 Agent、服务端与框架,控制平面仍必须保持权威。

MCP 2026-07-28 提供什么

在协议层,MCP 提供:

  • JSON-RPC 消息;
  • 无状态、自包含请求;
  • 工具(Tool)、资源(Resource)与提示词(Prompt);
  • 每次请求携带的能力声明与元数据;
  • 传输与错误语义;
  • Tasks、MCP Apps 等可选扩展;
  • 基于成熟 OAuth 与 OpenID Connect 规范的 HTTP 授权机制;
  • W3C Trace Context 链路上下文传播约定。

它不能证明:

  • 调用者拥有目标对象;
  • 一个 Agent 有权向另一个 Agent 委派;
  • 请求动作符合用户意图;
  • 破坏性副作用已获批准;
  • 工具输出可信;
  • 重试安全;
  • 应用状态仍然有效;
  • 工作流应继续执行。

这种分离不是需要绕过的限制,而是正确的所有权边界:协议传递请求,应用执行业务含义。

无状态协议,有状态应用

早期 MCP 版本使用协议级初始化握手与会话标识。在 2026-07-28 中,每个核心请求都带有兼容服务实例处理它所需的信息。

应用仍可有状态,推荐模式是显式、受授权的句柄(Handle):

text
tools/call browser.create -> { "browser_id": "br_7f..." }
tools/call browser.navigate { "browser_id": "br_7f...", ... }
tools/call browser.close    { "browser_id": "br_7f..." }

句柄只是状态引用,本身不授予访问权。

每次使用时,服务端都应验证:

  • 已认证主体及其租户是否拥有或允许使用句柄;
  • 句柄是否为当前工作负载与用途创建;
  • 是否已过期、撤销或进入终态;
  • 请求的状态转换是否有效;
  • 是否允许并发使用;
  • 结果是否有大小限制且隔离正确。

不要只把身份或权限编码在模型可见的不透明字符串中。句柄即使被猜到或泄露,也不能获得访问权。

多 Agent 调用中的身份

一个任务可能同时包含多种身份:

身份 含义
资源所有者 数据与权限涉及的用户或组织
已认证主体(Subject) 访问令牌代表的身份
客户端(Client) 向授权服务器注册的 MCP 客户端
工作负载(Workload) 执行任务的应用或作业
执行者(Actor) 请求当前动作的 Agent 或组件
委派者(Delegator) 向另一个执行者分配工作的身份
审批者(Approver) 审批有界副作用的人或服务

把这些身份压缩成一个 user_id,会破坏问责,并经常意外放大权限。

应由应用或身份系统建立可信执行者链路:

text
resource_owner -> workload -> planner_agent -> executor_agent -> tool

每一跳只能收窄或保持权限,不能静默扩大。模型生成的“规划者已批准”文本不是委派证明。

HTTP 授权规范覆盖什么

对于受保护的 HTTP 服务,MCP 授权规范基于 OAuth 定义角色:

  • MCP Server 作为 OAuth 资源服务器;
  • MCP Client 作为 OAuth 客户端;
  • 受保护资源元数据标识授权服务器;
  • resource 参数把请求绑定到目标 MCP Server;
  • 服务端校验访问令牌是否为自身目标受众(Audience)签发;
  • 权限范围(Scope)不足时可以触发有界的追加授权流程。

规范明确禁止令牌透传(Token Passthrough):MCP Server 不能接收一个并非为自己签发的令牌,再把它转发给下游 API。

业务授权仍属于应用:

text
传输层授权:
  “这个已认证客户端能否调用受保护的 MCP Server?”

业务授权:
  “这个主体能否通过当前工作负载与执行者链路,
   为这个用途取消当前状态下的这张特定订单?”

两者缺一不可。

用 Go 演示授权边界

下面的独立 Go 程序演示取消一张订单的策略判断。保存为 main.go 后执行 go run main.go。身份必须来自已验证的认证上下文,订单来自业务数据库,审批来自可信审批存储。调用者只提供订单 ID 和幂等键;不能把模型生成的参数反序列化为这些可信记录。

go
package main

import (
	"errors"
	"fmt"
	"time"
)

type Principal struct {
	SubjectID string
	TenantID  string
	ActorID   string
	Scopes    map[string]bool
}

type CancelRequest struct {
	OrderID        string
	IdempotencyKey string
}

type Order struct {
	ID       string
	TenantID string
	OwnerID  string
	Status   string
}

type Binding struct {
	TenantID, SubjectID, ActorID string
	Operation, OrderID, Key     string
}

type Approval struct {
	Binding   Binding
	ExpiresAt time.Time
	Revoked   bool
	Consumed  bool
}

func authorizeCancel(now time.Time, p Principal, r CancelRequest, o Order, a Approval) error {
	if p.SubjectID == "" || p.TenantID == "" || p.ActorID == "" {
		return errors.New("identity_required")
	}
	if r.OrderID == "" || r.IdempotencyKey == "" {
		return errors.New("request_incomplete")
	}
	if !p.Scopes["orders:cancel"] {
		return errors.New("insufficient_scope")
	}
	if r.OrderID != o.ID || p.TenantID != o.TenantID || p.SubjectID != o.OwnerID {
		return errors.New("object_not_authorized")
	}
	if o.Status != "open" {
		return errors.New("invalid_state")
	}
	expected := Binding{
		TenantID: p.TenantID, SubjectID: p.SubjectID, ActorID: p.ActorID,
		Operation: "orders.cancel@1", OrderID: r.OrderID, Key: r.IdempotencyKey,
	}
	if a.Binding != expected || a.Revoked || a.Consumed || !now.Before(a.ExpiresAt) {
		return errors.New("approval_invalid")
	}
	return nil
}

func main() {
	now := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
	p := Principal{"user-1", "tenant-1", "executor-1", map[string]bool{"orders:cancel": true}}
	r := CancelRequest{"order-1", "cancel-1"}
	o := Order{"order-1", "tenant-1", "user-1", "open"}
	a := Approval{
		Binding: Binding{
			TenantID: p.TenantID, SubjectID: p.SubjectID, ActorID: p.ActorID,
			Operation: "orders.cancel@1", OrderID: r.OrderID, Key: r.IdempotencyKey,
		},
		ExpiresAt: now.Add(time.Minute),
	}
	fmt.Println(authorizeCancel(now, p, r, o, a))
	r.OrderID, o.ID = "order-2", "order-2"
	fmt.Println(authorizeCancel(now, p, r, o, a))
}

预期输出依次是 <nil> 和 approval_invalid:即使两张订单属于同一用户,订单 1 的审批也不能授权取消订单 2。函数只判断可信快照,不负责验证令牌,也没有执行或持久化取消操作。

业务服务必须在执行时重新检查身份、当前策略、订单状态及审批有效性,并原子完成审批消费、订单修改和幂等结果持久化。同一键与同一请求指纹的重试应返回已记录结果,指纹冲突则拒绝。单靠这个内存函数无法阻止并发重放。

审批必须绑定具体操作

审批至少应绑定:

  • 已认证主体;
  • 受委派执行者或工作负载;
  • 工具与操作版本;
  • 目标对象或有界集合;
  • 关键参数;
  • 允许的副作用;
  • 有效期;
  • 最大使用次数;
  • 关联 ID 与幂等键。

“批准所有未来动作”不是安全捷径。批准一份计划,也不代表之后模型生成的每个参数都自动获得覆盖。

MCP 协议可以承载输入请求或任务状态,但应用必须定义审批语义、安全持久化,并在副作用发生前立即校验。

结构校验不能代替授权

工具的 Schema 很有价值:

  • 客户端可以发现必填字段;
  • 错误结构能被提前拒绝;
  • 枚举与上下界可以缩小输入范围;
  • 输出可以校验;
  • 契约可以版本化。

但 Schema 无法确认:

  • 调用者是谁;
  • 记录是否属于调用者的租户;
  • 当前值是否仍有效;
  • 价格、余额或额度是否变化;
  • 动作是否已批准;
  • 重复请求是否已经成功。

除非来自可信、固定来源,否则工具描述、注解、资源内容与远程 Schema 都应视为不可信。

工具结果也是输入边界

第二个 Agent 可能消费第一个 Agent 的工具结果,但这不会让结果自动成为指令。

工具与资源结果可能包含:

  • 提示注入;
  • 攻击者控制的 URL;
  • 超大或递归数据;
  • 带主动内容的 HTML 或文件;
  • 过期或相互冲突的记录;
  • 凭证与个人数据;
  • 冒充策略或审批的文本。

应执行:

  • MIME 与文件类型白名单;
  • 字节、行数、嵌套深度与重定向次数限制;
  • URL 与网络出口策略;
  • 提示词与界面中的内容隔离;
  • 适用场景下的恶意文件与压缩包扫描;
  • 来源与新鲜度元数据;
  • 留存与删除规则。

Host 应把外部内容展示为证据,不能把它合并进可信系统指令。

混淆代理与委派

多 Agent 系统会放大混淆代理(Confused Deputy)问题:高权限执行者可能从低信任规划者收到看似合理的请求,并用更广凭据执行。

防御措施包括:

  1. 保留原始主体与执行者链路;
  2. 为精确服务签发绑定目标受众的短期凭证;
  3. 把权限范围限制在委派任务内;
  4. 由业务服务执行对象级检查;
  5. 禁止 Agent 自行选择身份或目标受众;
  6. 把审批绑定到参数和副作用;
  7. 拒绝令牌透传;
  8. 审计委派与策略决策。

如果使用令牌交换,它必须是带显式策略的身份系统操作,不能把 Bearer Token 直接复制进另一个 Agent 的上下文。

副作用、重试与未知结果

工具调用的失败状态不止“成功”和“错误”:

text
not_started
started
committed
response_lost
outcome_unknown
compensated

对于有副作用的工具:

  • 要求持久化幂等键;
  • 保存请求指纹与终态结果;
  • 区分安全重试与结果对账;
  • 支持按操作 ID 查询状态;
  • 尽可能使用事务或事务发件箱(Outbox);
  • 把补偿与重试分开定义;
  • 不允许模型根据超时推断成功与否。

如果下游已经提交、但执行者在收到响应前超时,必须查询结果。盲目重试可能重复付款、消息、工单或部署。

并发与共享状态

多个 Agent 可能同时修改同一对象。协议消息顺序不是业务并发控制。

应使用负责该对象的系统所提供的机制:

  • 乐观版本号或 ETag;
  • 数据库事务;
  • 比较并交换(CAS);
  • 带清晰有效期的租约或锁;
  • 工作流状态机;
  • 单写者队列;
  • 包含当前状态的冲突响应。

应用应决定 Agent 是否允许并行,以及结果如何合并。“协调者会想办法”不是一致性模型。

控制平面检查表

控制 执行点
令牌签发者、目标受众、有效期 MCP Server / 资源服务器
主体、租户、受委派执行者 可信身份中间件
对象与字段访问 业务服务
工具白名单与参数限制 Host 与工具适配器
审批绑定 应用策略服务
状态转换 负责状态的工作流或数据库
幂等与结果对账 负责副作用的服务
时间、轮次与成本预算 Agent 运行时 / Host
文件系统与网络隔离 沙箱基础设施
结果限制与内容信任 服务端、Host 与渲染器
审计与链路关联 每个边界

重复关键检查可以形成纵深防御,但不要模糊权威归属。拥有数据与副作用的服务始终是最终执行点。

失败测试

至少测试:

场景 期望行为
令牌属于另一目标受众 工具执行前拒绝
跨租户对象 ID 在业务服务拒绝
执行者收到规划者的令牌 拒绝透传或错误目标受众
审批已过期或被篡改 拒绝副作用
响应丢失后的重复调用 返回已记录结果,不重复执行
状态句柄泄露 主体与租户不匹配时拒绝
工具结果中的注入指令 作为数据,不作为策略
两个 Agent 更新同一版本 一个成功,另一个得到明确冲突
任务中途进程重启 从持久化应用状态恢复
取消与提交竞争 返回已定义的对账结果

这些测试必须运行在组合系统上,不能只测试模拟的策略函数。

不依赖隐藏推理的可观测性

使用协议定义的 Trace Context 关联:

text
用户请求 -> Host -> MCP Client -> MCP Server -> 业务服务

记录:

  • 链路与关联 ID;
  • 已认证主体、租户、工作负载、执行者与委派者 ID;
  • 工具名、契约版本与服务版本;
  • 脱敏后的参数指纹;
  • 策略决策与原因码;
  • 审批引用;
  • 操作 ID 与幂等键;
  • 状态转换;
  • 结果与失败类别;
  • 延迟与有界用量。

不要把隐藏思维链留存设为治理要求。它既不是可靠策略记录,也不是隐私安全默认项。可观察动作与显式决策才是审计来源。

常见问题

MCP 是授权或策略引擎吗?

不是。HTTP 授权规范提供 OAuth 边界,但数据所属服务仍须判断:这个主体及执行者,是否可以对当前状态下的这个对象执行这项操作。

MCP 2026-07-28 如何处理状态?

协议核心移除了初始化握手与会话 ID。应用可以用显式句柄保留状态,但每次查找都需要授权、过期检查和合法的状态转换。

能直接把 MCP 访问令牌交给另一个 Agent 吗?

不要用复制 Bearer Token 的方式实现委派。应通过身份系统批准的流程约束执行者、目标受众、权限范围和有效期。MCP 禁止接收面向其他资源的令牌并透传给下游 API。

JSON Schema 能保证工具调用安全吗?

它只能校验已表达的输入约束,不能证明所有权或审批。格式正确的 order_id 仍可能属于另一个租户,执行前必须查询可信记录并授权。

多 Agent 审计应记录什么?

记录已验证身份、委派、策略与审批决策、工具版本、脱敏参数指纹、状态转换和最终结果,并用关联 ID 串联。不需要保存隐藏推理或原始凭证。

总结

MCP 为多 Agent 系统提供标准协议边界。2026-07-28 无状态核心让边界更容易路由、缓存、追踪与扩展;应用需要状态时,可以使用显式句柄。

安全性来自另一个被强制执行的控制平面:绑定目标受众的身份、显式委派、对象级授权、有界审批、事务内状态检查、幂等、并发控制、不可信结果处理,以及可重建的审计记录。

最持久的规则很简单:协议元数据可以描述请求,只有可信应用策略才能授权请求的业务含义与副作用。

一手来源

延伸阅读