Skip to content

feat(mcp): OAuth 2.1 authorization for /api/v1/mcp — self-serve connect for claude.ai / Claude Desktop / Claude Code (#2698) - #2709

Merged
os-zhuang merged 6 commits into
mainfrom
claude/dazzling-pasteur-dxy16k
Jul 9, 2026
Merged

feat(mcp): OAuth 2.1 authorization for /api/v1/mcp — self-serve connect for claude.ai / Claude Desktop / Claude Code (#2698)#2709
os-zhuang merged 6 commits into
mainfrom
claude/dazzling-pasteur-dxy16k

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

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 所需的部分:

  • 发现元数据:新增 RFC 9728 /.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="…" 广播元数据地址,客户端由此自举整个流程。
  • DCR(RFC 7591)强制启用:MCP 打开时默认允许未认证动态注册(MCP 客户端在用户登录前注册;有插件级限流,且匿名注册本身不授予任何权限——拿 token 仍需交互式 PKCE 登录 + consent)。可用 OS_OIDC_DCR_ENABLED 或新配置字段 plugins.dynamicClientRegistration 强制开/关。
  • AS 随 MCP 自动启用:OS_MCP_SERVER_ENABLED=true 时 oauthProvider 自动装配(显式 OS_OIDC_PROVIDER_ENABLED=false 仍优先)。三处 oidcProvider 开关判断收敛到单一 resolveOidcProviderEnabled(),避免插件装配 / /auth/config / 发现路由三者漂移。
  • Token → ExecutionContext 单源解析:访问令牌为 jwt 插件签发的 JWT(aud 经 RFC 8707 resource 绑定到 <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 门禁通过,未复制任何角色解析)。
  • OAuth bearer 仅在 MCP 路由生效:resolveExecutionContext 新增 opt-in(仅 dispatcher 的 /mcp 路径开启)。理由:OAuth token 的粗粒度 scope 只在 MCP 工具分发层被强制,REST/GraphQL 不做该强制,故不给它们 OAuth 通道。fail-closed:凡出示了 JWT 形态的 bearer 而校验失败(未知/过期/audience 不符),请求按匿名处理并 401,绝不回落到浏览器 cookie 会话。
  • 粗粒度 scope → 工具族(在工具分发处强制):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 都没有则直接 403 insufficient_scope。API key / session 主体不受 scope 限制(维持原行为)。scope 只收窄工具面——每次调用仍受主体的权限与 RLS 约束。
  • TLS 强制(localhost 豁免,OAuth 2.1):非回环的纯 HTTP 部署上整个 OAuth 通道保持关闭(不发布元数据、不接受 bearer),端点仅 API key 可用,并在启动日志明确警告。
  • CLI:OS_MCP_SERVER_ENABLED=true 现在会自动装配 MCP 插件(此前 dispatcher 按该环境变量放行路由、插件却要求 config requires:['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 → 工具族注册收窄 + 分发时拒绝。
  • 全量套件绿:spec 6669 / cli 466 / runtime 469 / plugin-auth 292 / mcp 62;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:补上具体连接片段。
  • spec schema 变更(ExecutionContext.oauthScopesplugins.dynamicClientRegistration)已重新生成 reference docs。

🤖 Generated with Claude Code

https://claude.ai/code/session_01V4W4bMy8nMNGKFxTCyrfrd


Generated by Claude Code

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
@vercel

vercelBot commented Jul 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 9, 2026 5:39am

Request Review

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V4W4bMy8nMNGKFxTCyrfrd
claude added 2 commits July 9, 2026 05:01
…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
@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file protocol:system tests protocol:ai tooling labels Jul 9, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 5 package(s): @objectstack/cli, @objectstack/mcp, @objectstack/plugin-auth, @objectstack/runtime, @objectstack/spec.

110 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/actions-as-tools.mdx(via @objectstack/mcp)
  • content/docs/ai/agents.mdx(via @objectstack/mcp, @objectstack/spec)
  • content/docs/ai/index.mdx(via @objectstack/mcp)
  • content/docs/ai/natural-language-queries.mdx(via @objectstack/mcp)
  • content/docs/ai/skills-reference.mdx(via packages/cli, @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/api/data-flow.mdx(via @objectstack/cli)
  • content/docs/api/environment-routing.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/mcp, @objectstack/runtime, @objectstack/spec)
  • content/docs/api/wire-format.mdx(via @objectstack/runtime)
  • content/docs/automation/approvals.mdx(via packages/spec)
  • content/docs/automation/flows.mdx(via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via packages/cli, @objectstack/runtime, packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via packages/spec)
  • content/docs/concepts/north-star.mdx(via packages/runtime, packages/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/runtime, @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/environment-variables.mdx(via @objectstack/mcp)
  • content/docs/deployment/index.mdx(via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx(via @objectstack/plugin-auth, @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx(via @objectstack/runtime)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/deployment/vercel.mdx(via @objectstack/runtime)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/cli.mdx(via @objectstack/cli, @objectstack/plugin-auth, @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via packages/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx(via packages/cli)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/cli, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via packages/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/plugin-auth, @objectstack/spec)
  • content/docs/permissions/authentication.mdx(via @objectstack/cli, @objectstack/plugin-auth, @objectstack/runtime)
  • content/docs/permissions/authorization.mdx(via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx(via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via @objectstack/spec)
  • content/docs/permissions/profiles.mdx(via @objectstack/spec)
  • content/docs/permissions/roles.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/spec)
  • content/docs/permissions/sso.mdx(via @objectstack/plugin-auth)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/plugin-auth, @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/cli, @objectstack/mcp, @objectstack/plugin-auth, @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/mcp, @objectstack/spec)
  • content/docs/protocol/objectos/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/objectos/http-protocol.mdx(via @objectstack/runtime)
  • content/docs/protocol/objectos/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/objectos/index.mdx(via @objectstack/runtime)
  • content/docs/protocol/objectos/lifecycle.mdx(via @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/objectos/plugin-spec.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/protocol/objectos/realtime-protocol.mdx(via @objectstack/cli)
  • content/docs/protocol/objectos/runtime-capabilities.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via packages/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx(via @objectstack/cli, @objectstack/mcp, @objectstack/plugin-auth, @objectstack/runtime, @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/plugin-auth, @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/setup-app.mdx(via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

…ur-dxy16k
# Conflicts:
#	packages/plugins/plugin-auth/src/auth-plugin.ts
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependenciesPull requests that update a dependency filedocumentationImprovements or additions to documentationprotocol:aiprotocol:systemsize/xlteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(mcp): OAuth 2.1 authorization for /api/v1/mcp (self-serve connect from claude.ai / Claude Code / Desktop)

2 participants

@os-zhuang@claude