WorkBuddy MCP 连接器 OAuth 2.1 对接
概述
WorkBuddy 连接器的 MCP 端点走 MCP 标准 OAuth 2.1 授权:WorkBuddy 是公共客户端(不持有 client_secret)兼 MCP Client,首次连接时自动完成「发现 → 动态注册 → 浏览器授权 → 令牌兑换 → 自动续期」全流程。官方文档(open.workbuddy.cn/docs/connector)公开了协议契约——五组端点、TTL 约定、回调白名单;但如何在服务端把这条链路正确落地官方没有给出,且存在大量只有实战才会暴露的坑:客户端行为假设、网关路径形态、HTTP 头字符集、审核规则。
本 skill 提炼自一套已上线并通过官方审核的生产实现,补齐官方文档之外的三块内容:
- 分端实现指南:授权服务器(AS)、资源服务器(RS)、同意页三端的职责边界与实现要点;
- 生产踩坑清单:约 20 条「现象 → 根因 → 修法」,每条都来自真实事故或官方审核驳回;
- 连接器打包与提审避坑:包格式、审核前置条件、驳回分类与整改方式。
本 skill 以 WorkBuddy 为例,但 RS/AS 划分、RFC 9728/8414/7591 流程与坑清单适用于任何实现了 MCP OAuth 的客户端。
何时使用
当用户:
- 要把一个 MCP Server(streamableHttp)接入 WorkBuddy,被要求支持 OAuth 登录授权;
- 正在实现或评审 OAuth 2.1 授权服务器 / 资源服务器 / 授权同意页,服务对象是 WorkBuddy 这类 MCP 客户端;
- 打包 WorkBuddy 连接器(mcp.json / connector-meta.json)并准备提审;
- 排查连接器授权故障:首次连不上、能用但一小时后批量 401、refresh 失败、重新授权死循环、伪造凭证也能拿到工具列表等。
不适用:与 WorkBuddy/MCP OAuth 无关的通用 OAuth 问题(自身 Web App 的登录态方案、第三方平台 OAuth 登录等)。其中的安全模式可参考,但 TTL、回调格式、审核规则等契约细节以 WorkBuddy 官方文档为准。
先建立全局图景:三个角色
向用户讲清楚角色划分,这是后面一切设计决策的基础:
| 角色 | 典型承担者 | 职责 |
|---|---|---|
| 客户端(Client) | WorkBuddy | OAuth 2.1 公共客户端(无 secret)+ MCP Client;内置 OAuth 管理器自动完成发现/注册/授权/续期 |
| 授权服务器(AS) | 你的业务后端 | OAuth 协议端点 + 令牌签发 + grant 管理;同时是被透传调用的业务 API |
| 资源服务器(RS) | 你的 MCP Server | MCP streamableHttp 端点;纯透传管道,不验签,有效性由 AS 裁决 |
| 同意页 | 你的 Web 前端 | 浏览器里展示授权范围与数据范围、一键同意/拒绝 |
三条关键原则(后面每条坑都和它们有关):
- RS 不验签:MCP Server 只透传 Bearer,越权与否永远由 AS 每请求实时裁决——单一裁决点,下游业务零改动;
- 协议端点裸写 RFC 格式:register/authorize/token/revoke 的响应必须是标准 OAuth 客户端认的
{error, error_description},绝不能套全站统一响应壳; - 同意页必须在用户登录态所在的前端域:登录 cookie 通常是 host-only,放后端域读不到。
六阶段端到端流程
WorkBuddy(Client) MCP Server(RS) 业务后端(AS) 同意页(前端)
│ ①POST /mcp 无凭证 │ │ │
├────────────────────────►│ 401 + WWW-Authenticate │ │
│ ②GET /.well-known/oauth-protected-resource │ │
├────────────────────────►│ 元数据→authorization_servers │
│ ③GET /.well-known/oauth-authorization-server │ │
├───────────────────────────────────────────────────►│ 端点清单+能力声明 │
│ ④POST /register (DCR) │ │ 201 client_id │
│ ⑤GET /authorize?code_challenge(PKCE S256) │ │
├───────────────────────────────────────────────────►│ 302 同意页?req=<uuid> │
├─────────────────────────────────────────────────────────────────────────►│ 登录→确认数据范围→同意
│ ⑥302 回调 workbuddy://…?code=…&state=… │ 授权码(≤10min,一次性) │
├───────────────────────────────────────────────────►│ ⑦POST /token 兑换 │
│ access(~1h) + refresh(≥30d) │ │
│ ⑧POST /mcp Authorization: Bearer <access> │ │
├────────────────────────►│ 原样透传 Bearer ────────►│ 每请求实时裁决 │
│ ⑨1h 后 401 → POST /token (refresh_token) 轮换 │ 复用检测 │
│ ⑩用户断开 → RFC 7009 revoke / 授权应用删除 → 下一请求即 401 │
实施路径决策树
先问清用户现状,再选路径:
你的现状?
├─ A. 已有业务后端 + 用户体系(登录 JWT),MCP Server 只是聚合层
│ → 推荐:后端做 AS,MCP Server 做 RS 纯透传;scope 复用现有权限码
│ 体系,下游鉴权零改动。→ 加载 as-implementation.md + rs-implementation.md
├─ B. 只有 MCP Server,没有独立业务后端或无用户体系
│ → 在 MCP Server 同进程或旁路服务里自建 AS(仍需一个能发登录会话的
│ 前端做同意页)。→ 加载 as-implementation.md(重点:数据模型与
│ 最小端点集)+ consent-page.md
└─ C. 只想先跑通、暂无 OAuth 开发预算
→ 先支持用户自填 Token/PAT(连接器 auth_mode:"token" + token-schema)
快速验证业务价值;注意同一服务若两种授权方式并存,须用两个不同
source 分别提审。正式体验仍需回到 A/B 补 OAuth。
→ 加载 connector-package.md
工作流程
按顺序执行。不要跳过第 1 步直接写代码。
第 1 步 — 确认现状与拓扑
向用户确认:有没有业务后端和用户体系?MCP Server 与后端是否同域?前端登录 cookie 在哪个域?据此按上面的决策树选定路径。同域部署(nginx 按路径分流)是最简单形态;跨域部署必须检查三点:issuer 与端点可达性、同意页域、回调白名单。
第 2 步 — 加载对应参考文档
- RS 侧(MCP Server):读
references/rs-implementation.md - AS 侧(协议端点/令牌/grant):读
references/as-implementation.md - 同意页:读
references/consent-page.md - 连接器打包与提审:读
references/connector-package.md - 排障或评审已有实现:读
references/pitfalls.md,逐条对照
第 3 步 — 分端实施(按 checklist 逐项落地)
RS 侧最小清单:
- [ ] 无凭证/无效凭证请求返回真实 HTTP 401 +
WWW-Authenticate(RFC 9728 §5.1),resource_metadata指向可达的元数据地址 - [ ] 注册 root 版 + path-aware 版两条 well-known 元数据路由(客户端两种实现都有)
- [ ] 对过期令牌做本地 exp 预检并先行 401——这是续期能否触发的生死线
- [ ] 凭证按形态分流工具面(PAT / OAuth JWT / 登录 JWT / 未知→最小面)
- [ ]
tools/list前置到后端真验证凭证(伪造 Bearer 拿到 200 工具列表会被官方审核驳回) - [ ] HTTP server body limit 调大到能装下 MCP 最大 payload(框架默认常为 1MB)
AS 侧最小清单:
- [ ] RFC 8414 元数据:仅
authorization_code/refresh_token、仅response_type=code、仅 PKCES256、token_endpoint_auth_methods_supported: ["none"] - [ ] RFC 7591 动态注册:回调白名单(
workbuddy://前缀 + 本机回环 http)、注册指纹幂等(重连会重复注册)、响应回显提交原文 - [ ] authorize:client/redirect 校验失败直接 400 绝不重定向(防 open redirect);其余校验失败 302 回调带
error+state - [ ] 授权码一次性(原子消费)+ 消费墓碑(重放检测)+ 重放连坐吊销派生令牌
- [ ] 同意页会话(Redis,600s,取用即删);授权绑定明确的数据范围(多租户系统适用)
- [ ] access JWT 带
typ:'oauth'、cid、scope、jti扩展 claim;refresh 只存哈希 - [ ] refresh 轮换 + 复用检测:哈希索引必须含已轮换历史行,命中 rotated 行 = 复用攻击 → 吊销整条 grant
- [ ] RFC 7009 revoke:无效令牌同样返回 200(不泄漏令牌有效性)
- [ ] 每请求实时裁决:grant active + 实时权限 ∩ scope(断开授权/角色变更立即生效)
- [ ] 协议端点直写 RFC 6749 §5.2 错误格式,不经全站响应拦截器
连接器包清单:见 references/connector-package.md 的提审检查项。
第 4 步 — 端到端自测
至少覆盖这几条链路(对应 pitfalls.md 的编号):
- 首连全流程:无凭证 401 → 元数据 → 注册 → 浏览器授权 → 兑换成功;
- 过期续期:access 过期后下一请求触发真 401 → 自动 refresh → 原请求自动重试成功;
- 断开即失效:授权应用里断开后,未过期的 access 下一请求即 401;
- 伪造凭证:手搓假 Bearer 调
tools/list应得 401 而非 200; - 安全攻击面:授权码重放 → 派生令牌全吊销;并发双刷 refresh → grant 吊销。
第 5 步 — 打包提审
按 references/connector-package.md:包格式 → 生产部署验证(已改未部署 = 没改,官方实测评审抓的是生产真实行为)→ 提审。驳回后按驳回分类整改:话术类改文案、功能类补代码并升版本重打包。
生产坑速查(Top 5,全文见 references/pitfalls.md)
| # | 现象 | 根因 |
|---|---|---|
| 1 | 首连能用,约 1 小时后批量失败,且永不自动恢复 | RS 把 401 包进了 JSON-RPC 错误文本(HTTP 200),客户端永远等不到续期信号 |
| 2 | 首次连接无法启动授权 | resource_metadata 用了 path-aware 形式,网关未路由该路径 → 404 |
| 3 | 伪造 Bearer 也能拿到工具列表(审核驳回) | tools/list 未前置后端验证凭证 |
| 4 | 标准 OAuth 客户端全部报错 | 协议端点响应被全站 {success} 统一壳包裹 |
| 5 | WWW-Authenticate 带中文导致连接异常 | HTTP 头仅允许 ASCII,本地化文案只能放 body |
参考文档索引
| 文件 | 内容 |
|---|---|
references/as-implementation.md |
AS 侧:端点契约表、DCR/authorize/token/revoke 实现要点、数据模型、安全清单 |
references/rs-implementation.md |
RS 侧:9728 挑战、透传管道、过期预检、audience 分流、关键代码模式 |
references/consent-page.md |
同意页:域约束、会话设计、数据范围选择、防 open redirect |
references/connector-package.md |
连接器包格式、审核规则、驳回分类、整改流程 |
references/pitfalls.md |
全量踩坑清单(现象→根因→修法→类别) |