MCP 认证详解:OAuth 2.1 在 MCP 中如何工作(2026 指南)
MCP 认证全解析:OAuth 2.1、受保护资源元数据、PKCE、Client ID 元数据文档、Resource Indicators,以及 stdio 环境变量凭据和 API Key 的用法与排错。
MCP 认证是如何工作的
远程(HTTP)MCP 服务器的认证使用 OAuth 2.1:MCP 服务器是 OAuth 资源服务器,MCP 客户端是 OAuth 客户端,由一个独立的授权服务器签发访问令牌,客户端在每个请求中以 Authorization: Bearer 的形式携带它。本地 stdio 服务器不走这套流程;按照规范,它们从环境中获取凭据,通常是通过环境变量传入的 API Key。本文基于当前规范修订版 2026-07-28,讲清楚有哪些变化、每个部分的作用,以及如何用官方 SDK 实现。
stdio 还是 HTTP:适用哪种认证模型?
授权规范 规定授权是可选的,然后按传输方式区分:
- 基于 HTTP 的传输 SHOULD 遵循 MCP 授权规范(OAuth 2.1)。
- stdio 传输 SHOULD NOT 遵循它,而是从环境中获取凭据。
- 其他传输 MUST 遵循其协议既有的安全最佳实践。
| 本地 stdio 服务器 | 远程 HTTP 服务器 | |
|---|---|---|
| 运行位置 | 你的机器上,作为客户端的子进程 | 托管服务 |
| 典型凭据 | 环境变量中的 API Key 或令牌 | OAuth 2.1 访问令牌 |
| 谁来登录 | 你自己,在创建 key 的时候 | 你自己,在 OAuth 流程中通过浏览器登录 |
| 撤销方式 | 在服务商处轮换 key | 撤销授权,或等短时效令牌过期 |
| 规范流程 | 无,直接从环境读取 | 受保护资源元数据、发现、PKCE |
OAuth 2.1 流程分步详解
1. 服务器返回 401 并给出指引
客户端在没有令牌的情况下调用 MCP 端点。服务器返回 401,并在 WWW-Authenticate 头中给出它的受保护资源元数据文档(RFC 9728)地址,还可以附上本次请求需要的权限范围:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
scope="files:read"
2. 客户端读取受保护资源元数据
MCP 服务器 MUST 实现 RFC 9728,并且该文档 MUST 在 authorization_servers 中至少列出一项:
{
"resource": "https://mcp.example.com/mcp",
"authorization_servers": ["https://auth.example.com"],
"scopes_supported": ["mcp:tools", "mcp:resources"]
}
如果响应头里没有 resource_metadata,客户端 MUST 按以下顺序回退到 well-known URI:先是带路径的 URI(端点在 /public/mcp 时为 https://example.com/.well-known/oauth-protected-resource/public/mcp),再是根路径 /.well-known/oauth-protected-resource。
3. 客户端发现授权服务器
授权服务器 MUST 提供 RFC 8414 元数据或 OpenID Connect Discovery,客户端 MUST 两者都支持。对于不带路径的 issuer,客户端依次尝试 /.well-known/oauth-authorization-server 和 /.well-known/openid-configuration。对于带路径的 issuer(如 https://auth.example.com/tenant1),依次尝试 OAuth 路径插入 URL、OIDC 路径插入 URL、OIDC 路径追加 URL。返回文档中的 issuer MUST 与用来构造 URL 的 issuer 完全一致,否则客户端 MUST 丢弃它。
4. 客户端获取 client ID
流程开始前,客户端需要一个 client_id。客户端注册页面 定义了三种机制和一个优先级顺序:
| 优先级 | 机制 | 适用场景 |
|---|---|---|
| 1 | 预注册 | 客户端已有该授权服务器的 client ID |
| 2 | Client ID Metadata Documents(CIMD) | 授权服务器声明了 client_id_metadata_document_supported |
| 3 | 动态客户端注册(RFC 7591) | 服务器暴露了 registration_endpoint 时作为回退 |
| 4 | 让用户手动填写 | 其他方式都不可用 |
近几个修订版最大的变化:动态客户端注册(DCR)已被弃用。新实现应使用 CIMD,即 client_id 本身是一个 HTTPS URL,指向一份由授权服务器抓取的 JSON 文档:
{
"client_id": "https://app.example.com/oauth/client-metadata.json",
"client_name": "Example MCP Client",
"redirect_uris": ["http://127.0.0.1:3000/callback"],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}
该 URL MUST 使用 https 且包含路径,文档 MUST 包含 client_id、client_name 和 redirect_uris,并且 client_id MUST 与 URL 完全一致。CIMD 的 client ID 可以跨授权服务器使用;预注册和 DCR 得到的凭据 MUST 绑定到签发它们的 issuer。
5. 带 PKCE 和 Resource Indicators 的授权码流程
客户端打开浏览器跳转到授权端点。最关键的三条要求:
- PKCE 是强制的。 客户端在技术上可行时 MUST 使用
S256,并且 MUST 检查服务器元数据中的code_challenge_methods_supported;如果缺失,客户端 MUST 拒绝继续(安全注意事项)。 resource参数是强制的。 客户端 MUST 在授权请求和令牌请求中都发送 RFC 8707 的resource参数,值为 MCP 服务器的规范 URI(例如https://mcp.example.com/mcp,不带片段,最好不带结尾斜杠),即使授权服务器会忽略它也要发。- 校验 issuer。 跳转之前,客户端记录预期的
issuer;授权码返回时,比对iss参数(RFC 9207),不一致就拒绝。这可以阻止授权服务器之间的 mix-up 攻击。
权限范围选择:如果 401 质询中带了 scope 就用它,否则请求 scopes_supported 中列出的权限。
6. 携带令牌调用服务器
POST /mcp HTTP/1.1
Host: mcp.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
令牌 MUST 在每个请求的 Authorization 头中携带,并且 MUST NOT 出现在查询字符串中。服务器 MUST 校验令牌,并确认它是以本服务器为受众签发的;无效或过期的令牌返回 401。
7. 工具需要更多权限时逐步提权
如果令牌缺少某个权限,服务器 SHOULD 返回带 error="insufficient_scope" 的 403,并给出本次操作所需的权限。客户端用「之前请求的权限」与「新质询的权限」的并集重新授权,并只重试有限次数。
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
scope="files:write",
resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"
给服务器作者的规则
- 绝不透传令牌。 MCP 服务器 MUST 只接受签发给自己的令牌,并且 MUST NOT 把客户端的令牌转发给上游 API。如果你要调用 GitHub 或 Slack,请单独获取上游令牌。原因见 MCP 安全最佳实践。
- 使用真正的授权服务器。 官方 授权教程 建议不要自己实现令牌校验;教程使用 Keycloak,并通过内省(introspection)校验受众。
- 令牌保持短时效。 授权服务器 SHOULD 签发短时效访问令牌,并且对公共客户端 MUST 轮换刷新令牌。
- 全部走 HTTPS。 授权端点 MUST 使用 HTTPS,重定向 URI MUST 是
localhost或 HTTPS。 - 不要记录凭据。 从日志中清除
Authorization头、授权码和令牌。
官方 SDK 实现要点
TypeScript。 官方教程中的服务器使用了 @modelcontextprotocol/sdk 的辅助函数:mcpAuthMetadataRouter 发布受保护资源元数据,requireBearerAuth 用你的校验器强制要求令牌,getOAuthProtectedResourceMetadataUrl 生成 WWW-Authenticate 质询中使用的 URL:
import { requireBearerAuth } from '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js';
import {
getOAuthProtectedResourceMetadataUrl,
mcpAuthMetadataRouter,
} from '@modelcontextprotocol/sdk/server/auth/router.js';
app.use(
mcpAuthMetadataRouter({
oauthMetadata,
resourceServerUrl: mcpServerUrl,
scopesSupported: ['mcp:tools'],
resourceName: 'MCP Demo Server',
})
);
const authMiddleware = requireBearerAuth({
verifier: tokenVerifier,
requiredScopes: [],
resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(mcpServerUrl),
});
Python。 在 Python SDK 中,MCPServer 类接收一个 token_verifier(TokenVerifier 的子类)和 AuthSettings。它会发布元数据文档,并对未认证的请求返回正确的 401:
from pydantic import AnyHttpUrl
from mcp.server import MCPServer
from mcp.server.auth.settings import AuthSettings
app = MCPServer(
name="MCP Resource Server",
token_verifier=token_verifier,
auth=AuthSettings(
issuer_url=AnyHttpUrl("https://auth.example.com/realms/master"),
required_scopes=["mcp:tools"],
resource_server_url=AnyHttpUrl("https://mcp.example.com/mcp"),
),
)
两个 SDK 还都提供了 checkResourceAllowed / check_resource_allowed 辅助函数,用于比对令牌的 resource 和你的服务器 URL。想观察完整的握手过程,可以用 MCP Inspector 连接。
API Key 与环境变量凭据
MCP 目录 中的大多数服务器仍然使用 API Key 认证。这没有问题,而且正是规范对 stdio 的描述。
通过环境变量配置 stdio 服务器(Claude Code 示例,来自 官方文档):
claude mcp add --env AIRTABLE_API_KEY=YOUR_KEY --transport stdio airtable \
-- npx -y airtable-mcp-server
接受 Bearer key 的远程 HTTP 服务器:
claude mcp add --transport http secure-api https://api.example.com/mcp \
--header "Authorization: Bearer your-token"
使用 OAuth 的远程 HTTP 服务器: 添加时不带凭据,然后在客户端里登录。例如先执行 claude mcp add --transport http sentry https://mcp.sentry.dev/mcp,再在 Claude Code 中运行 /mcp,在浏览器里完成登录。Sentry MCP Server、Notion MCP Server 和 GitHub MCP Server 都提供托管端点。更多客户端相关步骤见我们的 Claude Code MCP 指南。
为了不把 key 写进提交到仓库的配置里,可以在 .mcp.json 中引用环境变量:
{
"mcpServers": {
"api-server": {
"type": "http",
"url": "https://api.example.com/mcp",
"headers": {
"Authorization": "Bearer ${API_KEY}"
}
}
}
}
静态 key 能用,但它们通常长期有效且权限宽泛。OAuth 则提供按用户的授权确认、有范围且会过期的令牌,以及无需轮换共享密钥的撤销能力。
MCP 认证排错
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 客户端始终不打开浏览器 | 服务器返回的 401 不带 resource_metadata,且没有 well-known 文档 | 提供 /.well-known/oauth-protected-resource 或加上该响应头 |
| 提示不支持 PKCE,或客户端拒绝继续 | 授权服务器元数据缺少 code_challenge_methods_supported | 启用 PKCE S256 并发布该字段 |
| 客户端注册失败 | 不支持 CIMD、没有 registration_endpoint,或 OIDC 拒绝了重定向 URI | 启用 CIMD、预注册客户端,或为本地客户端把 application_type 设为 native |
| 授权服务器接受了令牌,但 MCP 服务器返回 401 | 受众不匹配 | 让令牌受众与 resource 参数中的服务器规范 URI 一致 |
| 反复出现 403 循环 | 服务器每次只质询一个缺失的权限 | 在一次质询中返回该操作所需的全部权限 |
下一步
从 MCP 服务器目录 中挑一个支持 OAuth 的托管服务器,用你的 客户端 连接它并观察整个流程。如果你在运行自己的服务器,上线前请先阅读 MCP 安全最佳实践。
常见问题
MCP 使用 OAuth 吗?
- 是的。对于使用 HTTP 传输的远程服务器,MCP 授权规范基于 OAuth 2.1,并结合受保护资源元数据、PKCE 和 Resource Indicators。授权是可选的,本地 stdio 服务器则应从环境中读取凭据。
本地 MCP 服务器怎么认证?
- 本地 stdio 服务器通常从环境变量中读取 API Key 或令牌,由客户端在启动服务器时传入。规范指出 stdio 实现不应使用 OAuth 流程,而应从环境中获取凭据。
MCP 必须使用动态客户端注册吗?
- 不需要。在 2026-07-28 修订版中,动态客户端注册已被弃用,仅为向后兼容而保留。客户端应优先使用预注册的 client ID,其次是 Client ID Metadata Documents,只有在授权服务器别无支持时才回退到动态注册。
远程 MCP 服务器能用 API Key 吗?
- 很多托管服务器接受放在 Authorization 头中的 API Key,Claude Code 等客户端也允许在服务器配置里设置这个头。这样可以用,但 OAuth 能提供按用户的授权确认、有范围且会过期的令牌,以及更方便的撤销。