SEP-991:使用 OAuth 客户端 ID 元数据文档启用基于 URL 的客户端注册

SEP-991:使用 OAuth 客户端 ID 元数据文档启用基于 URL 的客户端注册

社区译文;来源: MCP 29affa6b

本 SEP 已达到 Final 状态,并作为已接受设计的历史记录保留。 定稿后对协议所做的更改不会反映在此处。权威要求请参阅当前规范 及其变更日志。
字段值
SEP991
标题使用 OAuth 客户端 ID 元数据文档启用基于 URL 的客户端注册
状态Final
类型标准跟踪
创建日期2025-07-07
作者Paul Carleton (@pcarleton) Aaron Parecki (@aaronpk)
发起人无
PR#991

摘要

本 SEP 提议采用 draft-parecki-oauth-client-id-metadata-document-03 定义的 OAuth 客户端 ID 元数据文档,作为模型上下文协议(MCP)的一种额外客户端注册机制。该方式允许 OAuth 客户端使用 HTTPS URL 作为客户端标识符;此 URL 指向包含客户端元数据的 JSON 文档。它专门解决 MCP 中服务器与客户端之间没有预先建立关系的常见场景,使服务器无需预先协调即可信任客户端,同时仍能完全控制访问策略。

动机

模型上下文协议目前支持两种客户端注册方式:

  1. 预注册:要求客户端开发者或用户在每台服务器上手动注册客户端
  2. 动态客户端注册(DCR):通过将客户端元数据发送到授权服务器的注册端点,实现即时注册。

对于客户端经常需要连接此前从未遇到过的服务器这一 MCP 使用场景,两种方式都有明显限制:

  • 由开发者预注册并不现实,因为客户端发布时,相关服务器可能尚不存在
  • 由用户预注册会造成糟糕的用户体验,需要手动管理凭据
  • DCR 要求服务器管理无界数据库、处理过期,并信任客户端自行声明的元数据

目标使用场景:没有预先建立关系

本提案专门针对以下常见 MCP 场景:

  • 用户希望将客户端连接到其发现的一台服务器
  • 客户端开发者从未听说过这台服务器
  • 服务器运营者从未听说过这个客户端
  • 双方需要在没有事先协调的情况下建立信任

对于已经存在关系的场景,预注册仍是最佳方案。但是,MCP 的价值来自连接任意客户端和服务器的能力,因此必须解决“没有预先建立关系”的场景。

此外,MCP 服务器的数量远多于客户端(类似于 Web 浏览器远少于 API)。一种常见场景是 MCP 服务器开发者希望将使用权限限制为其信任的一组客户端。

关键创新:无需预先协调、由服务器控制的信任

客户端 ID 元数据文档提供了一种独特的信任模型:

  1. 服务器可以信任此前从未见过的客户端,依据包括:

    • 托管元数据的 HTTPS 域名
    • 元数据内容本身
    • 域名信誉和安全策略
  2. 服务器通过灵活策略保持完全控制权:

    • 开放服务器:可以接受任意 HTTPS client_id,实现最大程度的互操作性
    • 受保护服务器:可以仅允许受信任域名或特定客户端
  3. 不需要客户端预先协调:

    • 客户端无需提前知道服务器
    • 客户端只需托管自己的元数据文档
    • 信任来自客户端域名,而不是事先注册

规范变更

规范将新增客户端 ID 元数据文档,并将其设为 SHOULD;同时将 DCR 改为 MAY,因为我们认为,在此场景中,客户端 ID 元数据文档是更好的默认选项。

我们将主要依赖所链接 RFC 中的文本,尽量不重复其大部分内容。下面简要说明需要规定的内容。

    sequenceDiagram
      participant User as 用户
      participant Client as MCP 客户端
      participant Server as 授权服务器
      participant Metadata as 元数据端点<br/>(客户端的 HTTPS URL)
      participant Resource as MCP 服务器

      Note over Client,Metadata: 客户端在以下位置托管元数据:<br/>https://app.example.com/oauth/metadata.json

      User->>Client: 发起到 MCP 服务器的连接
      Client->>Server: 授权请求<br/>client_id=https://app.example.com/oauth/metadata.json<br/>redirect_uri=http://localhost:3000/callback

      Note over Server: 对用户进行身份验证


      Note over Server: 检测 URL 格式的 client_id

      Server->>Metadata: GET https://app.example.com/oauth/metadata.json
      Metadata-->>Server: JSON 元数据文档<br/>{client_id, client_name, redirect_uris, ...}

      Note over Server: 验证:<br/>1. client_id 与 URL 一致<br/>2. redirect_uri 位于允许列表中<br/>3. 文档结构有效<br/>4. 信任策略允许该域名

      alt 验证成功
          Server->>User: 显示包含 client_name 的同意页面
          User->>Server: 批准访问
          Server->>Client: 通过 redirect_uri 返回授权码
          Client->>Server: 使用授权码交换 token<br/>client_id=https://app.example.com/oauth/metadata.json
          Server-->>Client: access token
          Client->>Resource: 携带 access token 的 MCP 请求
          Resource-->>Client: MCP 响应
      else 验证失败
          Server->>User: 错误响应<br/>error=invalid_client or invalid_request
      end

      Note over Server: 缓存元数据供后续请求使用<br/>(遵循 HTTP 缓存标头)

客户端要求

  • 客户端 MUST 按照 RFC 要求,在 HTTPS URL 上托管其元数据文档
  • client_id URL MUST 使用 https scheme,并且包含 path component
  • 元数据文档 MUST 是有效 JSON,并至少包含:
    • client_id:与文档 URL 完全一致
    • client_name:用于授权提示的可读名称
    • redirect_uris:允许的重定向 URI 数组
    • token_endpoint_auth_method:公共客户端使用 "none"

请注意,由于客户端元数据可以提供公钥信息,客户端可以为 token_endpoint_auth_method 使用 private_key_jwt。

服务器要求

  • 服务器遇到 URL 格式的 client_id 时,SHOULD 获取元数据文档
  • 服务器 MUST 验证所获取文档包含匹配的 client_id
  • 服务器 SHOULD 遵循 HTTP 标头缓存元数据(建议最长 24 小时)
  • 服务器 MUST 验证重定向 URI 与元数据文档中列出的 URI 匹配

发现

  • 服务器通过 OAuth 元数据声明支持:client_id_metadata_document_supported: true
  • 客户端检测支持情况;如果不支持,可以回退到 DCR 或预注册

元数据文档示例:

{
  "client_id": "https://app.example.com/oauth/client-metadata.json",
  "client_name": "Example MCP Client",
  "client_uri": "https://app.example.com",
  "logo_uri": "https://app.example.com/logo.png",
  "redirect_uris": [
    "http://127.0.0.1:3000/callback",
    "http://localhost:3000/callback"
  ],
  "grant_types": ["authorization_code"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}

与现有 MCP 授权集成

本提案在预注册和 DCR 之外,新增客户端 ID 元数据文档作为第三种注册选项。服务器 MAY 支持这些方式的任意组合:

  • 预注册保持不变
  • DCR 保持不变
  • 通过 URL 格式的 client_id 检测客户端 ID 元数据文档,并在 OAuth 元数据中声明服务器支持情况。

理由

为何能解决“没有预先建立关系”的问题

预注册需要协调,DCR 要求服务器管理注册数据库;与二者不同,客户端 ID 元数据文档提供:

  1. 可验证的身份:HTTPS URL 同时充当标识符和信任锚点
  2. 无需协调:客户端发布元数据,服务器使用元数据
  3. 灵活的信任策略:服务器自行决定信任标准,无需客户端做出更改
  4. 稳定的标识符:与 DCR 的临时 ID 不同,URL 稳定且可审计

重定向 URI 证明

客户端 ID 元数据文档的一项关键优势是可以证明重定向 URI:

  1. 元数据文档通过 HTTPS,以密码学方式将重定向 URI 绑定到客户端身份
  2. 服务器可以相信元数据中的重定向 URI 由客户端控制,而不是由攻击者提供
  3. 这可以防止自行声明注册中常见的重定向 URI 操纵攻击

此方式的风险

风险:localhost URL 冒充

客户端 ID 元数据文档的一项局限是,它本身无法防止 localhost URL 冒充。攻击者可以通过以下方式冒充任意客户端:

  1. 将合法客户端的元数据 URL 作为自己的 client_id
  2. 绑定到合法客户端使用的同一 localhost 端口
  3. 在用户批准时截获授权码

这种攻击值得关注,因为服务器看到的是正确的元数据文档,用户看到的也是正确的客户端名称,因此很难发现。

特定于平台的证明(iOS DeviceCheck、Android Play Integrity)可以解决这一问题,但它们并非在所有平台上都可用。实现方式可以是:开发者运行一个后端服务,接收 DeviceCheck / Play Integrity 签名,并返回可用作 token_endpoint_auth_method 的 private_key_jwt 身份验证凭据的 JWT。

另一种不要求特定平台证明、但仍能提高攻击成本的相似方式,是使用 JWKS 和由客户端开发者托管的服务器端组件签发的短期 JWT。该组件可以使用特定平台机制以外的证明机制来证明客户端身份,例如客户端的标准登录流程。短期 JWT 可以降低凭据泄露和重放的风险,但无法完全消除风险:攻击者仍可将请求代理到合法客户端的签名端点。

完全缓解此风险不在本提案的范围内。在 localhost 重定向场景中,本提案具有与 DCR 相同的风险。

服务器 SHOULD 对仅使用 localhost 的客户端显示额外警告。

风险:服务器端请求伪造(SSRF)

授权服务器接收未知客户端提供的 URL,然后获取该 URL。恶意客户端可能利用这一点,让授权服务器代其发送非元数据请求。例如,提供一个 URL,指向授权服务器能够访问的私有管理端点。

可以在发起获取请求前验证 URL 及其解析到的 IP,以防止此问题。

风险:分布式拒绝服务(DDoS)

类似地,攻击者可能尝试利用一组授权服务器,对非 MCP 服务器发起拒绝服务攻击。

获取请求不会产生额外放大(即客户端发出请求所需的带宽,与发送到目标服务器的请求带宽大致相当),而且每台授权服务器都可以积极缓存元数据获取结果,因此它不太可能成为有吸引力的 DDoS 攻击向量。

风险:所引用规范的成熟度

客户端 ID 元数据文档 RFC 仍处于草案阶段。Bluesky 平台已经实现该草案,但它尚未正式批准,在 Bluesky 之外也未得到广泛采用,并且可能会随时间演进。我们的意图是随后的草案和最终标准一同演进并保持一致,同时尽量减少对现有实现的干扰和破坏。

这种方式存在尚未暴露的实现难题或协议缺陷风险。不过,虽然 DCR 已获正式批准,它同样存在许多实现难题;开发者尝试在 MCP 这样的开放生态环境中使用 DCR 时,正在面对这些问题。这些难题正是本提案的动机。

风险:客户端实现负担,尤其是本地客户端

本规范要求客户端增加一项基础设施,因为客户端需要在 HTTPS URL 后托管元数据文件。如果没有本规范,例如客户端可以完全是桌面应用程序。

预计托管该端点的负担较低,因为托管静态 JSON 文件相当直接,并且大多数已知客户端本来就有用于介绍客户端或提供下载链接的网页。

风险:授权方式碎片化

客户端和服务器要完整实现 MCP 授权已经很有挑战。如何正确实现授权及其最佳实践,是社区中最常见的问题之一。为授权流程增加一个分支,可能使其更加复杂和碎片化,导致更少的开发者能够成功遵循规范,并最终损害兼容性和开放生态系统的愿景。

本提案旨在为授权服务器和资源服务器开发者提供更清晰的重定向 URI 信任机制并降低运维开销,从而简化授权方案。本提案能否成立,取决于这种简化是否明确成为大多数人的更佳选择;这将推动更广泛的采用,并最终使其成为支持最广泛的选项。如果我们不认为它明显更好,就不应采用本提案。

本提案还为开放服务器和希望限制可用客户端的服务器提供了统一机制。其他方案要求客户端和服务器针对开放与受保护场景实现不同机制。

考虑过的替代方案

  1. 使用软件声明增强 DCR:更复杂,需要托管 JWKS 并签名 JWT
  2. 强制预注册:在 MCP 的分布式生态中,会造成糟糕的开发者和用户体验
  3. 双向 TLS:要求信任客户端证书颁发机构,在开放生态中并不现实
  4. 维持现状:服务器实现者将继续面对当前痛点

对于最常见的开放生态使用场景,客户端 ID 元数据文档严格优于 DCR。将来还可以进一步扩展,以更好地支持操作系统级证明和 jwks_uri 等能力。

向后兼容性

本提案完全向后兼容:

  • 现有预注册客户端继续工作,无需更改
  • 现有 DCR 实现继续工作,无需更改
  • 服务器可以逐步采用客户端 ID 元数据文档
  • 客户端可以检测支持情况,并回退到其他方式

原型实现

此处提供了一个原型实现,演示:

  1. 客户端侧元数据文档托管
  2. 服务器侧元数据获取和验证
  3. 与现有 MCP OAuth 流程集成
  4. 正确的错误处理和回退行为

安全影响

  1. 防范网络钓鱼:醒目显示客户端主机名
  2. SSRF 防护:验证 URL、限制响应大小、设置请求超时并限制出站请求速率

最佳实践

  • 仅在对用户完成身份验证后获取客户端元数据
  • 对出站元数据获取实施速率限制
  • 考虑针对新的、未知的或 localhost 域名显示额外警告
  • 记录元数据获取失败,以便监控

参考资料