SEP-1036:用于安全带外交互的 URL 模式信息征询
社区译文;来源: MCP 29affa6b
| 字段 | 值 |
|---|---|
| SEP | 1036 |
| 标题 | 用于安全带外交互的 URL 模式信息征询 |
| 状态 | Final |
| 类型 | 标准跟踪 |
| 创建日期 | 2025-07-22 |
| 作者 | Nate Barbettini (@nbarbettini) 和 Wils Dawson (@wdawson) |
| 发起人 | 无 |
| PR | #1036 |
摘要
本 SEP 为现有的信息征询客户端能力引入新的 url 模式,使系统能够绕过 MCP 客户端进行安全的带外交互。URL 模式信息征询可处理表单模式无法处理的敏感场景,例如收集敏感凭据、为外部(第三方)授权执行 OAuth 流程以及处理付款,同时不向 MCP 客户端暴露敏感数据。该模式将用户引导至浏览器中的可信 URL,在支持与第三方服务进行丰富集成的同时维持安全边界。
动机
当前 MCP 规范(2025-06-18)提供了一种信息征询机制,通过结构化的带内请求向用户收集非敏感信息(最常见的设想是由 MCP 客户端呈现表单,向最终用户收集数据)。然而,若干关键用例要求交互不得经过 MCP 客户端:
- 敏感数据收集:API 密钥、密码和其他凭据绝不能经过中间系统传输。
- 外部授权:MCP 服务器经常需要代表用户访问第三方 API。MCP 授权规范只覆盖客户端到服务器的授权,不覆盖服务器到第三方的授权。安全最佳实践明确禁止令牌透传,因此外部(第三方)OAuth 流程需要安全机制。这是从 #234 和 #284 的讨论中产生的一项尤为重要的动机。
- 付款和订阅流程:金融交易要求符合 PCI 标准并使用安全的付款处理方式,而带内数据收集无法满足这些要求。
如果没有标准化机制来完成这些交互,MCP 服务器只能采用非标准变通方案,或使用不安全的做法,例如通过带内表单式信息征询请求 API 密钥。本 SEP 引入 URL 信息征询模式,利用成熟的 Web 安全模式来安全处理敏感交互,以弥补这些缺口。
URL 信息征询与 MCP 授权有本质区别。URL 信息征询并非用于授权 MCP 客户端访问 MCP 服务器(这由 MCP 授权直接处理)。它用于 MCP 服务器需要代表用户取得敏感信息或第三方授权的场景。MCP 客户端的 bearer token 保持不变;客户端唯一的职责是向用户提供上下文,说明服务器希望其打开哪个信息征询 URL。
规范
概述
信息征询更新为支持两种模式:
- 表单模式(带内):服务器可以向用户请求结构化数据,并可选择使用 JSON schema 验证响应(除为现有能力增加名称外,没有其他变化)
- URL 模式(带外):服务器可以将用户引导至外部 URL,以完成不得经过 MCP 客户端的敏感交互
能力
支持信息征询的客户端在初始化期间 MUST 声明 elicitation 能力:
{
"capabilities": {
"elicitation": {
"form": {},
"url": {}
}
}
}为了向后兼容,空能力对象等同于仅声明支持 form 模式:
{
"capabilities": {
"elicitation": {},
},
}声明 elicitation 能力的客户端 MUST 至少支持一种模式(form 或 url)。
表单信息征询请求
相对于现有规范,唯一变化是在 elicitation/create 请求中增加 mode 字段:
{
"jsonrpc": "2.0",
"id": 1,
"method": "elicitation/create",
"params": {
"mode": "form", // New field
"message": "Please provide your GitHub username",
"requestedSchema": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": ["name"]
}
}
}URL 信息征询请求
URL 信息征询请求 MUST 指定 mode: "url" 并包含以下参数:
| 名称 | 类型 | 说明 |
|---|---|---|
url | string | 用户应前往的 URL。 |
elicitationId | string | 本次信息征询的唯一标识符。 |
message | string | 说明为何需要此交互的人类可读消息。 |
示例:OAuth 授权流程
{
"jsonrpc": "2.0",
"id": 3,
"method": "elicitation/create",
"params": {
"mode": "url",
"elicitationId": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://github.com/login/oauth/authorize?client_id=abc123&state=xyz789&scope=repo",
"message": "Please authorize access to your GitHub repositories to continue."
}
}响应操作
URL 信息征询响应使用与表单信息征询相同的三操作模型:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"action": "accept" // or "decline" or "cancel"
}
}action: "accept" 响应表示用户已同意进行交互。交互在带外发生;除非服务器发送完成通知,否则客户端不知道交互结果。
完成通知
由 URL 模式信息征询发起的带外交互完成后,服务器 SHOULD 发送 notifications/elicitation/complete 通知。这样客户端便可在适当时以编程方式作出反应。
- 该通知 MUST 只发送给发起信息征询请求的客户端。
- 该通知 MUST 包含原始
elicitation/create请求中建立的elicitationId。 - 客户端 MUST 忽略引用未知 ID 或已完成 ID 的通知。
- 如果一直没有收到完成通知,客户端 SHOULD 为用户提供手动继续交互的方法。
客户端 MAY 使用该通知自动重试因要求 URL 信息征询而收到错误的请求、更新用户界面或以其他方式继续交互。但是,由于无法保证通知一定送达,客户端不得无限期等待服务器通知。
{
"jsonrpc": "2.0",
"method": "notifications/elicitation/complete",
"params": {
"elicitationId": "550e8400-e29b-41d4-a716-446655440000"
}
}要求 URL 信息征询的错误
当请求必须等到信息征询完成后才能处理时,服务器 MAY 返回 URLElicitationRequiredError(错误码 -32042),表明需要 URL 模式信息征询。除非用户交互确实需要 URL 模式信息征询,否则服务器 MUST NOT 返回此错误。
{
"jsonrpc": "2.0",
"id": 2,
"error": {
"code": -32042,
"message": "This request requires more information.",
"data": {
"elicitations": [
{
"mode": "url",
"elicitationId": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://oauth.example.com/authorize?client_id=abc123&response_type=code&...",
"message": "Authorization is required to access your Example Co files."
}
]
}
}
}错误中返回的所有信息征询 MUST 都是 URL 模式信息征询,并包含 elicitationId。
返回 URLElicitationRequiredError 等同于发送 elicitation/create 请求。服务器可以返回错误(而不是另行发送 elicitation/create 请求),以便客户端明确知道某次信息征询与某个失败的客户端请求直接相关。
客户端必须将 URLElicitationRequiredError 响应视为等同于 elicitation/create 请求。例如,信息征询成功完成后,客户端可以自动重试失败的请求;收到完成通知后进行重试就是一种方式。
理由
设计决策
为何扩展信息征询,而不是创建新机制?
我们最初考虑为带外交互创建独立机制(见 #475 中的讨论)。但与 MCP 维护者讨论后,我们决定扩展现有信息征询规范,原因如下:
- 两种机制服务于同一个根本目的:向用户收集信息
- 为同一目的设置两个相似但相互独立的机制会令人困惑并容易出错
mode参数能够清晰地区分两种交互模式
为什么不能由客户端自行执行交互?
一种看似合理的建议是让 MCP 客户端自行执行交互,例如充当第三方授权服务器的 OAuth 客户端。然而,这不是一个好主意,原因有多项:
- 如果 MCP 客户端从第三方授权服务器取得用户令牌,MCP 服务器就会成为令牌透传服务器,而这是明确禁止的。
- 同样,对于付款类流程,MCP 客户端将需要执行符合 PCI 标准的付款处理,而这不应成为 MCP 客户端的要求。
为什么服务器不阻塞(等待)信息征询完成?
URL 模式信息征询请求按设计属于异步或“断开式”流程,因为其支持的交互本身就是异步的。付款流程、外部授权等可能需要数分钟甚至更长时间才能完成;在某些情况下,如果最终用户放弃操作,它们永远不会完成。
为什么禁止在表单模式中使用 URL?
非常明确地规定何时可以(以及何时不可以)在信息征询请求中发送 URL,可以改善客户端的安全态势。规范明确规定 URL 只能 出现在 URL 模式信息征询请求的 url 字段中,客户端实现者便能实现与安全模型一致的用户体验。例如,客户端可以拒绝把表单模式信息征询请求中的 URL 渲染为可点击的超链接,从而降低用户点击恶意服务器所发恶意 URL 的可能性。
考虑过的替代方案
令牌透传:由于安全最佳实践中记录的安全问题,直接将 MCP 客户端的令牌传递给外部服务的方案被否决。让 MCP 客户端获取其他令牌再传递给 MCP 服务器的方案也因同样原因被否决。
OAuth 专用能力:曾考虑创建专用于 OAuth 外部(第三方)授权的能力,但最终选择更通用的 URL 模式信息征询方案,因为它可以支持多种用例。
社区反馈
本提案吸收了 #475、#234 和 #284 讨论以及 Discord 上 #auth-wg 工作组的大量社区反馈。社区指出需要:
- 不向客户端暴露数据的安全凭据收集方式
- 与 MCP 授权分离的外部授权模式
- 对付款和订阅流程的支持
- 清晰的安全边界和信任模型
向后兼容性
本 SEP 引入以下破坏性变更:
能力声明:客户端现在必须指定其支持哪些信息征询模式:
{ "capabilities": { "elicitation": { "form": {}, "url": {} } } }以前,客户端只声明
"elicitation": {},不指定模式。模式参数:所有
elicitation/create请求现在都必须包含mode参数("form"或"url")。
迁移路径
为简化迁移:
- 服务器 SHOULD 在发送特定模式的请求前检查客户端能力
- 客户端 MAY 最初只支持表单模式,以维持兼容性
- 现有表单信息征询实现添加 mode 参数后可继续工作
参考实现
TypeScript 客户端/服务器实现:feat/url-elicitation
讲解视频:https://drive.google.com/file/d/1llCFS9wmkK_RUgi5B-zHfUUgy-CNb0n0/view?usp=sharing
安全影响
本 SEP 引入若干安全注意事项:
URL 安全要求
- 防止 SSRF:客户端必须验证 URL,以防止服务器端请求伪造攻击
- 协议限制:URL 信息征询只允许使用 HTTPS URL
- 域名验证:客户端必须向用户清楚显示目标域名
信任边界
URL 信息征询显式建立了清晰的信任边界:
- MCP 客户端永远不会看到 MCP 服务器通过 URL 信息征询取得的敏感数据
- MCP 服务器必须独立验证用户身份
- 第三方服务通过安全的浏览器上下文直接与用户交互
身份验证
服务器必须验证完成 URL 信息征询的用户与发起请求的用户是同一人。验证用户身份时不得依赖来自客户端的不可信输入(例如用户输入)。
实现要求
客户端必须:
- 使用可防止检查用户输入的安全浏览器上下文
- 验证 URL 以防止 SSRF
- 打开 URL 前取得用户明确同意
- 清楚显示目标域名
服务器必须:
- 将信息征询状态绑定到已认证的用户会话
- 在 URL 信息征询流程开始和结束时验证用户身份
- 实施适当的速率限制
双方应当:
- 记录安全事件以供审计
- 为信息征询请求实施超时机制
- 针对安全失败提供清晰的错误消息
与现有安全措施的关系
本提案以现有 MCP 安全措施为基础并予以补充:
- 在现有 MCP 授权框架内工作(本提案不影响 MCP 授权)
- 遵循有关令牌处理的安全最佳实践
- 保持客户端到服务器授权与服务器到第三方授权之间的职责分离