06 · 鉴权与安全
前面都在讲“怎么通信”。这一章讲“凭什么让你通信”——HTTP 传输上的鉴权,以及散落在全规范、但极其重要的安全红线。鉴权是可选的,但一旦上 HTTP 就应当遵守这套基于 OAuth 2.1 的模型。
6.1 鉴权的角色模型
MCP 在传输层提供鉴权,让客户端代表资源所有者去访问受保护的服务器(docs/specification/draft/basic/authorization/index.mdx:12)。三个 OAuth 角色:
| MCP 角色 | OAuth 角色 | 职责 |
|---|---|---|
| MCP 服务器 | 资源服务器(RS) | 校验访问令牌、提供受保护资源 |
| MCP 客户端 | OAuth 客户端 | 代表用户去拿令牌、带令牌访问 |
| 授权服务器(AS) | 授权服务器 | 和用户交互、签发令牌(可独立于 RS) |
传输相关的取舍很清楚(docs/specification/draft/basic/authorization/index.mdx:21):HTTP 传输应遵守本规范;stdio 传输不应用它——本地子进程直接从环境变量取凭证就好。
6.2 鉴权流程(PKCE 授权码流)
怎么读:从“无令牌被拒”开始,客户端一步步发现授权服务器、注册、走带 PKCE 的授权码流、最后拿到绑定了受众的令牌。
Client MCP Server(RS) Auth Server(AS)
│ 无令牌请求 │ │
│◄── 401 + WWW-Authenticate(含 resource_metadata URL)│
│ 取 Protected Resource Metadata(RFC 9728) │
│◄── 元数据(指向 AS) │ │
│ 取 AS 元数据 ─────────────────────────────────────►│
│◄── AS 元数据(授权/令牌端点) │
│ 生成 PKCE,带 resource 参数,开浏览器授权 ─────────►│
│◄── 重定向回调(授权码 + iss) │
│ 校验 iss,用 code_verifier + resource 换令牌 ──────►│
│◄── 访问令牌(+ 刷新令牌) │
│ 带 Bearer 令牌请求 ──► RS ── 校验受众 ── 返回资源 │
几个必须的基石(docs/specification/draft/basic/authorization/index.mdx:60):
- 授权服务器必须实现 OAuth 2.1。
- MCP 服务器必须实现 OAuth 2.0 Protected Resource Metadata(RFC 9728)——客户端就是靠它发现“该找哪个授权服务器”。
- 授权服务器必须至少提供 RFC8414 或 OpenID Connect Discovery 之一的发现机制。
6.3 受众绑定:防“令牌被拿去别处用”
这是 MCP 鉴权最核心的安全机制。客户端必须实现 RFC 8707 的 resource 参数——在授权请求和令牌请求里都明确写出“这个令牌要给哪个 MCP 服务器用”,用服务器的规范 URI(docs/specification/draft/basic/authorization/index.mdx:218)。
对应地,服务器必须校验令牌的受众确实是自己,并且只接受为自己签发的令牌,不得接受或转发任何别的令牌(docs/specification/draft/basic/authorization/index.mdx:286)。
这两条合起来防的是:令牌被某个服务器拿去冒充用户访问另一个服务器。
6.4 禁止令牌透传(token passthrough)
一条反复强调的红线:令牌透传——MCP 服务器接受客户端给自己的令牌、却不校验这令牌是否确为自己签发,就原样转发给下游第三方 API——是明令禁止的反模式(docs/docs/tutorials/security/security_best_practices.mdx:278 「### Token Passthrough」、:287 「explicitly forbidden」)。
这与 6.3 的受众绑定是一体两面:服务器只接受受众是自己的令牌(拒收别处的),自然也就不会把客户端的令牌捅到上游去。
如果服务器需要访问第三方(比如代用户调 GitHub),正确做法是走 URL 模式征询(04 章)让用户单独授权第三方:第三方令牌由服务器自己保管、 绑到用户身份。规范在这里有两条对称要求——第三方凭证不得流经 MCP 客户端(客户端永远看不到第三方凭证,docs/specification/draft/client/elicitation.mdx:523),且服务器不得拿客户端给自己的凭证去访问第三方(即上面的令牌透传,elicitation.mdx:524)。这维持了“MCP 鉴权”与“第三方鉴权”两条独立的信任边界。
6.5 客户端注册与 step-up 授权
客户端拿 client ID 有三条路,draft 调整了优先级(docs/specification/draft/basic/authorization/index.mdx:90、changelog docs/specification/draft/changelog.mdx:93):
- Client ID Metadata Documents(推荐)— 客户端直接用一个 HTTPS URL 当
client_id,授权服务器去那个 URL 抓元数据。 - 预注册 — 用已有的
client_id。 - 动态客户端注册(RFC 7591) — draft 把它降级为已废弃,仅为兼容不支持上面那种的授权服务器而保留。
运行时遇到“权限不够”(insufficient_scope)时走 step-up 授权流:服务器回 403 + WWW-Authenticate(带所需 scope),客户端应把“历史已请求的 scope ∪ 本次挑战的 scope”取并集去重新授权,避免丢掉别处需要的权限(docs/specification/draft/basic/authorization/index.mdx:401)。scope 的累积是客户端责任——这样服务器对“客户端有哪 些 scope”也能保持无状态。
6.6 贯穿全协议的安全主线
把散落各章的安全要求收成一张表——这是读者最该带走的“防线清单”:
| 防线 | 要求 | 出处 |
|---|---|---|
| 人在回路 | 工具调用、采样应始终有人能否决 | docs/specification/draft/server/tools.mdx:30、sampling.mdx:35 |
| 服务器隔离 | 服务器看不到整段对话、看不见别的服务器 | docs/specification/draft/architecture/index.mdx:101 |
| 受众绑定 | 令牌只能用于签发它的那个服务器 | authorization/index.mdx:286 |
| 禁止令牌透传 | 不得拿(受众非自己的)客户端令牌转发给第三方 | security_best_practices.mdx:278、:287 |
| 第三方凭证不经客户端 | 第三方凭证不得流经 MCP 客户端 | elicitation.mdx:523 |
| 敏感信息走 URL 模式 | 密码/密钥不得用 form 模式征询 | elicitation.mdx:30 |
| requestState 当攻击者输入 | 影响授权就必须 HMAC/AEAD + 防重放 | mrtr.mdx:222 |
| 校验头体一致 | HTTP 头与请求体不符则拒 | streamable-http.mdx:587 |
| 防 DNS rebinding | 校验 Origin、 本地只绑 localhost | streamable-http.mdx:56 |
| 图标/资源当不可信 | icon 字节、$ref 不自动解引网络 URI | basic/index.mdx:433、:299 |
| 路径遍历防护 | file:// 资源必须净化路径 | resources.mdx:433 |
一条值得单独点的细节:JSON Schema 的 $ref 不得被自动解引到网络 URI(防 SSRF),可选的取数模式也必须默认关闭并设白名单/超时/大小限制;复杂的组合关键字(anyOf/oneOf…)应设深度与子模式上限,防恶意 schema 当 DoS 向量(docs/specification/draft/basic/index.mdx:297、:310)。
6.7 边界与局限(诚实地说)
- 鉴权只覆盖 HTTP:stdio 明确不走这套,靠环境凭证——本地信任模型不同。
- Roots 不是访问控制:它只是“信息性指引”,协议不强制服务器待在根内(
docs/specification/draft/client/roots.mdx:23)。真正的访问控制得服务器自己做。 - 本地错误无标准码:SDK 内部的超时之类纯本地错误,规范暂未分配错误码,只要求别和“对端发来的错误”混淆(
docs/specification/draft/basic/index.mdx:144)。 - draft 是草案:协议版本
2026-07-28仍在演进;URL 模式征询等较新特性规范明说“未来可能变”(docs/specification/draft/client/elicitation.mdx:324)。生产实现多数还在跑已发布的2025-11-25。
横向对比(同 shelf 视角)
MCP 在 ai-protocol-reference(协议库)里是**“应用↔工具”的接入协议**:它不规定模型怎么推理,只规定能力怎么被发现、调用、鉴权。
- 和 A2A / agent 间协议相比:MCP 管的是“一个 agent 怎么用外部工具”,不是“多个 agent 怎么互相对话”。
- 和底层 LLM 厂商 API(Anthropic/OpenAI 的 tool use)相比:MCP 是更上一层的标准化与可组合层——同一个 MCP 服务器能被任何支持 MCP 的应用复用,而厂商 API 的工具定义绑死在单次调用里。draft 废弃 Sampling、建议“直接对接厂商 API”,正是在划清这条界:模型推理交给厂商 API,能力接入交给 MCP。
代码地图
| 主题 | 文件路径 | 符号 / 锚点 |
|---|---|---|
| 鉴权总流程与受众绑定 | docs/specification/draft/basic/authorization/index.mdx | Authorization Flow / Resource Parameter |
| 客户端注册 | docs/specification/draft/basic/authorization/client-registration.mdx | Client ID Metadata Documents |
| 授权服务器发现 | docs/specification/draft/basic/authorization/authorization-server-discovery.mdx | — |
| 鉴权安全要求 | docs/specification/draft/basic/authorization/security-considerations.mdx | — |
| 令牌透传禁令 | docs/docs/tutorials/security/security_best_practices.mdx | Token Passthrough |
| 全局安全最佳实践 | docs/docs/tutorials/security/security_best_practices.mdx | — |