模型 : claude-opus-4-6 (anthropic/claude-opus-4-6)
生成日期 : 2026-02-17
远程 MCP Server 通常需要认证才能访问。与内部 API 使用简单的 API Key 不同,MCP 协议采用了业界标准的 OAuth 2.0 协议作为认证方案。OpenCode 实现了完整的 OAuth 2.0 授权码流程(Authorization Code Flow),包括 PKCE 扩展、动态客户端注册、本地回调服务器等。本节将逐一解析这些组件的源码实现。
衍生解释:OAuth 2.0 与 Authorization Code Flow
OAuth 2.0 是一个开放的授权标准,被 Google、GitHub、Microsoft 等几乎所有主流互联网服务采用。它的核心思想是:用户可以授权第三方应用(如 OpenCode)访问自己在某个服务上的资源,而无需将自己的密码告诉第三方应用。
Authorization Code Flow(授权码流程)是 OAuth 2.0 中最安全的授权方式,流程如下:
授权服务器将用户重定向回应用,URL 中携带一个"授权码"(authorization code)。
应用使用授权码向授权服务器换取"访问令牌"(access token)。
PKCE(Proof Key for Code Exchange) 是对 Authorization Code Flow 的安全增强,专门为"公共客户端"(如 CLI 应用,无法安全存储 client_secret)设计。它在流程中引入了一对 code_verifier(随机字符串)和 code_challenge(其哈希值),确保即使授权码被截获,攻击者也无法使用它来换取访问令牌。
8.3.1 mcp/auth.ts:认证凭据存储
OAuth 认证涉及多种需要持久化存储的数据——访问令牌、刷新令牌、客户端注册信息、PKCE 的 code verifier、CSRF 防护的 state 参数等。McpAuth 模块负责管理这些数据的存储和读取。
Copy // mcp/auth.ts
export namespace McpAuth {
// OAuth 令牌
export const Tokens = z . object ( {
accessToken : z . string () , // 访问令牌
refreshToken : z . string () . optional () , // 刷新令牌(可选)
expiresAt : z . number () . optional () , // 过期时间(Unix 时间戳)
scope : z . string () . optional () , // 授权范围
} )
// 客户端注册信息(来自动态客户端注册)
export const ClientInfo = z . object ( {
clientId : z . string () , // 客户端 ID
clientSecret : z . string () . optional () , // 客户端密钥
clientIdIssuedAt : z . number () . optional () , // 颁发时间
clientSecretExpiresAt : z . number () . optional () , // 过期时间
} )
// 完整的认证条目
export const Entry = z . object ( {
tokens : Tokens . optional () , // OAuth 令牌
clientInfo : ClientInfo . optional () , // 客户端信息
codeVerifier : z . string () . optional () , // PKCE code verifier
oauthState : z . string () . optional () , // CSRF state 参数
serverUrl : z . string () . optional () , // 这些凭据对应的服务器 URL
} )
} 每个 MCP Server 对应一个 Entry,包含了完整的 OAuth 生命周期中所有需要持久化的数据。
认证数据存储在用户的全局数据目录中:
写入时使用了 0o600 的文件权限,这意味着只有文件所有者(当前用户)可以读写,其他用户和组无权访问:
衍生解释:Unix 文件权限 0o600
在 Unix/Linux 系统中,文件权限由三组 rwx(读/写/执行)位组成,分别对应文件所有者、所属组、其他用户。0o600 以八进制表示,展开为 rw-------:
这是存储敏感凭据文件的标准权限设置,类似于 SSH 的 ~/.ssh/id_rsa 文件的权限要求。
McpAuth 模块实现了一个重要的安全特性——URL 绑定验证 。每组凭据都记录了它们对应的服务器 URL,读取时会验证当前 URL 是否匹配:
这个设计防止了一个微妙但严重的安全问题:如果用户修改了 MCP Server 的 URL(例如从一个可信的服务器改为一个恶意的服务器),旧的令牌不应该被发送到新的 URL。getForUrl() 通过比对 URL 来确保凭据不会被误用。
McpAuth 提供了一组原子化的更新函数,每个函数只修改 Entry 中的特定字段:
8.3.2 mcp/oauth-provider.ts:OAuth Provider 实现
McpOAuthProvider 类实现了 MCP SDK 定义的 OAuthClientProvider 接口,它是 OAuth 流程中的"桥梁"——将 MCP SDK 的 OAuth 需求映射到 OpenCode 的凭据存储系统。
OAuthClientProvider 接口要求实现以下方法,每个方法对应 OAuth 流程中的一个步骤:
OAuth 流程需要一个回调 URL,授权服务器在用户授权后会将浏览器重定向到这个 URL。OpenCode 使用本地 HTTP 服务器(端口 19876)来接收这个回调——这是 CLI 应用实现 OAuth 的标准做法。
这些元数据在动态客户端注册(RFC 7591)时使用。注意 token_endpoint_auth_method 的动态选择——如果用户配置了 clientSecret,使用 client_secret_post 方式认证;否则标记为公共客户端(none),依赖 PKCE 保障安全。
衍生解释:动态客户端注册(RFC 7591)
在传统的 OAuth 中,第三方应用需要预先在授权服务器上注册,获取 client_id 和 client_secret。但对于开源的 CLI 工具来说,这种预注册模式并不实用——用户可能需要连接到任意的授权服务器。
RFC 7591 定义了"动态客户端注册"协议,允许客户端在运行时通过 HTTP 请求向授权服务器注册自己,自动获取 client_id。MCP SDK 内置了对这一协议的支持,当 clientInformation() 返回 undefined 时,SDK 会尝试动态注册。
注意 tokens() 方法中 expires_in 的计算——它将绝对过期时间(expiresAt,Unix 时间戳)转换为相对剩余时间(expires_in,秒数),因为 OAuth 标准使用相对时间格式。
PKCE 和 State 参数
这些方法由 MCP SDK 在 OAuth 流程的不同阶段调用。codeVerifier 用于 PKCE 安全机制,state 用于 CSRF(跨站请求伪造)防护——两者都是 OAuth 安全最佳实践的关键组件。
8.3.3 mcp/oauth-callback.ts:OAuth 回调处理
在 OAuth 的 Authorization Code Flow 中,用户在浏览器中完成授权后,授权服务器需要将授权码回传给应用。对于 CLI 应用来说,接收这个回调的唯一方式是在本地启动一个 HTTP 服务器。McpOAuthCallback 模块正是负责这个任务的。
这个回调服务器基于 Bun 的内置 HTTP 服务器实现,监听固定端口 19876。值得注意的是 ensureRunning() 的幂等性设计——如果服务器已经在运行就直接返回,如果端口被占用(可能是另一个 OpenCode 实例)也不会报错。
CSRF 防护:State 参数验证
回调处理的核心是 State 参数的验证,这是防止 CSRF 攻击的关键:
State 参数的工作原理:
衍生解释:CSRF(跨站请求伪造)攻击
CSRF 攻击是一种让用户在不知情的情况下执行非预期操作的攻击方式。在 OAuth 场景中,攻击者可能诱导用户的浏览器向回调 URL 发送一个伪造的请求,试图将攻击者自己的授权码注入到受害者的应用中。
State 参数是 OAuth 2.0 推荐的 CSRF 防护机制。应用在发起授权请求时生成一个随机的 state 值并暂存,当回调到来时验证 state 是否匹配——只有从应用自身发起的授权流程才能通过这个验证。
在 startAuth() 函数中,state 参数使用密码学安全的随机数生成器创建:
这生成了一个 64 字符的十六进制字符串(32 字节的随机数据),具有 256 位的熵——足以抵御暴力猜测攻击。
当回调成功到来时,通过 Promise 的 resolve 将授权码传递回等待的代码:
这是一个经典的"Promise + 外部 resolve"模式——waitForCallback 创建一个 Promise 并将其 resolve 函数存储在 Map 中。当回调到来时,从 Map 中取出对应的 resolve 函数并调用,从而"解开"等待中的 Promise。
将所有组件串联起来,完整的 OAuth 认证流程如下:
这段代码中有一个巧妙的工程细节:在打开浏览器之前就注册回调监听 (第 3 步)。这是为了避免一种竞态条件——如果用户的 IdP(身份提供者)有活跃的 SSO 会话,授权可能在浏览器打开后几乎立即完成,回调可能在 waitForCallback 注册之前就到达。通过先注册再打开浏览器,确保不会错过任何回调。
finishAuth() 函数完成最后一步——使用授权码换取访问令牌:
OAuth 回调页面提供了友好的用户反馈。成功时:
页面会在 2 秒后自动关闭。失败时则显示详细的错误信息。
对于无法打开浏览器的环境(如 SSH 远程开发、Docker 容器等),OpenCode 会通过 Bus 事件系统发布 BrowserOpenFailed 事件,让 TUI 界面显示授权 URL 供用户手动复制到浏览器中打开。
本节小结
OpenCode 的 MCP OAuth 认证实现由三个模块组成:McpAuth 负责凭据的持久化存储(使用 0o600 权限保护文件安全),McpOAuthProvider 实现了 MCP SDK 的 OAuthClientProvider 接口(支持预注册客户端和动态客户端注册两种模式),McpOAuthCallback 通过本地 HTTP 服务器接收 OAuth 回调(端口 19876)。整个认证流程采用了 PKCE 扩展和 State 参数双重安全机制,并通过 URL 绑定验证防止凭据被误用到不同的服务器。值得注意的工程细节包括:先注册回调再打开浏览器以避免竞态条件、密码学安全的随机数生成、以及对无浏览器环境的优雅降级处理。