接入外部 Agent
外部 Agent 是页面能力的使用者,不是页面的第二套实现。它可能运行在桌面应用、另一个浏览器标签页、MCP 宿主或企业编排服务中。它得到的是一份有边界的页面描述,以及页面愿意执行的能力清单。
当前协议实现两个方法:describe 读取能力面与当前上下文,invoke 请求页面执行一个已暴露的能力。绑定、持续观察和传输由宿主负责。因此,在标准传输尚未形成之前,连接也可以先跑通;但仅仅复制一份 JSON 形状,并不等于完成了一次可上线的接入。
三个参与者
生产连接包含三种不同职责。
| 角色 | 负责 | 不应负责 |
|---|---|---|
| 页面 | 声明、当前上下文、执行、业务状态 | 外部 Agent 的模型、提示词或推理循环 |
| Surface 边界 | 白名单交集、协议校验、限额、审计元数据 | 第二套权限或执行引擎 |
| 宿主传输 | 身份验证、委托会话、请求投递、撤销 | 凭“已声明”就默认允许调用全部动作 |
页面仍是“自己能做什么”的权威来源。宿主证明调用者是谁。Surface 把站点上限与本次委托范围取交集,再把通过的调用交给现有执行器。
连接过程
第一个请求读取页面:
{
"protocol": "pling.surface/0.1",
"id": "req-1",
"method": "describe",
"input": { "limit": 8 }
}响应包含页面身份、区域、有界记录、状态、页面反馈、不可用能力,以及当前可调用的能力。长列表按窗口返回;记录 id 可以覆盖活跃区域内的全部记录,但字段只返回一页。
随后,Agent 用稳定 id 调用一个能力:
{
"protocol": "pling.surface/0.1",
"id": "req-2",
"method": "invoke",
"capability": "todo.complete",
"input": { "record": "task-42" }
}响应把协议失败与业务结果分开。未暴露的能力在执行前被拒绝;合法调用仍可能返回动作不可用、审批被拒、参数校验失败或成功判据未满足。它们是不同事实,客户端不能都显示成“助手失败了”。
身份验证不属于协议内核
Surface 不读取 Cookie、Bearer token、OAuth 授权或委托凭证。传输层先验证请求,再把会话交给协议内核,其中包括 Agent id、用户 id、会话 id、有效期、撤销状态和允许范围。配置了会话后,会话缺失、过期、已撤销或越过范围都会 fail-closed。
这种分工是必要的:浏览器 Cookie 证明的是浏览器会话,不是自主 Agent 的身份。审计必须能同时回答“谁授权了这件事”和“哪个 Agent 执行了它”。传输层若把两种身份合并成一个字符串,协议试图保留的区别就消失了。
MCP 不是协议本身
MCP 是一种可能的绑定,不是 Surface 协议本身。浏览器扩展可以用自己的消息通道传输同样的请求;企业网关可以提供 HTTP 端点;桌面宿主可以调用本地桥。每种绑定仍须定义身份验证、防重放、限流、审计投递与撤销。
不要把请求体里的 userId 当作登录身份。不要把模型供应商密钥放进外部 Agent 的页面上下文。不要把本机演示桥描述成生产网关。协议面保持很小,是为了让这些选择显式可见,而不是被一个 SDK 调用藏起来。
安全地暴露能力
显式白名单是站点上限,不是方便列表。一个租户可以只开放订单查询而不开放审批;单次委托还可以更窄。能力名应表达业务意图,行内动作应使用有界模板,而不是为每个渲染行生成一条工具。
开放连接前,至少验证四种情况:
- 白名单之外的能力在
describe中不可见,在invoke中被拒绝。 - 会话缺失或过期时,执行器和业务函数都没有被调用。
- 危险动作仍然进入页面真实的确认路径。
- 在防重放窗口内,同一个请求 id 不会造成第二次业务副作用。
下一步是定义能力面本身。参见 设计安全的能力与 Surface 基础:describe 与 invoke。

