返回 AIPA 笔记
AIPA Day 51

MCP server 构建 II — Tasks 异步长任务 + OAuth 鉴权

MCP server 构建 II — Tasks 异步长任务 + OAuth 鉴权

2026-08-04
mcptasksoauthrfc8707async-tools

日期: 2026-08-04 阶段: Phase 2 - AI-native 参考架构 标签: #mcp #tasks #oauth #rfc8707 #async-tools

核心问题

Day 50 把 search_aml_notes 封装成了 stateless MCP server,但它有两个没解决的缺口:

  1. 检索很快(几十毫秒),但「跑一个全数据集 SAR 草拟 + eval」要好几秒甚至更久。 stateless HTTP 请求一发一收,长任务怎么办?2026-07-28 RC 的答案是 Tasks 扩展——tools/call 返回一个 task handle,client 再用 tasks/get/tasks/update/tasks/cancel 轮询驱动。今天讲透 Tasks 的生命周期状态机。
  2. 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_evalTask全数据集 assess+draft+codeChecks+judge,秒级
draft_sar_batchTask批量 SAR 草拟 + judge 校准,可能更久

B. 鉴权:OAuth 2.1 + RFC 8707 资源指示符 + 增量 scope

MCP server 在鉴权语义里扮演 OAuth 2.1 资源服务器(modelcontextprotocol.io/authorization;dasroot.net, 2026-04)。三条 MUST:

  1. client MUST 实现 RFC 8707 资源指示符:在向授权服务器要 token 时,用 resource 参数显式声明「这个 token 是给哪个 MCP server 用的」。授权服务器把它写进 token 的 aud(audience)claim(WorkOS, RFC 8707 指南)。
  2. server MUST 校验 token 的 audience 就是自己(OAuth 2.1 §5.2 + RFC 8707 §2):拿到一个 aud 指向别的 server 的 token,必须拒。这防的是「token 重放/转嫁」——攻击者把发给低敏感 server 的 token 拿来调高敏感 server。
  3. 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:readsearch_aml_notes, get_note中(知识检索)
aml:evalrun_full_eval中(只读评测)
aml:draftdraft_sar_batch(生成 SAR,须 HITL 复核)

反直觉洞察②:MCP 鉴权最危险的漏洞不是「没鉴权」,是「鉴权了但没校验 audience」。 很多 server 实现会校验「token 有效 + scope 够」就放行,却跳过 aud 校验。后果:一个发给 weather-mcp、scope 恰好也叫 read 的 token,能被攻击者拿来调 aml-mcp。RFC 8707 的 resourceaud 闭环就是堵这个洞——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)校验方式
工具名/schemaclient 发的 name+argumentsserver tools/list 声明断言 client 请求通过 server inputSchema 校验
能力声明client advertise「支持 Tasks 扩展」server 才会返回 task handle二者都声明才走异步路径
Trace 透传client 写 _meta.traceparentserver 读它接 trace同一 trace id 贯穿
鉴权client 带 resource+aud tokenserver 校验 aud==自己跨 server token 必被拒

诚实边界:D44 是本批次 P2 计划中由 client 集成笔记承载的内容;本篇按计划口径与其对齐,真实 diff 测试在两侧代码都落地后补。

设计要点/决策表

要点决策理由
长任务模型Tasks 扩展,server-directedstateless 下不能靠保持连接等结果
task handle 定位当敏感凭证保护tasks/list 已删,handle 是唯一状态载体
哪些工具走 Taskrun_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' },并实现 handleRpctasks/get/tasks/update/tasks/cancel 的分派;handle 用不可猜测的随机串(当敏感凭证)。run_full_eval 复用 src/aml/evalChecks.tscodeCheckPassRatesrc/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 演示状态机。

参考资料

  1. 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、OIDC application_type、凭证绑 issuer)(2026-07)
  2. 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)
  3. WorkOS — Resource Indicators in OAuth 2.0: A guide to RFC 8707resource 请求参数→token aud claim 闭环;server 校验 audience 防 token 转嫁 (2026)
  4. dasroot.net — The New MCP Authorization Specification (OAuth 2.1 + Resource Indicators) / nhimg.org — Resource indicators tighten MCP token boundaries:增量 scope consent、最小权限 (2026-04)
  5. 本仓库 src/aml/evalChecks.tscodeCheckPassRate)、src/aml/evalBaseline.tssrc/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 resourceaud 闭环被 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)上的落地成本,待接后端时评估。