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

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 9, 2026 5:39am

Request Review

@os-zhuang os-zhuang marked this pull request as ready for review July 9, 2026 04:41
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-actions github-actions Bot 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

github-actions Bot commented Jul 9, 2026

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 free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation protocol:ai protocol:system size/xl tests tooling

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