教程
MCP Server 如何添加 OAuth 2.1 身份认证?从 Authorization Server 到 Access Token 的 Remote MCP API 实战教程
无状态 MCP 解决的是会话,不是谁能调你的工具。Remote MCP 一旦挂到公网,身份认证必须从 Authorization Server 发现走到 Access Token,而不是在 JSON-RPC 里塞一把共享密钥。
2026-07-28 的 MCP 把 HTTP 传输上的授权写成可选;一旦要保护资源,就必须按 OAuth 2.1 子集落地。Cursor、Claude Desktop、OpenAI Responses 远程 MCP、自建 Agent Host,都会先对 Remote MCP 发一次不带 token 的请求。Server 回 401,并在 WWW-Authenticate 里指出 Protected Resource Metadata。客户端再去发现 Authorization Server、完成登记、走授权码 + PKCE、带上 resource 参数,换到 Access Token,然后在每一跳 HTTP 上放 Authorization: Bearer。这篇按这条 Remote MCP API 链路写实战:角色怎么分、metadata 长什么样、token 怎么验 audience、scope 不够怎么 step-up。规范原文见 MCP Authorization。站内已有 MCP 是什么、Stateless MCP、inputSchema 与 Tool Calling,本文不重复那些层,只补「谁被允许调用」。STDIO 传输不要走这套浏览器跳转,凭证从环境变量取。
三方角色:MCP Server 不负责发 Token
最容易写错的第一件事,是把 MCP Server 当成发 token 的人。规范里,受保护的 MCP Server 是 OAuth 2.1 Resource Server:它接受、校验、按 audience 消费 Access Token。MCP Client 是 OAuth 2.1 客户端,代表资源所有者去要 token,再带 token 调工具。Authorization Server(AS)负责登录、同意、签发。AS 可以和 Resource Server 同机,也可以是公司已有的 IdP(Okta、Keycloak、Auth0、自建 OIDC)。规范不规定你怎么实现 AS,只规定 MCP Server 如何把它的位置告诉客户端——见 Authorization Server Discovery。
授权对 MCP 实现是可选的。HTTP 传输一旦要保护资源,就应该遵守这套规范。STDIO 明确不应该跟这套走。把共享 API Key 塞进 tools/call 的 params,或把 token 放进 JSON-RPC _meta,都不是 2026-07-28 的 Remote MCP 认证。认证在 HTTP 传输层,不在方法信封里。
对照站内分层:MCP 是发现与调用协议;无状态化拿掉的是 Session,不是鉴权;inputSchema 管 arguments 形状,验过 Schema 也不等于调用者有权。OAuth 回答的是「这个 Bearer 是不是签给本资源、scope 够不够」。Agent 循环本身见 AI Agent 是什么。
| 角色 | OAuth 2.1 身份 | 你要交付什么 |
|---|---|---|
| MCP Client / Host | OAuth Client | 发现、PKCE、存 token、每跳带 Bearer |
| MCP Server | Resource Server | 401 + PRM、验 token、audience 绑定 |
| Authorization Server | 签发方 / IdP | 登录、同意、授权码、Access Token |
发现 Authorization Server:401 与 RFC 9728
实战从一次失败的调用开始。Client 对 https://mcp.example.com/mcp 发 tools/list 或任意 JSON-RPC,不带 Authorization。Server 必须回 HTTP 401,并在 WWW-Authenticate 里带 Bearer 与 resource_metadata。scope 建议带上,告诉客户端这次最少要申请什么。客户端必须能解析这个头:有 resource_metadata 就用它,没有再按 RFC 9728 探测 well-known。
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="mcp",
resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
scope="invoices:read"
MCP Server 必须实现 OAuth 2.0 Protected Resource Metadata(RFC 9728)。文档里 authorization_servers 至少写一个 AS issuer,还可以写 resource(本 Server 的规范 URI)、scopes_supported、bearer_methods_supported。客户端拿到后再去拉 AS metadata:无路径的 issuer 先试 /.well-known/oauth-authorization-server,再试 OpenID openid-configuration;带 tenant 路径的按规范优先级插入路径。拉回来的文档里 issuer 必须与用来构造 URL 的 issuer 字符串完全一致,否则视为攻击,整份 metadata 丢掉。
{
"resource": "https://mcp.example.com/mcp",
"authorization_servers": [
"https://auth.example.com"
],
"bearer_methods_supported": ["header"],
"scopes_supported": [
"invoices:read",
"invoices:write"
],
"resource_documentation": "https://mcp.example.com/docs"
}
上面是 401 与一份最小可用的 Protected Resource Metadata。resource 不要带 fragment,也不要省略 scheme。带不带尾斜线要全站统一,规范建议无语义时不要尾斜线。多写几个 AS 时,每一个都是独立签发方,客户端必须分开存登记状态。
登记客户端:CIMD、预注册与 DCR
发现完 AS,Client 必须先有 client_id,才能发授权请求。三种机制,按规范优先级:① Client ID Metadata Documents(CIMD)——client_id 本身是一个 HTTPS URL,AS 去拉这份 JSON,校验 redirect_uris;② 预注册,控制台里发的机密或公开客户端;③ Dynamic Client Registration(RFC 7591)POST /register。规范写明 DCR 已弃用,只为兼容还不支持 CIMD 的 AS 保留。新项目优先 CIMD 或预注册。
多个 authorization_servers 时,每一个都是独立的 AS。client_id、密钥、token 按 AS 分开存,绝不能拿 A 的凭证去打 B 的 token 端点。这是 mix-up / confused deputy 的常见入口。
公开客户端(桌面 Host、浏览器扩展)必须按 OAuth 2.1 做 PKCE。机密客户端也建议做。不要自己发明「把 client_secret 写进 MCP Server 环境变量、再转发给 AS」这种代理登记——MCP Server 是 Resource Server,不是 Client。
授权码 + PKCE + resource:换到 Access Token
登记之后才是授权码流。Client 在打开浏览器之前必须:生成 PKCE code_verifier / code_challenge;在授权请求和后续 token 请求里都带 resource,值是 MCP Server 的规范 URI(RFC 8707);按 scope 策略选权限(优先 401 里的 scope,否则 scopes_supported);把已校验的 AS issuer 记在与 code_verifier 同一条请求记录里。用户同意后,回调里的 iss 按 RFC 9207 与记录值做简单字符串比较,不要做大小写折叠或去尾斜线。
Token 请求带 code、code_verifier、resource。成功响应是 JSON:access_token、token_type 为 Bearer、expires_in,可能有 refresh_token 与 scope。不要假设一定有 refresh token。MCP Server 的 scopes_supported 和 401 的 scope 里不要放 offline_access——那是 Client 向 AS 要的,不是资源需求。AS metadata 的关键字段如下;authorization_response_iss_parameter_supported 为 true 时,缺少 iss 的回调必须拒绝。
{
"issuer": "https://auth.example.com",
"authorization_endpoint": "https://auth.example.com/oauth/authorize",
"token_endpoint": "https://auth.example.com/oauth/token",
"jwks_uri": "https://auth.example.com/.well-known/jwks.json",
"registration_endpoint": "https://auth.example.com/oauth/register",
"code_challenge_methods_supported": ["S256"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"response_types_supported": ["code"],
"token_endpoint_auth_methods_supported": [
"none",
"client_secret_basic"
],
"authorization_response_iss_parameter_supported": true,
"scopes_supported": [
"invoices:read",
"invoices:write",
"offline_access"
]
}
Access Token 对 Client 常常是不透明字符串;排障时它经常是 JWT。先看 iss、aud(或 resource)、scope、exp、client_id。aud 必须绑到本 MCP Server,不能拿着「给支付 API 的 token」来调 tools/call。下面这枚 payload 的 aud 正确;若改成别的 API 的 URI,Resource Server 必须 401。
{
"iss": "https://auth.example.com",
"sub": "user_1842",
"aud": "https://mcp.example.com/mcp",
"client_id": "https://host.example.com/oauth/client.json",
"scope": "invoices:read",
"exp": 1790000000,
"iat": 1789996400
}
Bearer 调 Remote MCP API,并校验 audience
拿到 token 之后,每一跳 Client → MCP Server 的 HTTP 都必须带 Authorization: Bearer,包括发现、tools/list、tools/call、resources/read。Token 禁止放进 query。JSON-RPC 方法名与 arguments 仍按原样放在 body 里——认证头和信封是两层。无状态 MCP 下没有 Session 可以「登录一次后面免检」;每次请求自带鉴权。对照 Stateless MCP。
POST /mcp HTTP/1.1
Host: mcp.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
Accept: application/json, text/event-stream
{
"jsonrpc": "2.0",
"id": 42,
"method": "tools/call",
"params": {
"name": "searchInvoices",
"arguments": {
"startDate": "2026-01-01",
"endDate": "2026-01-31",
"status": "paid"
}
}
}
Server 按 OAuth 2.1 §5.2 验 token:签名或 introspection、exp、iss、以及 RFC 8707 的 audience。失败一律 401。验过 audience 仍要确认这个 token 是本 AS 签发的。Server 不得接受或转发「给别人用的」token。Client 也不得把别的资源的 token 发给这个 MCP Server。这是 confused deputy 的硬约束。
鉴权通过不等于 arguments 合法。Bearer 只回答「谁、对哪个资源、哪些 scope」。searchInvoices 的日期和枚举仍要 parse + Schema + 业务规则。不要把「token 有效」写成 tools/call 的唯一门卫。形状契约见 MCP 与 JSON Schema。
Scope、403 与 JSONVue 联调
已有 token 但 scope 不够时,Server 应回 403,WWW-Authenticate 带 error="insufficient_scope"、本次操作需要的 scope、以及同样的 resource_metadata。一次挑战里给齐本次所需 scope,不要一次缺一个逼用户点两次同意。Client 做 step-up 时要取「旧 scope ∪ 挑战 scope」,避免丢掉其他工具已经拿到的权限。
| HTTP | 含义 | 下一步 |
|---|---|---|
| 401 | 未认证或 token 无效 / 过期 / audience 不对 | 读 PRM,重新走授权或换 token |
| 403 + insufficient_scope | token 有效,权限不够 | step-up:合并 scope 再授权 |
| 400 | 授权请求畸形 | 先修 Client 的请求,不要重试业务 |
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
scope="invoices:write",
resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
error_description="Write scope required to create a credit note"
联调时你会经手至少四份 JSON:Protected Resource Metadata、AS metadata、token 响应、(若是 JWT)payload。再加一份 tools/call 的 params.arguments。它们职责不同,不要揉进一个文件做「通用校验」。失败样本各留一条:缺 authorization_servers 的 PRM、issuer 不一致的 AS metadata、aud 绑错的 JWT、缺字段的 arguments。
浏览器里即可完成:JSON 格式化看 metadata 是否能 parse;JWT 解码看 aud / scope / exp;JSON Schema 校验对你为 PRM 写的契约;JSON Diff对比刷新前后的 payload,或模型 arguments 与 MCP params。数据不离开本机。
延伸阅读:MCP 是什么、Stateless MCP、MCP 与 JSON Schema、A2A vs MCP、AI Agent 是什么。
常见问题 FAQ
本地 STDIO MCP 也要上 OAuth 2.1 吗?
不要。规范写明 STDIO 不应走这套浏览器授权,凭证从环境变量取。OAuth 2.1 是给 HTTP Remote MCP 的。同一套工具可以同时提供 STDIO 与 Streamable HTTP,但只有 HTTP 面需要 401、PRM 和 Bearer。
能不能把 Access Token 放进 JSON-RPC params 或 _meta?
不能当作规范实现。Access Token 必须放在 HTTP Authorization 头,且不得出现在 query。信封里的 _meta 管协议版本等,不管登录。把 token 写进 body,等于把它送进日志、代理和模型上下文。
自己签发 JWT、Server 用共享密钥验,算实现了 MCP OAuth 吗?
只做验签不够。Client 发现的是 Authorization Server,不是「Server 内嵌的密钥」。你仍要提供 RFC 9728 metadata、authorization_servers、带 resource 的授权/token 请求、audience 绑定,以及 401 / 403 挑战。自建 AS 可以发 JWT,但发现与 resource 参数不能省。
前面已经有 API Gateway 在验 JWT,还要再做一遍吗?
网关可以替你验签名和过期,但 MCP Host 还要能发现 AS、带 resource、读 WWW-Authenticate。只在网关验「任意合法 JWT」、不绑 audience,会把发给其他 API 的 token 放进来。把 MCP 当普通 REST 护着,Host 往往连不上。
总结与下一步
OAuth 2.1 在 Remote MCP 里不是「再加一个登录页」,而是一条从 401 发现、AS metadata、客户端登记、PKCE、resource、Access Token 到每跳 Bearer 的传输层合同。MCP Server 是 Resource Server;发 token 的是 Authorization Server。
落地顺序:先让 401 + Protected Resource Metadata 能被现有 MCP Client 解析,再接 AS,最后才写 tools/call 的业务。用 JSONVue 把 metadata 和 JWT 样本留在本地,和 Schema 校验分开。需要协议入口读 MCP 是什么;需要无状态传输读 Stateless MCP。