·MCP 服务器
基于 2025-11-25 版 MCP 授权规范:Protected Resource Metadata 发现链、CIMD 与动态客户端注册的新优先级、RFC 8707 resource 参数绑定、audience 校验与 step-up 授权,外加症状导向的 OAuth 排错与三个端到端集成案例。2026 年 9 月更新。
MCP 授权与 OAuth 2.1 完全指南:发现、注册与步进授权(2026)
MCP 授权(MCP Authorization)是 Model Context Protocol 为 HTTP 传输定义的 OAuth 2.1 鉴权框架:MCP 服务器扮演资源服务器(resource server),用访问令牌保护自己的端点,而 stdio 本地传输按规范不应使用这套流程。2025-11-25 版规范重写了客户端注册机制——Client ID Metadata Documents(CIMD)上位为主路径,动态客户端注册(DCR)降级为兼容选项——导致 2025 年写的大部分 MCP OAuth 教程已经过时。本文基于 2025-11-25 版授权规范,把发现链、三种注册方式、PKCE + resource 参数的完整授权流程、服务端校验义务与步进授权一次讲清。(刚接触 MCP?先读什么是 MCP;两种传输的取舍见 MCP 传输方式对比。)
TL;DR
- MCP 服务器是 OAuth 2.1 里的资源服务器;stdio 传输从环境变量取凭证,不走这套流程
- 发现链:
401 + WWW-Authenticate→ Protected Resource Metadata(RFC 9728,MCP 服务器 MUST 实现)→ 授权服务器元数据(RFC 8414 / OIDC Discovery,二选一都 MUST 支持)- 2025-11-25 关键变化:CIMD 成为主注册方式(SHOULD),DCR 降为 MAY(仅为向后兼容)
resource参数(RFC 8707)在授权与令牌请求中都必须带,用于把令牌绑定到目标 MCP 服务器- MCP 服务器 MUST 校验令牌 audience,且 MUST NOT 把客户端令牌透传给上游 API
- 运行时权限不足走 step-up:
403 + insufficient_scope→ 带新 scope 重新授权 → 重试要有上限
2025-11-25 版规范改了什么
MCP 授权规范经历过三个公开版本,客户端注册机制的变化最大——这也是老教程失效的根源:
| 机制 | 2025-06-18 版 | 2025-11-25 版 |
|---|---|---|
| 动态客户端注册(DCR,RFC 7591) | SHOULD 支持(实际上的主路径) | MAY 支持,仅为向后兼容保留 |
| Client ID Metadata Documents(CIMD) | 不存在 | SHOULD 支持,无预先关系场景的首选 |
| 受保护资源元数据的发现方式 | 仅 WWW-Authenticate 头 | WWW-Authenticate 头 + well-known URI 回退(两种都 MUST 支持) |
| 授权服务器元数据 | 仅 RFC 8414 | RFC 8414 与 OIDC Discovery 兼容,客户端 MUST 都支持 |
| 步进授权(step-up) | 无正式流程 | 正式定义:403 + insufficient_scope → 重新授权 → 重试上限 |
如果你在 2025 年写过「先 POST /register 拿 client_id」的 MCP 客户端,按新版规范它依然能工作(DCR 是合法的回退路径),但主路径已经换了:客户端应先在授权服务器元数据里查 client_id_metadata_document_supported,为真则走 CIMD。
为什么远程 MCP 服务器需要 OAuth
MCP 把「要不要鉴权」留给实现,但定了基调:授权是可选能力(OPTIONAL),一旦支持,HTTP 传输 SHOULD 遵循本规范。两种传输的凭证模型完全不同:
| 维度 | stdio(本地进程) | Streamable HTTP(远程) | |---|---|---| | 凭证来源 | 环境变量(如 API key) | OAuth 2.1 访问令牌 | | 规范立场 | SHOULD NOT 使用本授权规范 | SHOULD 遵循本授权规范 | | 典型威胁 | 本机进程越权读文件 | 令牌泄露、跨服务重放、混淆代理 | | 用户在场 | 无浏览器可用 | 有浏览器可完成授权码流程 |
远程场景的本质变化是:MCP 服务器不再跑在用户机器上,它要代表「资源所有者」判断请求合法性,这正是 OAuth 2.1 的资源服务器角色。规范同时要求:所有授权服务器端点 MUST 走 HTTPS,重定向 URI MUST 是 localhost 或 HTTPS。
发现链:从 401 到授权服务器
客户端第一次连一个受保护的 MCP 服务器时,什么配置都没有——整套机制从一次 401 开始,共四步:
- 客户端不带令牌请求 MCP 端点,服务器返回
401 Unauthorized,WWW-Authenticate头里带resource_metadataURL; - 客户端拉取该 URL,得到 Protected Resource Metadata(RFC 9728),其中
authorization_servers字段列出一个或多个授权服务器; - 客户端按优先级尝试授权服务器元数据端点(带路径的 issuer 依次试:
/.well-known/oauth-authorization-server/<path>→/.well-known/openid-configuration/<path>→/<path>/.well-known/openid-configuration;不带路径依次试前两者的根形式); - 拿到元数据后按 OAuth 2.1 流程走授权码 + PKCE。
401 响应长这样(规范原文示例):
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
scope="files:read"
两个实现细节容易踩坑:其一,WWW-Authenticate 头里的 scope 参数是客户端选 scope 的第一优先来源(见下文 scope 策略);其二,若无头信息,客户端 MUST 按固定顺序探测 well-known URI——先 MCP 端点的路径形式,再根形式。发现逻辑写错的表现是「某些服务器连得上、某些连不上」。
客户端注册:CIMD 为主,DCR 退居兼容
「客户端怎么拿到 client_id」是 2025-11-25 版改动最大的部分。MCP 定义了三种注册机制,支持全部三种的客户端 SHOULD 按此优先级选择:
| 方式 | 适用场景 | 规范立场(2025-11-25) |
|---|---|---|
| 预注册(pre-registration) | 客户端与服务器有既有合作关系 | 优先级 1:有就用 |
| Client ID Metadata Documents | 无预先关系(最常见) | 优先级 2:服务器元数据声明 client_id_metadata_document_supported: true 时使用 |
| 动态客户端注册(DCR,RFC 7591) | 向后兼容或特殊需求 | 优先级 3:回退路径,服务器元数据里有 registration_endpoint 才可用 |
CIMD 的核心想法:用 HTTPS URL 当 client_id。客户端把一份 JSON 元数据文档托管在自己的域名下,授权服务器遇到 URL 形态的 client_id 时主动拉取、校验、展示:
{
"client_id": "https://app.example.com/oauth/client-metadata.json",
"client_name": "Example MCP Client",
"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"
}
硬性校验规则:文档里的 client_id MUST 与其 URL 精确一致;client_id MUST 是带路径的 HTTPS URL;授权服务器 MUST 校验授权请求里的 redirect_uri 确实出现在文档的 redirect_uris 里。文档必备字段是 client_id、client_name、redirect_uris 三个。
CIMD 也有自己的安全面:授权服务器要防 SSRF(拉取 URL 前考虑私有地址过滤),且 CIMD 防不了 localhost 冒充——攻击者可以拿着合法客户端的元数据 URL、绑定任意本机端口领授权码。因此规范建议服务器对「仅 localhost 重定向」的请求展示额外警告,并 MUST 在授权页显著展示重定向 URI 的主机名。
授权流程:PKCE、resource 参数与 scope 策略
三种注册方式都会汇入同一条授权码流程。完整顺序(规范原图的文字版):
- 生成 PKCE 参数(
code_verifier+S256的code_challenge); - 打开浏览器,发起授权请求,带
code_challenge和resource参数; - 用户在授权服务器完成认证与同意,服务器按
redirect_uri回传授权码; - 客户端用授权码 +
code_verifier+resource换取访问令牌(可选 refresh token); - 客户端带
Authorization: Bearer <token>头调用 MCP 服务器——每个请求都要带,包括同一逻辑会话内的后续请求;令牌 MUST NOT 出现在 URI 查询串里。
PKCE 是 MUST,且客户端 MUST 用 S256 方法。规范还加了一条防御:客户端 MUST 先在服务器元数据里确认 code_challenge_methods_supported 存在——字段缺失说明服务器不支持 PKCE,客户端 MUST 拒绝继续,而不是静默降级成裸授权码流程。
resource 参数来自 RFC 8707(Resource Indicators),作用是把令牌绑定到目标 MCP 服务器:授权请求和令牌请求都 MUST 携带,值是 MCP 服务器的规范 URI——小写 scheme 与 host、可带端口、不带 fragment,如 https://mcp.example.com 或 https://mcp.example.com/server/mcp;按惯例不带尾斜杠。无论授权服务器是否声明支持,客户端 MUST 照发——这是令牌 audience 绑定的前提。
scope 的选择也有既定优先级:先用 401 响应 WWW-Authenticate 里的 scope 参数;没有再用 Protected Resource Metadata 的 scopes_supported(连它也没有就省略 scope 参数)。初始授权只要最小集合,之后靠 step-up 按需增权——这符合最小权限原则,也避免首屏授权弹一长串权限。
服务端义务:audience 校验与 token passthrough 禁令
MCP 服务器(资源服务器)一侧的硬性义务是整份规范里安全浓度最高的部分:
- 每个令牌都校验 audience。 服务器 MUST 按 RFC 8707 校验令牌确实是签给「本服务器」的;audience 不符或令牌过期 MUST 回 401。只验签名不验 audience 等于没设防——别的服务签发的合法令牌照样能进你的门。
- 禁止令牌透传。 如果 MCP 服务器自己要调上游 API(如用 GitHub API 实现 git 工具),它对上游是另一个 OAuth 客户端,应该向上游授权服务器单独申请令牌;把客户端给它的令牌原样转发给上游是 MUST NOT。这就是「混淆代理」(confused deputy)问题:下游会把透传的令牌误认为 MCP 服务器的身份,审计与权限边界同时失守。
- 对静态 client_id 的代理服务器:MCP 代理 MUST 在转发前为每个动态注册的客户端取得用户同意。
- 错误码约定:401 = 未认证或令牌无效,403 = scope 不足,400 = 请求格式错误。
安全最佳实践文档把 token passthrough 列为头号反模式,并给出与本文一致的建议:audience 校验 + resource 参数 + 禁止透传,三者共同构成令牌边界。
运行时权限升级:step-up authorization
用户已经授权过、令牌还有效,但某个操作需要新权限——2025-11-25 版为此定义了正式的步进授权(step-up)流程。服务器侧回 403 Forbidden 并在 WWW-Authenticate 里声明缺什么:
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
scope="files:read files:write user:profile",
resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
error_description="Additional file write permission required"
客户端侧的标准动作:解析错误信息 → 按 scope 选择策略确定新集合 → 发起重新授权 → 用新令牌重试原请求。两条护栏值得写进实现:重试要有上限(规范原话是「不超过几次」,把再次失败当作永久性授权失败处理);代理用户操作的客户端 SHOULD 走 step-up,而 client_credentials 类的后台客户端 MAY 直接中止。没有上限的重试会把一次 scope 配置错误放大成授权循环风暴。
实现清单:上线前各查什么
服务端自检(按规范条款顺序):
- 实现了 RFC 9728 Protected Resource Metadata,且文档含
authorization_servers字段; - 401 响应带
WWW-Authenticate: Bearer resource_metadata="...",并附带scope提示; - 令牌校验同时核对签名与 audience;上游调用使用独立令牌,绝不透传;
- 授权页完整展示 client 名称与重定向 URI 主机名。
客户端自检:
- 能解析
WWW-Authenticate并按顺序回退 well-known URI; - 授权服务器元数据支持 RFC 8414 与 OIDC Discovery 两条路(带路径 issuer 按三级优先序尝试);
- PKCE 用 S256,且先确认
code_challenge_methods_supported存在; - 授权与令牌请求都带
resource参数;每个请求都带 Bearer 头; - step-up 流程有重试上限。
测试工具用官方 MCP Inspector——它实现了完整的发现与授权流程,可以对你的服务器走一遍 401 → 元数据 → 授权码 → 带令牌调用的全链路。服务器本体的 mcpServers 配置写法见 MCP 服务器配置指南;连不上时的排查套路见 MCP 调试实战。授权扩展(如 OAuth 相关的增量能力)单独维护在 modelcontextprotocol/ext-auth 仓库,采用可选、可组合、独立版本化的模式,不与核心规范绑定。
常见坑与反模式:从症状定位 OAuth 实现错误
上面的清单是「上线前逐项查」,这一节反过来:从你看到的症状出发,倒推是哪一条没做对。以下五种症状覆盖了实际集成里绝大多数的 OAuth 事故(规范原文见 MCP 授权规范)。
症状 1:授权成功了,调 API 却永远 401 invalid_token。 现象:令牌拿到了、Bearer 头也带了,请求照样 401。原因:授权请求与令牌请求携带的 resource 参数不一致(尾斜杠、大小写、路径差异),或者某一端漏发——签出来的令牌 audience 与服务器自身的 URI 对不上,资源服务器按规范必须拒绝。修法:两处使用完全相同的规范化 URI(小写 scheme/host、无 fragment、按惯例无尾斜杠),并把「校验 audience」写进服务器日志,失败时打印期望值与实际值。
症状 2:某些服务器连得上,某些连不上。 现象:你的客户端对 A 服务器发现流程一路绿灯,对 B 卡死。原因:发现顺序写死成一种,或只实现了 RFC 8414 而没支持 OIDC Discovery——新版规范要求两条路都 MUST 支持,带路径的 issuer 还要按三级优先序尝试。修法:按规范实现完整回退链:WWW-Authenticate 优先,well-known URI 按「发现链」一节的顺序逐级尝试。
症状 3:对不支持 PKCE 的服务器静默降级。 现象:安全审计发现线上在发裸授权码请求。原因:客户端没检查元数据里的 code_challenge_methods_supported 字段,字段缺失时走进了无 PKCE 的分支。修法:字段缺失 = 服务器不支持 PKCE = 客户端 MUST 拒绝继续——直接报错终止流程,而不是降级。授权码被截获的代价远大于「暂时连不上」。
症状 4:redirect_uri 忽而不匹配,忽而不安全。 现象:本地好好的,部署到生产就报 redirect_uri_mismatch;或者反过来——为了「省事」放宽匹配后收到安全工单。原因:注册与请求的 redirect_uri 不逐字一致(尾斜杠、端口、localhost 与 127.0.0.1 之差都算不同 URI)。修法:把注册时用的 URI 存成常量逐字复用;本机回环场景按 RFC 8252 允许端口可变,生产域名则必须逐字一致,没有任何通配余地。
症状 5:令牌出现在 URL 或日志里。 现象:审计发现 access_token 出现在网关访问日志,甚至上游服务的 URL 参数里。原因:把令牌拼进了 URI 查询串,或者日志打印了完整的 Authorization 头——规范明确 MUST NOT 令牌出现在 URI 中。修法:令牌只走 Bearer 头;日志对 Authorization 与 Cookie 头做脱敏;告警规则里加一条「URI 中出现 token 形态的字符串」。
实战案例:三个典型集成场景从头到尾
案例 A:客户端接入受保护的 MCP 服务器。 场景:拿到 https://mcp.example.com/mcp,零预配置。步骤:裸请求收到 401 → 解析 WWW-Authenticate 里的 resource_metadata URL → 拉取 RFC 9728 文档拿到 authorization_servers → 对授权服务器按三级顺序探测元数据 → 确认 client_id_metadata_document_supported 后托管一份 client metadata 文档完成注册 → 带 PKCE 与 resource 走完授权码流程 → 每个请求带 Bearer 头。全程可用 MCP Inspector 对照验证——它的实现就是这套流程的参考答案。
案例 B:把内部 REST API 改造成受保护的 MCP 服务器。 场景:公司已有订单 API,认证走自建 OIDC(如 Keycloak),现在要包一层 MCP。步骤:实现 Protected Resource Metadata 文档,authorization_servers 指向 Keycloak;所有端点的 401 响应带 WWW-Authenticate 头与 scope 提示;令牌校验在签名之外加 audience 断言(确认令牌是签给本服务器的);同意页完整展示 client 名称与 redirect_uri 主机名。做完这四件事,任何合规的 MCP 客户端都能零配置接入。
案例 C:git 工具要调 GitHub API——双令牌架构。 场景:你的 MCP 服务器封装了 git 操作,需要以用户身份调 GitHub API。错误做法:把客户端给你的令牌透传给 GitHub——规范 MUST NOT 禁止透传,这就是「混淆代理」。正确做法:服务器作为独立的 OAuth 客户端向 GitHub 申请自己的令牌(自己的 client_id、自己的 scope),与 MCP 客户端令牌分账管理、独立轮换、独立撤销。两个令牌两条信任链,审计边界才清晰(详见安全最佳实践)。
常见问题
stdio 的 MCP 服务器需要 OAuth 吗?
不需要。规范明确写着 stdio 传输 SHOULD NOT 使用这套授权流程——本地进程直接从环境变量取凭证(如 API key),没有浏览器可完成授权码流程。OAuth 流程只针对 HTTP 传输的远程 MCP 服务器,它们以资源服务器身份运行。
OAuth Client ID Metadata Documents(CIMD)是什么?
一种客户端注册方式:客户端用一个 HTTPS URL 作为 client_id,URL 指向托管在客户端域名下的 JSON 元数据文档(含 client_id、client_name、redirect_uris 等字段)。授权服务器遇到 URL 形态的 client_id 时主动拉取并校验。它解决的是 MCP 最常见的场景——客户端与服务器事先没有任何关系,没法预注册。2025-11-25 版规范起,客户端与授权服务器 SHOULD 支持 CIMD。
为什么 2025 年的 MCP OAuth 教程现在过时了?
因为 2025-11-25 版规范改写了注册机制的优先级:动态客户端注册(DCR)从 SHOULD 降到 MAY、只为向后兼容保留,而 CIMD 成为无预先关系场景的首选。老教程教「先 POST /register」,新规范要求客户端先查授权服务器元数据的 client_id_metadata_document_supported 字段。发现的回退顺序、step-up 授权也都是新版才有的内容。
resource 参数是必须的吗?漏了会怎样?
是必须的。规范要求 MCP 客户端 MUST 实现 RFC 8707,在授权请求和令牌请求中都带上 resource 参数,值为 MCP 服务器的规范 URI(不带尾斜杠、无 fragment)。它是令牌 audience 绑定的基础:服务器 MUST 校验令牌确实是签给自己的。漏掉 resource 参数意味着令牌可能被签发成「通用的」,跨服务重放的风险随之出现。
MCP 服务器可以把我的令牌转发给上游 API 吗?
不可以。规范明确 MUST NOT 透传客户端令牌:MCP 服务器调用上游 API 时扮演的是上游授权服务器的一个独立 OAuth 客户端,应当单独申请令牌。透传会导致「混淆代理」问题——下游把令牌当作 MCP 服务器的身份信任,权限边界与审计同时失灵。
收到 insufficient_scope 错误该怎么处理?
这是 step-up 授权的入口:服务器回 403,WWW-Authenticate 头里带 error="insufficient_scope"、scope(所需最小集合)与 resource_metadata。客户端解析后发起新一轮授权(带上原有 scope 加新 scope),用新令牌重试原请求。务必设置重试上限——规范建议「几次」为限,再次失败按永久性授权失败处理,避免无限授权循环。
MCP 客户端第一次连接受保护服务器时,怎么知道该去哪里授权?
从一次 401 开始。客户端先不带令牌请求 MCP 端点,服务器返回 401 并在 WWW-Authenticate 头里给出 resource_metadata URL;拉取后得到 Protected Resource Metadata(RFC 9728),其 authorization_servers 字段列出可用的授权服务器;再按固定回退顺序探测授权服务器元数据端点(RFC 8414 与 OIDC Discovery 都要支持),拿到端点后走授权码 + PKCE 流程。
MCP 的 OAuth 流程里 PKCE 是可选的吗?该用哪种 code challenge 方法?
PKCE 是强制的,且必须用 S256 方法。规范要求客户端生成 code_verifier 与 S256 的 code_challenge,并在发起授权前先检查服务器元数据里的 code_challenge_methods_supported 字段——字段缺失说明服务器不支持 PKCE,客户端必须拒绝继续,而不是静默降级成裸授权码流程,否则授权码可能被截获兑换。
官方参考资料
- MCP 授权规范(2025-11-25 版,本文主要依据)
- MCP 授权规范(2025-06-18 版,对比参考)
- MCP 安全最佳实践:token passthrough 与混淆代理
- RFC 8707:Resource Indicators for OAuth 2.0
- RFC 9728:OAuth 2.0 Protected Resource Metadata
- OAuth Client ID Metadata Documents(IETF draft)
- Stytch:MCP OAuth 与 Dynamic Client Registration(2025 年视角,可对照新版差异)
- modelcontextprotocol/ext-auth:MCP 授权扩展仓库
- MCP Inspector:官方调试工具
本文基于 2025-11-25 版 MCP 授权规范与 2026 年 8 月的实现现状;规范仍在演进,动手实现前请对照最新版本号。