FastMCP v4 后台任务重构:基于 SEP-2663 的 fastmcp-tasks 扩展设计全解
2026/9/11 14:12:12 网站建设 项目流程

FastMCP v4 后台任务重构:基于 SEP-2663 的 fastmcp-tasks 扩展设计全解

【免费下载链接】fastmcp🚀 The fast, Pythonic way to build MCP servers and clients.项目地址: https://gitcode.com/GitHub_Trending/fa/fastmcp

本篇技术指南基于 FastMCP 仓库中已定型并落地(Shipped,#4602、#4603)的 v4 设计文档 dev-docs/v4-notes/background-tasks.md 编写,围绕"后台任务从 SEP-1686 迁移到 SEP-2663 扩展"这条主线展开。文章会说明任务扩展的协议线形状、fastmcp-tasks可选包的打包与激活模型、FastMCP 原生服务端扩展 API(mcp.add_extension)、客户端三层调用体验,以及设计中的范围边界、风险与已定案决策;同时结合仓库内fastmcp_tasks包的源码实现与fastmcp核心中的任务声明原语,给出可复现的配置与代码依据。读完你可以掌握:如何用task=True+TasksExtension在 FastMCP v4 上运行 MCP 后台任务,理解其轮询式协议与 Docket 执行引擎的边界,以及这套扩展 API 为何能同时成为未来 MCP Apps 集成的统一入口。

TL;DR:任务机制保留,协议底座换为 SEP-2663

FastMCP 的后台任务能力不会消失。MCP 规范把任务协议从核心中移出,并合并为一个Final 状态的扩展——io.modelcontextprotocol/tasks(SEP-2663),它保留了 FastMCP 已经实现的轮询模型。截至当前仓库状态,没有任何语言的 SDK 为它提供运行时实现,而 FastMCP 拥有唯一接近生产形态的执行引擎(Docket/Redis),且其协议形态与 SEP-2663 高度一致。

因此 v4 的决策是:基于 SEP-2663 重建任务支持,做成仓库内的可选包fastmcp-tasks,用task=True门控激活,与 MCP Apps 用app=True门控的模式完全对齐。迁移过程中移除 SEP-1686 的线层(wire layer),保留并重新安置执行引擎;同时引入FastMCP 原生的服务端扩展 API,让任务(以及后续的 Apps)通过一个文档化的统一机制接入,而不是继续对核心做特制改动。

净效果是:已经用@mcp.tool(task=True)的服务端代码无需任何改动即可平滑过渡,且 FastMCP 很可能成为 tasks 扩展在生态中的第一个运行时实现。

背景:任务的现状(SEP-1686 时代)

FastMCP 3 曾基于SEP-1686(短暂存在于 MCP 核心规范中的任务协议)实现后台任务。该实现横跨 server、client、CLI 和一个 SDK shim,约 4,000 行代码,可拆成两个性质完全不同的部分(见 fastmcp_tasks 包结构):

  • 线层(wire layer):能力通告、tasks/get|result|list|cancel四个处理器、在增强版tools/call上返回的CreateTaskResult,以及一个基于 Redis 的推送中继,让 worker 能触达客户端以投递通知和 elicitation(追问)请求。
  • 执行引擎(execution engine):基于 Docket(队列、worker、结果存储、TTL,支持memory://redis://后端),外加 FastMCP 自己构建的持久化层:按 auth 作用域复合键隔离任务访问、跨 worker 进程的请求上下文快照/恢复、与同步路径一致的参数强转,以及fastmcp tasks workerCLI。

SDK v2 迁移时把 SEP-1686 从核心规范中移除,v4 设计笔记一度记录"删除任务机制,需要任务的用户停留在 FastMCP 3"。当时的判断在信息不完整的前提下是正确的——假设后继协议要么不存在、要么不可实现。而这两个假设后来都被证伪。

上游变化:SEP-2663 做了什么

任务协议是被重构而非删除。SEP-2663("Tasks Extension")已是 Final 状态,于 2026-05-15 在上游合并,取代 SEP-1686。它定义io.modelcontextprotocol/tasks扩展,是一个基于 SEP-2133 扩展机制的能力协商特性,保留了 SEP-1686 的轮询核心并加以收紧。

线形状(wire shape)

  1. 客户端通告任务能力(在每次请求的_meta中)。这是"同意"——"我能处理任务结果"——而不是发起任务的请求。
  2. 客户端发出普通tools/call由服务端决定是否以任务方式运行。
  3. 若被任务化,服务端返回CreateTaskResult(一个携带resultType: "task"的 claimed 结果形状),其中的taskId服务端生成
  4. 客户端轮询tasks/get直到状态进入终态;结果内联在该响应中返回。
  5. 任务执行过程中的输入(elicit/sample/roots)采用轮询式:状态翻转为input_required,未决请求出现在inputRequests映射中,客户端通过tasks/update应答。
  6. tasks/cancel是协作式的。推送是可选能力(notifications/taskssubscriptions/listen),服务端可以不发。

与 SEP-1686 的差异

设计文档用一张对照表总结了差异,最值得注意的是其中大部分是删除——因为规范在向 FastMCP 已经构建的方向靠拢:

维度SEP-1686(旧)SEP-2663(新)FastMCP 现状
任务 id 生成客户端生成服务端生成已是服务端生成
tasks/list存在移除(枚举风险)已是返回[]的桩
结果获取独立的tasks/result内联tasks/get合并两个处理器即可
tasks/delete存在移除(依赖 TTL)TTL 是 Docket 原生能力
创建竞态notifications/tasks/created必须持久化创建差一个读己之写(read-your-writes)检查
任务内输入推送中继 +_meta标记轮询input_required+tasks/update替换掉最棘手的模块
状态集合7 个(含submittedunknown5 个收缩映射表
可增强请求任意tools/call仅工具面(见范围)
LB 路由未规定Mcp-Name: <taskId>共享 Redis 下无关紧要

关键点:目前没有任何运行时实现。ext-tasks仓库只有 schema 和文字规范;TypeScript 与 Python SDK 只携带线类型和一致性测试夹具,没有客户端/服务端实现。这个领域是开放的。

决策:构建它

两个事实推翻了之前"删除并等待"的判断:

  1. 规范正是 FastMCP 已实现的形态,只是去掉了一个可以丢弃的推送中继。重建主要由删除和一个薄薄的新线适配器组成,而非从零开始。
  2. FastMCP 位置独特。SEP-2663 的隐含前提是:持久化的服务端存储、服务端生成的高熵 id、容忍最终一致性的创建流程、多节点路由——这恰是 Docket/Redis 提供的。没有其他框架内置了这套能力。

在迁移期间继续维护 SEP-1686 机制是死重(它是_sdk_patches.pyshim、TaskNotificationHandler以及一批协议时代 xfail 的唯一原因)。基于 SEP-2663 重建既能清除这笔技术债,又能产出一个"零代码改动迁移"的旗舰 v4 能力。

架构:引擎与线的拆分

引擎/线分层

现有代码已经沿着这条线清晰分离,重建只是把边界变成包边界:

  • 移除:SEP-1686 线层——能力通告、四个 CRUD 处理器,以及最大的收获:整个 Redis 推送中继(server/tasks/elicitation.pynotifications.py)。它存在只是因为 SEP-1686 没有基于轮询的任务内输入通道;SEP-2663 的input_required/tasks/update取代了它。请求/响应存储保留,推送信封(push envelope)不再需要。
  • 保留并重新安置:Docket 执行引擎、auth 作用域键编码(这是tasks/get/update/cancel授权层——比规范中"taskIds 可以当作 bearer token"更强)、上下文快照/恢复、参数强转、worker CLI。这些全部与线协议无关。
  • 新增:一个薄的 SEP-2663 线适配器——能力、tasks/get/update/cancel方法,以及一个决定是否任务化并执行的tools/call拦截器。

在源码中可以看到这条边界的落地:核心的 fastmcp_slim/fastmcp/utilities/tasks.py 只保留纯声明——TaskModeTASKS_EXTENSION_ID(反向 DNS 标识io.modelcontextprotocol/tasks)、TaskConfigTaskMeta——而 Docket 相关的依赖注入与"安装提示"全部迁入 fastmcp_tasks/fastmcp_tasks/dependencies.py(模块 docstring 明确写着"在 SEP-1686 → SEP-2663 迁移中从 fastmcp.server.dependencies 移出")。

打包与激活模型

fastmcp-tasks是仓库内uvworkspace 成员,模板沿用fastmcp_remote(独立pyproject.toml、与主版本锁步、通过fastmcp元包再导出)。与 MCP Apps 的开发者体验完全平行:

关注点MCP Apps后台任务
作者标记(核心)@mcp.tool(app=True)@mcp.tool(task=True)
可选包prefab-uifastmcp-tasks
额外依赖 extrafastmcp[apps]fastmcp[tasks]
缺包行为响亮的安装提示服务端构建时响亮的安装提示

核心只保留声明:task=True/TaskConfig是组件上的元数据,不导入任何引擎。引擎和线适配器都住在fastmcp-tasks包里。现有[tasks]extra 从 SEP-1686 机制改指向fastmcp-tasks,因此pip install fastmcp[tasks]task=True都会在现代化线协议之下继续工作。

激活保持隐式但响亮(沿用现有require_docket()模式,绝不静默降级):任何task=True都会在构建期触发对fastmcp-tasks的惰性导入,缺装立即抛错。作者标记为任务、却在线内静默执行的工具是一个正确性 bug,而不是优雅回退。require_docket的实现可见 fastmcp_tasks/fastmcp_tasks/dependencies.py#L45-L72:缺装时提示pip install 'fastmcp[tasks]',装了旧版pydocket时提示升级版本。

扩展 API:mcp.add_extension

MCP 扩展(SEP-2133)是 SDK v2 中真正的新抽象——v1 时代并不存在。MCP Apps 当时手工拼接集成并非错误选择,而是先于该工具诞生。如今 FastMCP 的服务端完全绕过 SDK 的Extension类(把ui能力手工拼接进低级服务端并直接遍历工具元数据),而客户端原生转发ClientExtension。每新增一个协议扩展都意味着对核心的特制手术。

Tasks 成为推动修复此问题的契机,设计引入单一注册点:

from fastmcp import FastMCP from fastmcp_tasks import TasksExtension mcp = FastMCP("Server") mcp.add_extension(TasksExtension(url="redis://...")) # 启用任务所必需 @mcp.tool(task=True) # 意图:这个工具可以以任务方式运行 async def crunch(dataset: str) -> str: ...

add_extensiontask=True生效是必需的——不会因为存在task=True标记就被自动检测。这是有意为之:

  • 扩展需要配置(后端 URL、worker 并发、TTL 默认值),add_extension(TasksExtension(...))是其天然归宿;自动检测只会把这些配置打散到 settings/env 中,并隐藏启用时刻。
  • 要求显式注册能让能力通告保持诚实——服务端仅在扩展已注册时才通告tasks能力
  • 它移除了最危险的 footgun:因为没人配置 Redis,工具在生产环境静默运行在内存后端上。

两个关注点保持清晰分离:task=True组件级意图("这个工具可以是任务");add_extension服务端级启用与配置("这个服务端运行任务,方式如下")。用了task=True却没有注册扩展,会在构建期报出响亮的错误。

在源码中,ServerExtension基类(fastmcp_slim/fastmcp/server/extensions.py)定义了四类可贡献物:

  • 协商能力settings()拼接到ServerCapabilities.extensions[identifier]
  • 新增请求方法methods()返回MethodBinding,注册时通过add_request_handler挂到低级服务端;
  • tools/call拦截器intercept_tool_call()是工具体运行前的最后一道门——它组合在 FastMCP 中间件链之后、组件执行之前,可以观察、短路或放行调用;
  • 生命周期lifespan()随服务端生命周期进入/退出——这是 SDK 的Extension所缺失的钩子,用于启动后端/worker。

基类遵循 SDK 的 httpx 式形状:每个贡献方法都有默认实现,子类只需覆写所需部分。MethodBinding还有防呆约束:不能绑定规范已有的请求方法(tools/callcompletion/complete等),否则会静默遮蔽服务端自己的处理器,见 extensions.py#L97-L108。

扩展 vs 中间件:判别准则

为避免过度应用该抽象,设计文档给出明确判别器:扩展是客户端必须理解的协商性协议变更;中间件是客户端永远看不见的单边服务端行为。PII 检测、鉴权、限流走中间件;Tasks、Apps 走扩展。一个试金石:删掉能力通告——如果客户端的任何行为没有改变,那它就是中间件。

客户端体验

SEP-2663 移除了客户端"把这次调用做成任务"的标记——由服务端决定。这恰好映射到 FastMCP 现有的两层客户端表面:友好的call_tool与低层的call_tool_mcp,因此几乎没有新增 API:

  • call_tool(name, args)(友好层):通告能力;若服务端把调用任务化,则透明驱动轮询循环并返回完成后的结果。调用方完全察觉不到是否被任务化。任务内input_required会路由到客户端已有的 elicitation handler,经tasks/update应答——因此后台 elicitation 与前台 elicitation 看起来完全一样,零新增客户端 API。
  • call_tool_mcp(...)(低层):把原始CreateTaskResultclaimed 形状交还给自行管理任务的调用方。
  • 友好接口上的"快速返回"标志:立即得到Task句柄(.status().wait().cancel(),可 await),不阻塞——这是进度与取消的逃生舱。

客户端侧的实现在 fastmcp_tasks/fastmcp_tasks/client.py:TasksClientExtension自动注册到每个 FastMCPClient上(调用方无需任何 opt-in),其 claim resolver 在底层把tasks/get轮询到完成,再以CallToolResult返回真实结果;ToolTask则是显式句柄。模块 docstring 还注明:任务只存在于现代协议(modern protocol)上——在 legacy 连接上 SDK 会剥离能力通告,服务端永不任务化,该扩展处于惰性(inert)状态。

服务端侧,TaskConfig三种模式直接翻译(对应 fastmcp_slim/fastmcp/utilities/tasks.py#L42-L62 中的TaskMode):

  • required→ 总是任务化(对未声明的客户端返回-32021,即MISSING_REQUIRED_CLIENT_CAPABILITY);
  • optional→ 客户端声明了才任务化;
  • forbidden→ 永不任务化。

TasksExtension.intercept_tool_call的判定逻辑见 fastmcp_tasks/fastmcp_tasks/extension.py#L213-L271:它还会解析请求_meta中的版本规范,避免对指向旧版本的tools/call错误地任务化最高版本的工具;同时只在现代 era(2026-07-28 起)才认可客户端 opt-in。

实操:安装、配置与运行

安装

作为 FastMCP 的tasksextra 安装(见 fastmcp_tasks/README.md):

uv pip install "fastmcp[tasks]"

最小服务端

from fastmcp import FastMCP from fastmcp_tasks import TasksExtension mcp = FastMCP("Analytics") mcp.add_extension(TasksExtension(url="redis://localhost:6379/0")) @mcp.tool(task=True) async def analyze(dataset: str) -> str: # 长时间运行的工作。客户端立即拿到任务句柄并轮询结果; # 这段代码在后台 worker 中执行。 ...

task=True是意图声明——该工具可能以任务方式运行——而按规范,服务端在每次调用时决定是否真的任务化。需要更精细控制时使用TaskConfig

from fastmcp.utilities.tasks import TaskConfig @mcp.tool(task=TaskConfig(mode="required")) async def must_run_async(n: int) -> int: # 总是以任务方式运行;未 opt-in 的客户端会被明确告知。 ...

注意:注册TasksExtension是服务task=True工具的前提——工具声明意图,扩展提供引擎。服务端注册了task=True工具却没有任务扩展,会在启动时响亮失败,而不是静默在线内运行。

配置项与默认值

后端在扩展上配置。每个选项都有对应的FASTMCP_DOCKET_*环境变量,因此纯环境变量配置的部署可以无参数构造TasksExtension()(fastmcp_tasks/fastmcp_tasks/settings.py 中DocketSettings完整定义了这些字段):

选项环境变量默认值说明
urlFASTMCP_DOCKET_URLmemory://后端 URL。memory://用于单进程;redis://host:port/db用于分布式 worker。
nameFASTMCP_DOCKET_NAMEfastmcp队列名。同名同 URL 的服务端与 worker 共享同一任务队列。
worker_nameFASTMCP_DOCKET_WORKER_NAMENone(Docket 自动生成)worker 名称。
concurrencyFASTMCP_DOCKET_CONCURRENCY10每个 worker 的最大并发任务数。
redelivery_timeoutFASTMCP_DOCKET_REDELIVERY_TIMEOUT300s任务重投递超时:worker 未在期限内完成任务,任务会被重投递给其他 worker。
reconnection_delayFASTMCP_DOCKET_RECONNECTION_DELAY5sworker 失去与后端连接后的重连间隔。
minimum_check_intervalFASTMCP_DOCKET_MINIMUM_CHECK_INTERVAL50msworker 轮询新任务的频率。调低降低任务拾取延迟但增加 CPU;高吞吐生产环境建议调高。

此外TasksExtension构造器还接受urlnameworker_nameconcurrencyredelivery_timeoutreconnection_delayminimum_check_interval共 7 个关键字参数(extension.py#L86-L108),任何未传参数都会回落到环境变量默认值。

两组额外的设置类值得了解:

  • TasksSettings(前缀FASTMCP_TASKS_):encryption_key,用于对任务上下文快照静态加密。快照携带提交者的访问令牌与 HTTP 头,写入 Docket 后端并存活到任务 TTL;共享同一任务队列的服务端与 worker 必须配置相同密钥,worker 无法解密的快照会让任务失败而不是以匿名身份运行。未设置时快照以明文 JSON 存储。密钥经 PBKDF2 派生 Fernet key,任意非空字符串可用,但建议至少 32 个随机字符。
  • TasksClientSettings(前缀FASTMCP_TASKS_CLIENT_):poll_interval(默认0.5秒),是客户端等待后台任务时回退轮询的上限;仅当服务端未通告自己的pollIntervalMs时生效——此时客户端从约20ms起步并倍增到该上限,快速任务即时解决、长任务不会锤爆服务端。服务端通告了pollIntervalMs时精确遵从该值并忽略此设置。

运行独立 worker(分布式)

基于 Redis 的分布式部署中,让专用 worker 进程与服务端并存:

python -m fastmcp_tasks.worker_cli worker server.py

共享同一后端 URL 与队列名的 worker 与服务端共享任务队列,因此执行能力可以独立于请求服务的前端节点横向扩展。worker CLI 实现见 fastmcp_tasks/fastmcp_tasks/worker_cli.py。

实施顺序(Sequencing)

  1. 先设计与单测扩展 API:针对 tasks 的全表面(能力、方法、拦截、客户端 claims/通知)进行设计与测试——作为独立可测层,在任务逻辑落地之前先用一个琐碎的内测扩展验证隔离性。
  2. 构建fastmcp-tasks:从被移除的 SEP-1686 层中抽取引擎,编写 SEP-2663 适配器,移植客户端半边。
  3. 把 MCP Apps 迁移到扩展 API 上:快速跟进,不在关键路径上,用 Apps 现有的绿色测试作为回归网。

Tasks 先行,因为只有它能触达扩展 API 的全表面;先用 Apps 的子集设计会把自己逼进死角。Apps 成为第二个消费者,用于确认设计具有通用性。

v1 范围(非目标)

  • 仅轮询。可选的notifications/tasks推送与subscriptions/listen集成推迟到后续fastmcp-tasks版本,让第二条 Redis 通知队列就此消亡而非移植。
  • tools/call,不领先规范。SEP-2663 只增强tools/call。FastMCP 3 曾在 SEP-1686 下对 prompts 和 resources 提供task=True,先于 SDK——那是个错误:它产生了线协议无法表达的能力、一批永久 xfail 和 sdk-feedback #3 的差距。本次重建不会重蹈覆辙task=是仅限工具的表面,通用 prompt/resource 任务脊梁被丢弃而非携带。若规范将来扩展增强类型,表面随之增长。
  • 以 experimental 状态发布ext-tasksschema 标记为 experimental 且无发布版本;fastmcp-tasks初期同样标注 experimental,schema 演进时按自身节奏发版。

风险与缓解

风险缓解
规范变动(扩展仍为 experimental)在无线相关的引擎上放一个薄线适配器;以 experimental 发布;SEP 本身是 Final,即使字段名变动,轮询模型也是稳定的。
Era 门控——SDK 在 2026 之前的协商版本剥离capabilities.extensions(sdk-feedback #2)能力通告实质上要求 2026-07-28 era;FastMCP 3 覆盖 legacy 任务。#2 现在门控一个旗舰能力 → 上报上游。
同时开发新抽象 + 全新功能先隔离构建并单测扩展 API(步骤 1),再在其上落地任务逻辑。
命名混淆——[tasks]extra 在同名下改指向有意的 changelog 说明;用户代码与 extra 名均不变,只有线协议现代化。

设计决策(已定案)

以下是曾经的开放分叉,维护者已拍板定案,记录于此以保证实施方向无歧义:

  1. 线适配器位置——在fastmcp-tasks包内。引擎与 SEP-2663 线适配器都住在包中,核心只携带task=True声明。这把 experimental schema 的变动与核心隔离,代价是与 Apps 先例分叉(Apps 的ui线胶水今天仍在核心中——Apps 迁移到扩展 API 时会向该模型收敛)。
  2. 扩展 API 形态——FastMCP 原生mcp.add_extension(),启用 tasks 为必需项。选它而非对 SDK 的MCPServer(extensions=...)做薄透传,是因为 FastMCP 原生 API 能把 SDKExtension不提供的Context、组件注册表、auth 作用域交给扩展。add_extensiontask=True必需的(不做自动检测)——它是后端配置的唯一归宿,也是能力通告的诚实来源。
  3. 客户端默认——友好接口透明完成call_tool驱动轮询循环并返回完成结果;call_tool_mcp暴露原始CreateTaskResult;"快速返回"标志给出Task句柄。
  4. experimental 标注——要fastmcp-tasks至少在一个小版本周期内标注 experimental,跟随 experimental 的ext-tasksschema。
  5. 资源/提示词脊梁——丢弃,仅限工具。本次重建不领先 SDK 的可增强请求类型,纠正 SEP-1686 时代的错误。

延伸阅读

  • 设计文档:dev-docs/v4-notes/background-tasks.md
  • 任务特性一览(feature program):dev-docs/v4-notes/feature-program.md
  • fastmcp-tasks包实现:fastmcp_tasks/(README 见 fastmcp_tasks/README.md)
  • 核心任务声明原语:fastmcp_slim/fastmcp/utilities/tasks.py
  • 服务端扩展 API 基类:fastmcp_slim/fastmcp/server/extensions.py
  • 任务扩展适配器:fastmcp_tasks/fastmcp_tasks/extension.py
  • 后端/worker 配置:fastmcp_tasks/fastmcp_tasks/settings.py
  • 客户端任务驱动:fastmcp_tasks/fastmcp_tasks/client.py
  • 相关测试:tests/tasks/、tests/server/test_dependencies.py

【免费下载链接】fastmcp🚀 The fast, Pythonic way to build MCP servers and clients.项目地址: https://gitcode.com/GitHub_Trending/fa/fastmcp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询