BrainWeb/payload-mcp-oauth

GitHub: BrainWeb/payload-mcp-oauth

为 Payload CMS MCP 插件提供 OAuth 2.1 + PKCE 及动态客户端注册支持,使其能够安全连接到 Claude 桌面端和网页端的认证插件。

Stars: 2 | Forks: 0

# @brainwebuk/payload-plugin-mcp-oauth 为 [`@payloadcms/plugin-mcp`](https://www.npmjs.com/package/@payloadcms/plugin-mcp) 提供 OAuth 2.1 + PKCE + 动态客户端注册支持, 从而使基于 Payload 的 MCP server 可以作为 **Custom Connector 添加到 Claude.ai 中**, 并与现有的 API-key 流程并存。 该插件是**纯粹附加的**:它包装了 MCP endpoint 处理程序,并添加了 OAuth endpoint 和 collections。你现有的 API-key MCP 客户端将继续保持 不变地运行。 - 带有 **PKCE(仅限 S256)** 的 OAuth 2.1 授权码流程 - **动态客户端注册**(RFC 7591)— Claude.ai 自动注册 - 通过 RFC 8414 / RFC 9728 well-known 文档进行发现 - 静态加密的 Token(HMAC-SHA-256);支持刷新和撤销 - **OAuth Clients** 和 **OAuth Tokens** 作为管理 collections 显示在 **MCP** 导航组下(仅限管理员;公共 REST/GraphQL 接口保持关闭) ## 要求 | | Version | |---|---| | `payload` | `^3.0.0` | | `@payloadcms/plugin-mcp` | `^3.0.0` (测试版本 3.85.0) | | `next` | `^14 \|\| ^15 \|\| ^16` (仅适用于导出的 proxy/middleware) | | Node | `>= 20` | ## 安装 ### 1. 添加 package ``` pnpm add @brainwebuk/payload-plugin-mcp-oauth # 或者:npm i / yarn add ``` ### 2. 注册插件(在 `mcpPlugin` 之后) 在 `payload.config.ts` 中,**紧接着** `mcpPlugin()` 之后注册 `payloadMcpOAuth()`, 并向其传递你提供给 `mcpPlugin()` 的**相同** options 对象。 ``` import { mcpPlugin } from '@payloadcms/plugin-mcp' import type { MCPPluginConfig } from '@payloadcms/plugin-mcp' import { payloadMcpOAuth } from '@brainwebuk/payload-plugin-mcp-oauth' import { buildConfig } from 'payload' // Assign ONCE to a const and reuse the same reference in both calls. ⚠️ const mcpOptions: MCPPluginConfig = { collections: { users: { enabled: { find: true, update: true } }, media: { enabled: { find: true, create: true } }, }, } export default buildConfig({ // ...db, collections, admin, etc. plugins: [ mcpPlugin(mcpOptions), payloadMcpOAuth({ issuer: process.env.NEXT_PUBLIC_SERVER_URL || 'http://localhost:3000', mcpPluginOptions: mcpOptions, // ← the SAME object, not a copy }), ], }) ``` ### 3. 添加 proxy (Next.js 16) / middleware (Next.js 14–15) OAuth 发现 (`/.well-known/...`) 和裸主机 MCP 连接器需要两个 Payload 插件无法自行注册的主机级 URL 重写。该插件将它们作为现成的请求处理程序提供 —— 请使用你的 Next.js 版本所使用的约定文件(靠近你的 `app/` 目录)将其接入。重新导出该处理程序,但需将 `config` 声明为**本地**字面量。 **Next.js 16+** — Next 将 `middleware` 约定重命名为 `proxy`。请创建 `src/proxy.ts`: ``` export { mcpOAuthMiddleware as proxy } from '@brainwebuk/payload-plugin-mcp-oauth/middleware' export const config = { matcher: [ '/', '/.well-known/oauth-authorization-server', '/.well-known/oauth-protected-resource', ], } ``` **Next.js 14–15** — `proxy` 约定尚不存在;创建 `src/middleware.ts` 并使用相同的主体,将其导出为 `middleware`: ``` export { mcpOAuthMiddleware as middleware } from '@brainwebuk/payload-plugin-mcp-oauth/middleware' export const config = { matcher: [ '/', '/.well-known/oauth-authorization-server', '/.well-known/oauth-protected-resource', ], } ``` 已经有 proxy/middleware 了?改为组合使用它们(以 Next 16 为例;在 14–15 版本中 将文件命名为 `middleware.ts`,函数命名为 `middleware`): ``` import type { NextRequest } from 'next/server' import { createMcpOAuthMiddleware } from '@brainwebuk/payload-plugin-mcp-oauth/middleware' const mcpOAuth = createMcpOAuthMiddleware() // accepts { apiRoute, mcpEndpointPath, ... } export function proxy(request: NextRequest) { // ...your logic first... return mcpOAuth(request) } export const config = { matcher: ['/', '/.well-known/oauth-authorization-server', '/.well-known/oauth-protected-resource' /* + yours */], } ``` ### 4. 设置环境变量 ``` # 客户端访问的公开 HTTPS URL。用作 OAuth issuer 以及在 discovery metadata 中。 NEXT_PUBLIC_SERVER_URL=https://cms.example.com # 用于对静态 token 进行哈希处理的 HMAC pepper —— 生产环境中必需(>= 32 个字符)。 # 使用以下命令生成:openssl rand -hex 32 PMOAUTH_TOKEN_PEPPER=<64-hex-chars> ``` 在开发环境中,如果未设置 `PMOAUTH_TOKEN_PEPPER`,将使用内置的不安全 pepper(并伴有警告)。在 `NODE_ENV=production` 环境下,如果 它缺失或长度少于 32 个字符,插件将在**启动时报错**。 ### 5. 重新生成管理导入映射(如果你的应用使用了的话) 此插件不注册任何自定义管理组件,因此它*不需要*重新生成导入 映射。如果你的应用已经维护了 `src/app/(payload)/admin/importMap.js`, 在安装后重新生成它是无害的,并且能保持整洁: ``` pnpm payload generate:importmap ``` (如果你的应用不使用 `src` 目录,请去掉 `src/` 前缀。) ### 6. 应用 schema 变更 该插件添加了 collections(`oauth-clients`、`oauth-auth-codes`、`oauth-tokens`、 `oauth-csrf-nonces`)。使用你的应用已经使用的任何 schema 工作流 —— **不要 混用它们**: - **Dev 推送**(在开发环境中 SQLite/Postgres 的默认设置):只需启动应用;新的 表将在下次启动时被推送。**不要**针对推送同步的开发数据库运行 `migrate:create`/`migrate` —— 你会遇到 *"table … already exists"* 错误。 - **Migrations**(生产环境):运行 `pnpm payload migrate:create` 以生成包含 新 collections 的 migration,然后运行 `pnpm payload migrate`。 就这些 —— 启动应用,OAuth endpoint 即上线,并在管理侧边栏的 **MCP** 组下显示 **OAuth Clients** 和 **OAuth Tokens**。 ## 从 Claude.ai 连接 1. Settings → **Connectors** → **Add custom connector**。 2. 输入你的服务器 URL(裸主机,例如 `https://cms.example.com`,即可生效 — middleware 会将其路由到 MCP endpoint)。 3. Claude.ai 发现授权服务器,进行动态注册,并启动 OAuth + PKCE 握手。 4. 你将被引导至 Payload 管理员登录页面 + 同意屏幕;批准以 颁发 token。 验证发现端点是否可访问: ``` curl https://cms.example.com/.well-known/oauth-protected-resource curl https://cms.example.com/.well-known/oauth-authorization-server ``` ## 使 MCP 对 AI agents 可用 连接后,agent 只知道 MCP server *告诉*它的内容。从丰富的 collections(具有嵌套块、条件字段等的页面构建器)生成的工具 体积庞大且不明显,因此 agents 在执行 `create*` 调用时会不断试错。利用 `@payloadcms/plugin-mcp` 暴露的引导渠道来弥合这一差距 —— 所有内容都通过 协议以 **server → agent** 的方式传递,因此它们可以覆盖每个客户端(Claude.ai Web、Desktop、Code 以及 非 Claude 的 MCP 客户端): - **`serverOptions.instructions`** — `mcpPlugin()` 上的一个“如何使用此服务器”字符串。 - **per-collection `description`** — 告诉 agent 何时/为何使用某个 collection。 - **field `admin.description`** — 流入每个工具的输入 schema,因此 agent 会内联读取字段规则(例如“仅在……时需要”)。 - **`prompts`** — agent 可以调用的预构建引导工作流。 ``` mcpPlugin({ serverOptions: { serverInfo: { name: 'Author Website', version: '1.0.0' }, instructions: ` This server manages an author marketing site (pages, posts, media). - Publish by setting "_status": "published". - pages.hero.type is none|lowImpact|mediumImpact|highImpact; high/mediumImpact REQUIRE hero.media (a Media id) — upload first; prefer lowImpact otherwise. - pages.layout is an array of blocks: content, cta, mediaBlock, archive, formBlock. - If a tool schema is large, create a minimal doc first, then add blocks with the update tool.`, }, collections: { pages: { description: 'Landing/marketing pages built from a hero + layout blocks.', enabled: { find: true, create: true, update: true }, }, }, }) ``` ### Claude Code 的安装助手(可选) 此 repo 还充当具有 `install` 技能的 Claude Code 插件市场,该技能将 引导 Claude Code 完成插件的配置(config、proxy、env、schema)以及 常见陷阱。在 Claude Code 中: ``` /plugin marketplace add BrainWeb/payload-mcp-oauth /plugin install payload-mcp-oauth@brainwebuk ``` 然后要求 Claude Code "install payload-plugin-mcp-oauth"(或运行 `/payload-mcp-oauth:install`)。这有助于安装插件的**开发者**; 因为 Skills 是客户端的,它对运行时的 connector agent 没有影响。 ## 配置 | Option | Type | Default | Description | |---|---|---|---| | `issuer` | `string` | — (必填) | 公共基础 URL;OAuth 颁发者 + metadata 基础。 | | `mcpPluginOptions` | `MCPPluginConfig` | — (必填) | 传递给 `mcpPlugin()` 的**相同**对象。 | | `userCollection` | `string` | `'users'` | 包含用户账号的 collection。 | | `disabled` | `boolean` | `false` | 在不卸载的情况下关闭 OAuth:无 endpoint,无 token 接入,`mcpPluginOptions` 不受影响(API-key MCP 继续工作)。Collections 保持注册以维持 schema 一致性。当设置了 `mcpPluginOptions.disabled` 时也会自动检测。 | | `adminAccess` | `Access` | `userCollection` 中经过身份验证的用户 | 谁可以在管理界面中查看/管理 OAuth collections。见下文。 | | `accessTokenTtlSeconds` | `number` | `3600` | Access-token 生命周期。 | | `refreshTokenTtlSeconds` | `number` | `86400` | Refresh-token 生命周期。 | | `authCodeTtlSeconds` | `number` | `300` | 授权码生命周期。 | | `rateLimits` | `RateLimitOptions` | `{}` | 针对 endpoint 的速率限制覆盖。 | ### 管理界面与访问 `oauth-clients` 和 `oauth-tokens` 作为 collections 显示在 **MCP** 导航 组下(与 MCP 插件的 API Keys 并列)。`read`/`update`/`delete` 由 `adminAccess` 控制;始终拒绝 `create`(客户端通过 DCR 自行注册,tokens 由 token endpoint 生成)。`oauth-auth-codes` 和 `oauth-csrf-nonces` 保持 隐藏并完全锁定。 默认的 `adminAccess` 授权**在你的 `userCollection` 中**的任何已验证用户, 并拒绝公共 REST/GraphQL 接口 —— 这对于 标准入门应用是正确的,其中 `users` 仅包含操作员。**如果你的 `userCollection` 混合了管理员和不受信任的最终用户,请传递你自己的规则:** ``` payloadMcpOAuth({ issuer, mcpPluginOptions: mcpOptions, adminAccess: ({ req }) => req.user?.role === 'admin', }) ``` ### 添加的 Endpoints `GET /.well-known/oauth-authorization-server`, `GET /.well-known/oauth-protected-resource`, `POST /api/oauth/register`, `GET /api/oauth/authorize`, `POST /api/oauth/consent`, `POST /api/oauth/token`, `POST /api/oauth/revoke`. OAuth tokens 使用 `pmoauth_` 前缀。MCP 处理程序检查 Bearer 值: `pmoauth_…` 走 OAuth 路径;其他任何内容则原封不动地委托给原始的 API-key 处理程序。 ## 故障排除 | 症状 | 可能原因 | |---|---| | `Error: payloadMcpOAuth must be registered AFTER mcpPlugin()` | 插件顺序 —— 将 `payloadMcpOAuth()` 放在 `mcpPlugin()` 之后。 | | OAuth tokens 报 401 错误,但 API keys 正常工作 | `mcpPluginOptions` 不是**相同**的对象引用(步骤 2)。 | | `/.well-known/...` 返回应用的 HTML / 404 | 缺少 `proxy.ts` / `middleware.ts` 或其 `matcher` 未包含 well-known 路径(步骤 3)。 | | **所有**路由均报 500 错误;日志提示 *"can't recognize the exported `config` field … it mustn't be reexported"* | `config` 是从 `…/middleware` 重新导出的,而不是在你的 `proxy.ts` / `middleware.ts` 中声明为本地字面量(步骤 3)。 | | 出现 `The "middleware" file convention is deprecated` 警告 (Next 16) | 将 `src/middleware.ts` 重命名为 `src/proxy.ts` 并将处理程序导出为 `proxy`(步骤 3)。 | | 同意屏幕正常显示,但 **Approve** 返回 `401 access_denied / "Authentication required"` | **≤ 0.3.0** 版本中的插件 bug:同意页面发送了 `Referrer-Policy: no-referrer`,因此浏览器在 Approve 的 POST 请求中发送了 `Origin: null`,而 Payload 丢弃了会话(`GET` 渲染没有 `Origin`,所以它正常工作;但 `POST` 不行)。**在 0.3.1 中已修复 —— 请升级。** 如果在 ≥ 0.3.1 版本中仍然存在,说明你的 `serverURL` 与浏览器使用的源不匹配 —— 请检查 `NEXT_PUBLIC_SERVER_URL`(确切的 scheme + host,没有尾部斜杠)。 | | 管理导航中缺少 **OAuth Clients / OAuth Tokens**,或者它们的面板显示 *"Nothing found"* | 登录用户未被 `adminAccess` 授权。默认情况下,他们必须属于 `userCollection`;对于混合角色的应用,请传递自定义的 `adminAccess`(参见 *管理界面与访问*)。 | | `migrate` 失败并提示 *"table … already exists"* | 你对已经由 dev push 创建的数据库运行了 `migrate` —— 请选择一种工作流(步骤 6)。 | | 在 `pnpm dev` 中重建 `payload_locked_documents_rels` 时出现 `SQLITE_ERROR: no such column: oauth_clients_id` | SQLite push 无法将新 collections 的 lock-FK 列添加到**已经推送**的数据库中(Payload/drizzle 重建的一个怪癖)。**在 0.3.2 中已修复** —— OAuth collections 设置了 `lockDocuments: false`,因此它们不在那里添加任何列。在 ≤ 0.3.1 版本上:在首次启动*之前*添加插件,或者重置开发数据库(`rm your.db*`)以便全新创建 schema。 | | 在开发环境中启动正常,但在部署时报错 | 在生产环境中未设置 `PMOAUTH_TOKEN_PEPPER`(步骤 4)。 | ## 开发 此 repo 是一个 pnpm workspace:已发布的插件位于 `packages/plugin` 中,而 `examples/payload-app` 是一个参考的 Payload 3 应用(SQLite用于集成 测试。 ``` pnpm install # once, at the repo root pnpm dev:example # run the reference example app (workspace source) pnpm --filter ./packages/plugin build # build the plugin pnpm test # unit tests across the workspace pnpm typecheck # type-check pnpm lint # lint ``` ### 从打包的插件启动测试站点 `test:install:serve` 将**打包**的插件(`pnpm pack`,即真正 发布的构件)安装到一个临时应用中,完全按照上述文档说明进行连接,并保持 `next dev` 运行,以便你可以四处点击体验 —— 包括 OAuth 管理界面。 ``` pnpm test:install:serve # http://localhost:3000 pnpm test:install:serve -- --port 4000 pnpm test:install:serve -- --reuse # skip the rebuild, reuse the last install (fast restart) pnpm test:install:serve -- --live # expose a public HTTPS URL (Cloudflare tunnel) for Claude.ai ``` `--live` 会打开一个 Cloudflare 快速隧道(需要 [`cloudflared`](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/); 无需登录),将该公共 HTTPS URL 配置为 OAuth 颁发者和 Payload `serverURL`,并打印将站点添加为 **Claude.ai 中的 Custom Connector** 的步骤 — 这样你就可以针对你的本地构建驱动真实的 OAuth 连接。 它会打印 URL 以及预设的管理员登录信息(`install-test@example.com` / `install-test-password-123`)。它是**默认全新**的 —— 每次启动都会从全新打包的插件重新配置, 因此你永远不会点击浏览过时的构建;一旦某次启动成功,可以传递 `--reuse` 以实现更快的重启。首次运行速度很慢(完整的 `pnpm install` + 冷编译 —— 需要几分钟时间)。按 Ctrl+C 停止。 ### 运行全新安装测试 ``` pnpm test:install # asserts the full install + OAuth handshake end to end pnpm test:install -- --keep # keep the temp app on success, for debugging ``` 这是驱动打包、管理导入映射、schema 推送、 OAuth + PKCE 握手、管理可见性/访问权限控制、禁用矩阵以及 增量安装的测试工具 —— 请参阅 [`scripts/install-test/README.md`](scripts/install-test/README.md) 了解其检查内容和原因的完整列表。 ## License MIT 由位于英国诺福克的网页设计工作室 [BrainWeb](https://www.brainweb.co.uk/) 构建和维护
标签:Claude, CVE检测, MCP, MITM代理, OAuth2.1, Payload CMS, 自动化攻击