feat(mcp): OAuth 2.1 authorization for /api/v1/mcp — self-serve connect for claude.ai / Claude Desktop / Claude Code (#2698)#2709
Merged
Conversation
Add the spec-compliant MCP authorization flow so any OAuth-capable MCP client (claude.ai custom connectors, Claude Desktop, Claude Code) can connect to a deployment self-serve, while API keys stay the unchanged headless track. Each deployment is its own authorization server, backed by the embedded better-auth oauth-provider — no central authority. - spec: MCP_OAUTH_SCOPES tool-family scope constants (data:read, data:write, actions:execute); ExecutionContext.oauthScopes; plugins.dynamicClientRegistration auth-config field (reference docs regenerated) - plugin-auth: AS auto-enables with OS_MCP_SERVER_ENABLED (explicit OS_OIDC_PROVIDER_ENABLED still wins); unauthenticated RFC 7591 DCR (OS_OIDC_DCR_ENABLED override); MCP scopes + RFC 8707 validAudiences on the provider; RFC 9728 protected-resource metadata (+ path-inserted variant) and the RFC 8414 path-inserted AS metadata alias; local fail-closed JWT access-token verification against the deployment's own JWKS (verifyMcpAccessToken); OAuth 2.1 TLS rule (loopback exempt) gates the whole track - runtime: MCP-only OAuth bearer provenance in resolveExecutionContext (delegating to the single shared resolveAuthzContext; a presented but invalid JWT bearer never falls back to an ambient session); 401s from /api/v1/mcp advertise resource metadata via WWW-Authenticate; tokens granting no MCP scope get 403 insufficient_scope; granted scopes are forwarded to tool registration - mcp: grantedScopes narrows tool-family registration fail-closed; SKILL.md Connect section + README document both auth tracks - cli: OS_MCP_SERVER_ENABLED=true auto-loads the MCP plugin so the flag alone yields a connectable endpoint - tests: token verification (real jose crypto: signature/issuer/ audience/expiry/M2M), scope gating, dispatcher fail-closed matrix, DCR/flag wiring, API-key regression suite Verified end-to-end against a live dev instance: discovery -> DCR -> PKCE login + consent -> token (aud=/api/v1/mcp) -> MCP SDK client tool calls under the logged-in user's permissions/RLS; scope narrowing and forged/expired-token rejection included. Closes #2698 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V4W4bMy8nMNGKFxTCyrfrd
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V4W4bMy8nMNGKFxTCyrfrd
…ur-dxy16k # Conflicts: # packages/plugins/plugin-auth/src/auth-manager.ts
…me was stale) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V4W4bMy8nMNGKFxTCyrfrd
Contributor
📓 Docs Drift CheckThis PR changes 5 package(s): 110 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V4W4bMy8nMNGKFxTCyrfrd
…ur-dxy16k # Conflicts: # packages/plugins/plugin-auth/src/auth-plugin.ts
This was referenced Jul 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2698
为
/api/v1/mcp增加符合 MCP 授权规范的 OAuth 2.1 通道:任何支持 OAuth 的 MCP 客户端(claude.ai 自定义连接器、Claude Desktop、Claude Code)都可以自助连接——浏览器登录、以本人身份运行,无需管理员预先发放 API key。API key 通道(CI / 无头 agent)完全不变,并新增了回归测试。设计决策与理由见 issue #2698(按 owner 决定不写 ADR,理由随 issue + changeset)。实现要点
每个部署就是自己的授权服务器(issue 设计决策 #1)。复用已内嵌的 better-auth
@better-auth/oauth-provider(此前由OS_OIDC_PROVIDER_ENABLED门控、已挂好 authorize/token/consent/revoke 端点与sys_oauth_*表),本 PR 在其上补齐 MCP 所需的部分:/.well-known/oauth-protected-resource(含/api/v1/mcp路径插入变体),并补挂 RFC 8414 路径插入别名/.well-known/oauth-authorization-server/api/v1/auth(issuer 带路径,规范客户端按此形式请求)。/api/v1/mcp的 401 响应通过WWW-Authenticate: Bearer resource_metadata="…"广播元数据地址,客户端由此自举整个流程。OS_OIDC_DCR_ENABLED或新配置字段plugins.dynamicClientRegistration强制开/关。OS_MCP_SERVER_ENABLED=true时 oauthProvider 自动装配(显式OS_OIDC_PROVIDER_ENABLED=false仍优先)。三处 oidcProvider 开关判断收敛到单一resolveOidcProviderEnabled(),避免插件装配 //auth/config/ 发现路由三者漂移。aud经 RFC 8707resource绑定到<origin>/api/v1/mcp,已配置validAudiences)。资源侧在AuthManager.verifyMcpAccessToken()用本部署自己的 JWKS 本地校验(签名/issuer/audience/过期,fail-closed 返回 null;无sub的 client-credentials token 一律拒绝——MCP 面是主体绑定的,无头场景走 API key)。校验通过后仅作为第二种主体来源,角色/权限/RLS 聚合仍走唯一的resolveAuthzContext(check:authz-resolver门禁通过,未复制任何角色解析)。resolveExecutionContext新增 opt-in(仅 dispatcher 的/mcp路径开启)。理由:OAuth token 的粗粒度 scope 只在 MCP 工具分发层被强制,REST/GraphQL 不做该强制,故不给它们 OAuth 通道。fail-closed:凡出示了 JWT 形态的 bearer 而校验失败(未知/过期/audience 不符),请求按匿名处理并 401,绝不回落到浏览器 cookie 会话。data:read→ list/describe/query/get,data:write→ create/update/delete,actions:execute→ list_actions/run_action。常量单源在@objectstack/spec/ai(MCP_OAUTH_SCOPES)。授权范围外的工具不注册(SDK 视为 unknown tool 拒绝);token 一个 MCP scope 都没有则直接 403insufficient_scope。API key / session 主体不受 scope 限制(维持原行为)。scope 只收窄工具面——每次调用仍受主体的权限与 RLS 约束。OS_MCP_SERVER_ENABLED=true现在会自动装配 MCP 插件(此前 dispatcher 按该环境变量放行路由、插件却要求 configrequires:['mcp'],单开开关会得到 501)。测试
plugin-auth(31 新增):真实 jose 签名的 JWT 校验矩阵(签名/issuer/audience/过期/M2M/垃圾输入)、TLS 规则、开关解析、oauthProvider 装配(DCR 标志、scope、validAudiences)。runtime(8 新增,走完整dispatch()管道):401+WWW-Authenticate(OAuth 关闭时保持纯 401)、token 主体绑定与 scope 透传、403 insufficient_scope、无效 bearer 不回落 cookie 会话、API key 回归(x-api-key/Bearer osk_…不受影响、不被 scope 限制)。mcp(9 新增):scope → 工具族注册收窄 + 分发时拒绝。check:authz-resolver通过。端到端验证(真实 dev 实例)
针对
objectstack dev --fresh(showcase 应用,OS_MCP_SERVER_ENABLED=true)以官方@modelcontextprotocol/sdk客户端(Claude Code 同款传输实现)完成 35/35 项协议级检查:401 自举 → 两级 PRM → 路径插入 AS 元数据 → 未认证 DCR → PKCE(S256)+resource绑定 → consent → token(aud=/api/v1/mcp,含 refresh token)→initialize/tools/list/tools/call以登录用户身份跑通(RLS 生效、sys_*隐藏)→ 只读 scope 收窄 → 伪造 token 401 → API key 全通道回归。说明:浏览器内交互登录/consent 页面由 Console SPA(objectui,/_console/oauth/consent)承载,本仓库 e2e 以协议等价方式调用同一 consent 端点;真实浏览器全流程属 dogfood 验证,配套 Setup「Connect an agent」页面在 issue 列明的后续任务中。文档(验收项)
packages/mcp/README.md:新增远程连接章节(OAuth 人机通道 + API key 无头通道,含 claude.ai 公网可达性说明)。packages/mcp/src/skill.ts:生成的 SKILL.md「Connect」改为双通道。content/docs/ai/agents.mdx:新增「Connect your AI (BYO-AI over MCP)」逐客户端接入表 + 私有部署可达性说明(本地客户端可达内网;claude.ai web 需公网 HTTPS)。content/docs/getting-started/build-with-claude-code.mdx:补上具体连接片段。ExecutionContext.oauthScopes、plugins.dynamicClientRegistration)已重新生成 reference docs。🤖 Generated with Claude Code
https://claude.ai/code/session_01V4W4bMy8nMNGKFxTCyrfrd
Generated by Claude Code