Skip to content

feat(mcp): 动态 OAuth 授权(浏览器弹窗登录)——AS + 同意页 + 前端回跳 - #117

Merged
fxbin merged 7 commits into
mainfrom
issue-116-mcp-oauth-auth-server
Oct 17, 2026
Merged

fxbin merged 7 commits into
mainfrom
issue-116-mcp-oauth-auth-server

Conversation

@fxbin

@fxbin fxbin commented Oct 17, 2026 •

Copy link
Copy Markdown
Owner

What & why

MCP server 补齐 AGENT_API.md 声明 not yet implemented 的动态 OAuth 授权:MCP 客户端(Claude Code / ZCode 等)只配置 https://<host>/mcp 一个 URL,首连自动走 OAuth 2.1 授权码 + PKCE + DCR(RFC 7591)弹浏览器登录授权。存量静态 token(个人 API token / session token)用法零改动。

架构:实现官方 SDK(mcp 2.3.0)OAuthAuthorizationServerProvider 协议,AS 路由挂 FastAPI 根路径(issuer=SITE_BASE_URL,SDK 固定根路径 + RFC 8414 元数据要求);三张 PG 表(clients / authorizations / tokens);同意页走前端 /mcp/authorize(复用登录态,顺带给 /login 加 returnTo);refresh 30 天轮换、重放检测整族撤销、client_secret Fernet 落库、raw token 只回传一次(库存 sha256)。

安全加固批次(圆桌评审裁定,4:0/3:1/2:2 共识 + Conductor 裁定,见 commit 4ca2420):

Fixes #116

Area

  • backend
  • frontend
  • database / migration
  • auth
  • docs
  • config / infra

Verification

  • bash backend/scripts/test_full_local.sh(一次性 PG 全量)exit 0——含加固批次后的完整套件
  • 52 个 MCP 测试全绿(service 18 + HTTP 19 + server 15):DCR→authorize(带发起者绑定)→approve(仅发起者)→PKCE 换取→OAuth token 打 /mcp/ 跑 score_items 全链路、匿名 /authorize 跳登录、非发起者 info/approve 404、无发起者行 fail-closed、重放告警 caplog、scope 闸门拒绝/放行、CI 断言工具集=登记集、重放连坐、/revoke 即失效、未注册 redirect_uri 400、并发 approve 单胜
  • alembic 冒烟:upgrade head → downgrade -1 → upgrade(initiator_user_id 列随建表迁移,一次性 PG)
  • npx tsc --noEmit、npm run test:coverage(199 passed + 覆盖率门禁)
  • ruff check / format、check_layering、check_agent_docs
  • Manual smoke:未做真实浏览器端到端(需本地起前后端 + 真 MCP 客户端),以 HTTP 层 ASGI 全链路测试替代

Verifier verdict

pass — 两轮独立复核:①实现后(非实现过程)正交安全审查 PASS,三个次要发现已修(approve 原子化、@track_job、负向测试);②圆桌评审(4 席独立 Agent,纪要 .vidt/roundtables/)对四个残余风险逐项走攻击树与 RFC 条款,裁定全部落地或机器化排期——发起者绑定修复中顾衡(维护者席)核实代码后修正了攻击场景误读并倒戈支持现在修。

Checklist

Notes for reviewer

  • SDK 三坑(代码注释有记录):/revoke 表单必须含 client_secret 键(公共客户端传空值,上游 RevocationRequest 必填声明);provider 的 load_access_token 必须返回 OAuth client_id,否则 SDK 撤销归属校验静默跳过(已与 verifier 的 user.id 映射拆成两个函数);token 模型 expires_at 是 int。
  • 排期项(MCP OAuth 完整 scope 模型 = 首个写操作工具的前置 DoD(含 code 连坐/client GC) #118,机器化触发器=首个写操作工具立项):完整 scope 模型(valid_scopes/RS 强制/validate_token_resource 双路径/文案单一事实源)、③整族撤销+授权表 family 关联列+verifier 豁免、client 垃圾清理(7 天未授权即删,并入 03:55 作业)。
  • 残余已知边界:发起者绑定按 user_id 而非 session(正常跨浏览器授权不受罚);局域网 http 开发环境 issuer 校验不过 → 启动显式 error log + 跳过 AS 挂载(静态 token 不受影响)。
  • 迁移是纯增量三表(含 initiator_user_id),downgrade 直接删表;已在一次性库验过 up/down/up。

fxbin added 4 commits October 17, 2026 19:59
三张表(DCR 客户端 / 授权请求-授权码一体 / access+refresh token 族)+
业务编排(单次性授权码、refresh 轮换与重放连坐撤销、Fernet secret),
TopicEyeTokenVerifier 先查 OAuth access token 再回落存量静态 token。
AS 路由挂 FastAPI 根路径(issuer=SITE_BASE_URL,非法时显式报错跳过),
同意页三个会话认证端点 /api/v1/mcp-oauth/*,/authorize /token /register
限流,调度器每日清理过期授权行与 token。
/mcp/authorize 同意页复用登录态(未登录带 returnTo 跳 /login,回跳地址
净化防开放重定向),next 代理 /authorize /token /register /revoke 与
/.well-known 保证单源部署 issuer 可达。
AGENT_API.md 补全 AS 端点表、两种认证方式与运维注记(含 /revoke 需带
client_secret 键的 SDK quirk);AGENTS.md 更新 app/mcp 入口层职责描述。
fxbin added 3 commits October 17, 2026 23:36
① /authorize 换自建包装路由:解析发起者会话经 contextvar 入库(initiator_user_id
并列进未上线迁移),匿名先跳登录页带 returnTo 续传,info/approve/deny 仅发起者
可用——堵住「攻击者发起、诱导受害者批准」的 approve-any 冒用路径。
② 工具级 scope 中央闸门 fail-closed(app/mcp/scopes.py):未登记工具直接拒绝,
CI 断言工具集与登记集一致,首个写工具的真实 scope 语义排期 issue #118。
③ 已消费授权码重放留下泄密信号告警(整族撤销随 #118)。
含两个配置「有意为之」注释与 request_id 熵值(uuid4≈122bit)文档修正。
DCR 开放注册、客户端名可任意取(含仿冒官方),同意页原是整身官方皮肤——
加粗警示置于回调主机旁补齐知情权;日报缺失自动生成快照写入授权说明。
returnTo 指向后端路径(/authorize 经 next 代理)必须整页导航,客户端路由推不到。
@fxbin
fxbin merged commit 2b68239 into main Oct 17, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP server 支持动态 OAuth 授权(浏览器弹窗登录)

1 participant