MCP.so
登录
返回博客

MCP 认证详解:OAuth 2.1 在 MCP 中如何工作(2026 指南)

2026年10月15日MCP.so TeamMCP.so Team

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
2Client 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 能提供按用户的授权确认、有范围且会过期的令牌,以及更方便的撤销。