MCP server 构建 II — Tasks 异步长任务 + OAuth 鉴权
MCP server 构建 II — Tasks 异步长任务 + OAuth 鉴权
日期: 2026-08-04 阶段: Phase 2 - AI-native 参考架构 标签: #mcp #tasks #oauth #rfc8707 #async-tools
核心问题
Day 50 把 search_aml_notes 封装成了 stateless MCP server,但它有两个没解决的缺口:
- 检索很快(几十毫秒),但「跑一个全数据集 SAR 草拟 + eval」要好几秒甚至更久。 stateless HTTP 请求一发一收,长任务怎么办?2026-07-28 RC 的答案是 Tasks 扩展——
tools/call返回一个 task handle,client 再用tasks/get/tasks/update/tasks/cancel轮询驱动。今天讲透 Tasks 的生命周期状态机。 - AML 数据是敏感的,谁都能调
search_aml_notes显然不行。 MCP 鉴权基于 OAuth 2.1 + RFC 8707 资源指示符——token 必须审计绑定到本 server,否则一个发给别的 server 的 token 不能拿来调我。今天讲鉴权流程和 scope 设计。
最后用「对照 D44(MCP client 集成)的 diff」校验一件事:client 侧发起的 tools/call 与 server 侧 Day 50 注册的工具,schema 和能力声明必须一致,否则集成时静默错位。
关键内容
A. Tasks 语义:长任务异步化与生命周期状态机
stateless 模型下,长任务不能靠「保持连接等结果」(连接可能命中任意实例、可能超时)。RC 的 Tasks 扩展重塑了调用语义(MCP Blog, 2026-07-28):
- task 创建是 server-directed:client 声明它支持 Tasks 扩展,由 server 决定某次
tools/call是否作为 task 跑(不是 client 强制)。快任务(检索)直接同步返回,慢任务(全量 eval)返回 task handle。 - client 拿到 handle 后用
tasks/get(查状态/结果)、tasks/update(推进,如补充输入)、tasks/cancel(取消)驱动。 tasks/list被删——没有会话就无法安全 scope「列出我的任务」(MCP Blog, 2026-07-28)。这意味着 task handle 本身就是能力凭证:谁持有 handle 谁能查,server 不维护「某用户的任务列表」。
反直觉洞察①:Tasks 不是「server 帮你管任务队列」,恰恰相反——server 端对 task 几乎无记忆,handle 是无状态世界里唯一的状态载体。 直觉会把 Tasks 理解成「服务端有个任务表,我能列出我的所有任务」。RC 故意删掉
tasks/list就是要打掉这个幻觉:在 stateless 下,能力必须随凭证(handle)走,不能随会话(已不存在)走。落地推论:handle 必须当成敏感凭证保护——它等价于「读取这个任务结果的权限」,泄露 handle ≈ 泄露结果。
Tasks 生命周期状态机:
client: tools/call (声明支持 Tasks 扩展)
│
▼
server 判定: 快任务?
┌───────┴────────┐
是 否
│ ▼
同步返回 返回 task handle ──┐
CallToolResult 状态: working │
│ │ client 轮询 tasks/get
┌───────────┼─────────────┘
▼ ▼ ▼
tasks/update tasks/get tasks/cancel
(补充输入) (查状态/结果) (取消)
│ │ │
└─────┬─────┘ ▼
▼ cancelled (终态)
┌──────────┴──────────┐
▼ ▼
completed (终态) failed (终态)
结果可经 tasks/get 取 带 error,不重试由 client 决策
映射到我们的工具:
| 工具 | 同步/Task | 理由 |
|---|---|---|
search_aml_notes | 同步 | RRF 检索几十 ms |
get_note | 同步 | 按 path 取全文,常数时间 |
run_full_eval | Task | 全数据集 assess+draft+codeChecks+judge,秒级 |
draft_sar_batch | Task | 批量 SAR 草拟 + judge 校准,可能更久 |
B. 鉴权:OAuth 2.1 + RFC 8707 资源指示符 + 增量 scope
MCP server 在鉴权语义里扮演 OAuth 2.1 资源服务器(modelcontextprotocol.io/authorization;dasroot.net, 2026-04)。三条 MUST:
- client MUST 实现 RFC 8707 资源指示符:在向授权服务器要 token 时,用
resource参数显式声明「这个 token 是给哪个 MCP server 用的」。授权服务器把它写进 token 的aud(audience)claim(WorkOS, RFC 8707 指南)。 - server MUST 校验 token 的 audience 就是自己(OAuth 2.1 §5.2 + RFC 8707 §2):拿到一个
aud指向别的 server 的 token,必须拒。这防的是「token 重放/转嫁」——攻击者把发给低敏感 server 的 token 拿来调高敏感 server。 - server MUST 实现 OAuth 2.0 Protected Resource Metadata(RFC 9728):暴露
/.well-known/oauth-protected-resource,告诉 client 去哪个授权服务器、要什么 scope。
2026 规范还引入增量 scope consent(incremental scope consent,nhimg.org 2026):client 每次只请求该操作所需的最小 scope,对齐最小权限原则——而不是一上来要一把「全权」token。
鉴权流程(首次调用 search_aml_notes):
1. client → server: tools/call (无 token)
2. server → client: 401 + WWW-Authenticate
指向 /.well-known/oauth-protected-resource (RFC 9728)
3. client 读 metadata → 找到授权服务器 + 所需 scope
4. client → 授权服务器: 授权请求
resource=https://aml.example/mcp ← RFC 8707
scope=aml:read ← 增量最小 scope
5. 授权服务器 → client: access_token (aud=https://aml.example/mcp)
6. client → server: tools/call + Authorization: Bearer <token>
7. server 校验: aud == 自己? scope 含 aml:read? iss 可信?
┌── 是 → 执行 hybridSearch
└── 否 → 403 (拒绝,记审计)
RC 还硬化了几点(MCP Blog, 2026-07-28):client MUST 按 RFC 9207 校验授权响应的 iss 参数;DCR 时声明 OpenID Connect application_type;凭证绑定到签发授权服务器的 issuer。
我们的 scope 设计(最小权限):
| scope | 授予工具 | 敏感度 |
|---|---|---|
aml:read | search_aml_notes, get_note | 中(知识检索) |
aml:eval | run_full_eval | 中(只读评测) |
aml:draft | draft_sar_batch | 高(生成 SAR,须 HITL 复核) |
反直觉洞察②:MCP 鉴权最危险的漏洞不是「没鉴权」,是「鉴权了但没校验 audience」。 很多 server 实现会校验「token 有效 + scope 够」就放行,却跳过
aud校验。后果:一个发给weather-mcp、scope 恰好也叫read的 token,能被攻击者拿来调aml-mcp。RFC 8707 的resource→aud闭环就是堵这个洞——audience 校验是 MCP 鉴权的承重墙,不是可选项。这也正是 Day 52 红队要重点打的面之一。
C. 对照 D44(MCP client 集成)核对一致性
D44 写的是 client 侧——AML Copilot 作为 MCP host 去调用外部 MCP server(如链上数据 server)。Day 50/51 写的是 server 侧——把自己的检索暴露给外部。两侧必须对齐,否则集成时静默错位。校验清单(做成 diff 测试):
| 一致性维度 | client 侧 (D44) | server 侧 (D50/51) | 校验方式 |
|---|---|---|---|
| 工具名/schema | client 发的 name+arguments | server tools/list 声明 | 断言 client 请求通过 server inputSchema 校验 |
| 能力声明 | client advertise「支持 Tasks 扩展」 | server 才会返回 task handle | 二者都声明才走异步路径 |
| Trace 透传 | client 写 _meta.traceparent | server 读它接 trace | 同一 trace id 贯穿 |
| 鉴权 | client 带 resource+aud token | server 校验 aud==自己 | 跨 server token 必被拒 |
诚实边界:D44 是本批次 P2 计划中由 client 集成笔记承载的内容;本篇按计划口径与其对齐,真实 diff 测试在两侧代码都落地后补。
设计要点/决策表
| 要点 | 决策 | 理由 |
|---|---|---|
| 长任务模型 | Tasks 扩展,server-directed | stateless 下不能靠保持连接等结果 |
| task handle 定位 | 当敏感凭证保护 | tasks/list 已删,handle 是唯一状态载体 |
| 哪些工具走 Task | run_full_eval/draft_sar_batch | 秒级+,检索仍同步 |
| 鉴权框架 | OAuth 2.1 + RFC 8707 + RFC 9728 | 对齐 MCP 鉴权规范 |
| audience 校验 | 强制,aud≠自己即拒 | 堵 token 转嫁(承重墙) |
| scope 粒度 | 按工具分 read/eval/draft,增量请求 | 最小权限 |
| 与 D44 一致性 | schema/能力/trace/鉴权四维 diff | 防 client-server 静默错位 |
对本项目的落地
- 扩展
src/agent/mcp/server.ts:为run_full_eval/draft_sar_batch实现 Task 路径——返回{ taskHandle, status:'working' },并实现handleRpc对tasks/get/tasks/update/tasks/cancel的分派;handle 用不可猜测的随机串(当敏感凭证)。run_full_eval复用src/aml/evalChecks.ts的codeCheckPassRate与src/aml/evalBaseline.ts。 - 新建
src/agent/mcp/auth.ts:导出verifyToken(token, expectedAudience, requiredScope)—— 校验aud==自己、scope充分、iss可信(RFC 9207);任一不满足返回结构化 403 并写审计。scope 表(aml:read/aml:eval/aml:draft)集中常量化。 - 教学 lab 增强:在 Day 50 的 MCP explorer 面板加「Tasks 时间线」视图(working→completed/failed/cancelled 状态流转)和「鉴权模拟」(故意发一个
aud错配的 token,演示被拒 + 审计记录)——把状态机和承重墙都可视化。 - diff 测试:新建
src/agent/mcp/__tests__/clientServerContract.test.ts,断言 D44 client 侧构造的tools/call请求能通过 D50 server 侧 inputSchema 校验,且能力声明/trace/鉴权四维一致。 - 诚实标注:
auth.ts头注写明「OAuth 授权服务器对接为真实部署阶段(需后端);本阶段实现 token 校验逻辑 + 教学模拟,不含真实 IdP 集成」。Task 的真实异步执行依赖常驻后端,静态阶段以同步执行 + 模拟 handle 演示状态机。
参考资料
- Model Context Protocol Blog — The 2026-07-28 MCP Specification Release Candidate:Tasks 扩展(server-directed、
tasks/get/tasks/update/tasks/cancel、删tasks/list)、鉴权硬化(RFC 9207 校验iss、OIDCapplication_type、凭证绑issuer)(2026-07) - modelcontextprotocol.io — Specification: Authorization:MCP server 作 OAuth 2.1 资源服务器,MUST 实现 RFC 8707 资源指示符、MUST 校验 token audience(OAuth 2.1 §5.2)、MUST 实现 RFC 9728 Protected Resource Metadata (2026)
- WorkOS — Resource Indicators in OAuth 2.0: A guide to RFC 8707:
resource请求参数→tokenaudclaim 闭环;server 校验 audience 防 token 转嫁 (2026) - dasroot.net — The New MCP Authorization Specification (OAuth 2.1 + Resource Indicators) / nhimg.org — Resource indicators tighten MCP token boundaries:增量 scope consent、最小权限 (2026-04)
- 本仓库
src/aml/evalChecks.ts(codeCheckPassRate)、src/aml/evalBaseline.ts、src/agent/trace/useTraceStore.ts;D44 MCP client 集成笔记(本批次 P2 计划)(2026-06)
SOTA 检查 (2026-06-11)
- Tasks 扩展与 OAuth 2.1/RFC 8707 鉴权是 2026-07-28 RC 的确定方向(RC 2026-05-21 锁定)。本篇写于 2026-08-04 计划时点;执行当周须复核最终规范对 Tasks 状态名(working/completed/failed/cancelled)和鉴权 MUST 项是否有措辞调整。
- audience 校验作为 MCP 鉴权承重墙在 2026 已是共识:RFC 8707
resource→aud闭环被 MCP 规范强制;未见替代方案。增量 scope consent 是 2026 新增的最小权限强化。 - 「task handle = 敏感凭证」是 stateless 化的直接推论,
tasks/list被删坐实了这一点;执行时须把 handle 的存储/传输按密级处理。 - 过时认知警示:2025 上半年「MCP 用 API key / 自定义 header 鉴权」的做法已被 OAuth 2.1 资源服务器模型取代;任何不校验
aud的实现都是已知漏洞面(见 Day 52)。 - 待跟踪:最终规范发布后回填 Tasks 是否支持进度百分比/部分结果流式;以及 OAuth 增量 consent 在真实 IdP(如 Auth0/WorkOS)上的落地成本,待接后端时评估。