FastMCP v4 特性计划(Feature Program)全解析:采样移除、MRTR 启发式交互与 2026 协议时代的演进路线

FastMCP v4 特性计划(Feature Program)全解析:采样移除、MRTR 启发式交互与 2026 协议时代的演进路线

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

导读

本文基于 FastMCP 仓库 v4 开发笔记中的 feature-program.md,系统梳理 FastMCP v4.0 在 MCP Python SDK v2 迁移之后的前向特性计划(Feature Program):包括采样(Sampling)与 roots 的服务器端移除、基于多轮往返(MRTR)的启发式交互(Elicitation)、首个一等公民的 2026 客户端、FastMCP 原生扩展 API、SEP-2663 后台任务重建,以及第二轮 SDK 委托收敛。读完本文,你将掌握 v4 每项特性的状态判定体系(Shipped / Designed / Planned / Not started)、每项特性与 change-register.md、background-tasks.md、known-gaps.md 的对应关系,并能据仓库源码核验每项结论的落地位置。

背景:v4 的三股驱动力与特性状态体系

FastMCP v4.0 本质上是引擎换底(engine swap)。根据 index.md 的说明,三股力量推动了大版本号:

  1. MCP Python SDK v2 重建:协议类型从mcp.types拆分为独立的mcp_types包,所有协议字段从 camelCase 改为 snake_case(如inputSchema→input_schema、mimeType→mime_type、isError→is_error)。服务端请求处理模型重写:handler 按方法字符串注册并返回裸结果模型,不再有request_ctxContextVar,服务端中间件成为 SDK 的一等概念。
  2. 协议版本 2026-07-28:SDK v2 单服务器可同时服务多个协议时代。在有会话的握手(handshake)时代之外,引入了无会话(sessionless)的2026-07-28时代——通过server/discover发现能力,并移除了服务端发起的请求(SEP-2577)。
  3. 采样与 roots 从服务器 API 中移除:2026-07-28时代移除了服务端在调用中途向客户端推送请求的能力,这使ctx.sample、ctx.sample_step、ctx.list_roots无法工作。v4.0 选择将它们从服务器 API 中彻底移除,而不是让它们只对旧客户端半工作。

迁移是基础,而本篇文章的主角——前向 v4 特性计划——是一系列基于迁移合并后的 PR 序列。每项特性都携带明确的状态标记:

状态含义
Shipped已合并到main,并附有对应 PR
Designed方案已定,存在 API 草图,尚未开始实现
Planned形态已达成一致,但设计细节仍待敲定
Not started被识别为 v4 范围,但尚未设计

文中标注为 "sketch" 的代码块展示的是预期 API,在当前代码树中尚无法解析。这一点在阅读下文代码示例时需特别注意。

采样移除(Sampling removal)——Shipped in 4.0

为什么采样必须死

采样是"推送形状"(push-shaped)的 API:服务端在调用中途借用客户端的模型(ctx.sample、ctx.sample_step)。2026-07-28时代移除了服务端发起的请求,因此它无法在现代连接上工作;而Client切换到mode="auto"默认值后,现代连接成了默认体验而非边缘情况——时代门控(era gate)从边缘情况变成了默认体验。另外,后台任务中的采样在 v2 下早已名存实亡:worker 的后向通道(back-channel)在提交请求返回后即消失,而从未构建过中继(sdk-feedback #9)。

移除的完整清单

弃用与时代门控在 #4448 中落地,而移除完成了整个计划。以下内容全部消失:

  • ctx.sample、ctx.sample_step、ctx.list_roots
  • server/sampling/包(含SamplingTool与结构化结果采样)
  • FastMCP(sampling_handler=..., sampling_handler_behavior=...)
  • examples/sampling/

从源码看,这一移除是硬移除。在 server/context.py 中已无sample/sample_step/list_roots方法;server/server.py 的_REMOVED_KWARGS映射表会对FastMCP()传入的已移除 kwargs 抛出点名 SEP-2577 与迁移方向的TypeError。调用已移除的 Context 方法则直接触发AttributeError。仓库 change-register.md 将该变更标记为Breaking,并给出验证位置:tests/server/test_protocol_eras.py::test_removed_server_initiated_methods_are_absent。

迁移故事:没有 drop-in,只有架构建议

文档明确承认:不存在一键替换(no drop-in)。指引是架构层面的——

  • 采样:从你的服务器直接调用 LLM,使用你自己的 API key,而不是借用客户端的模型。
  • roots:把路径作为工具参数传入,或通过 guard 模式(其input_requests映射仍携带ListRootsRequest)向客户端询问。

什么被刻意保留

客户端侧并不受影响:

  • 客户端侧 provider handlers(Anthropic、OpenAI、Google GenAI)以及Client(sampling_handler=..., roots=...)保留——因为 FastMCP 客户端仍必须应答旧服务器(legacy server)的请求,且 MRTR 需要客户端侧的这些 handlers。被移除的只是服务端推送发射器。
  • ProxyClient的默认中继 handlers 因同样的互操作原因保留,但现在直接调用 SDK 会话。

这一"服务端移除、客户端保留"的边界在 client/client.py 中可见——sampling_handler仍是Client构造参数之一,服务于"应答旧服务器"的互操作场景。

MRTR 启发式交互(MRTR elicitation)——Guard 形式已发布,声明式Resolve层已设计

启发式交互如何在现代时代存活

启发式交互通过多轮往返(multi-round-trip,MRTR)在现代时代存活。2026 线路信封(wire envelope)将启发式交互编码为多轮输入请求:工具返回InputRequiredResult并每轮重新执行,每一轮都是完整的请求→响应周期。命令式ctx.elicit依赖会话后向通道,而该通道在2026-07-28前台调用上已不存在;在现代时代,启发式交互只能通过 MRTR 触达。

Guard 形式(已发布)

4.0 中已发布的 guard 形式为:工具返回InputRequiredResult,并从ctx.input_responses/ctx.request_state读取客户端的回答,每轮重新运行。它精确镜像了 SDK 的基础 guard 模型——没有 FastMCP 自创的 DX;框架负责request_state的密封;在握手时代连接上返回该结果会产生清晰的时代错误。

从源码核验:

  • server/mixins/mcp_operations.py 的_on_call_tool将InputRequiredToolResult(定义于 tools/base.py)包装后通过 runner 以resultType: "input_required"到达线路;
  • server/context.py 提供input_responses/request_state属性;
  • 框架在低层服务器上安装 SDK 的RequestStateBoundary中间件,密封每个外发request_state,并在工具运行前解封与验证每个入站回声——工具只看到明文,篡改、过期或外部 token 被以冻结的线路错误拒绝;
  • server/server.py 的FastMCP(request_state_security=RequestStateSecurity(keys=[...]))为多副本部署提供共享密钥;省略时每个进程在临时密钥下密封(单进程场景正确)。

尚未构建的声明式Resolve(...)层

剩余工作是位于上述已发布原语之上的声明式Resolve(...)层,当前状态为Designed(方案已定、有 API 草图、未实现):一个新的fastmcp.elicitation模块——Resolve、Elicit、ElicitationResult——作为 SDK resolver 的薄封装,接入 FastMCP 自己的工具层(FastMCP 工具不继承 SDK 的自动 resolver 接线)。它将检测Annotated[_, Resolve(...)]参数、构建 resolver 计划,并在第一轮返回 SDK 的InputRequiredResult而非工具体。

以下为预期 API 草图(test="skip"标记表明该模块当前尚不存在,不可解析):

from typing import Annotated from pydantic import BaseModel from fastmcp import FastMCP, Context from fastmcp.elicitation import Resolve, Elicit, ElicitationResult mcp = FastMCP("shipping") class Address(BaseModel): street: str city: str zip: str async def ask_address(ctx: Context) -> Elicit[Address]: return Elicit("Where should we ship this order?", Address) @mcp.tool async def create_shipment( order_id: str, address: Annotated[Address, Resolve(ask_address)], # unwrapped; decline -> ToolError ) -> str: return f"Shipping {order_id} to {address.city}" @mcp.tool async def maybe_ship( order_id: str, address: Annotated[ElicitationResult[Address], Resolve(ask_address)], # full outcome ) -> str: if address.action != "accept": return "cancelled" return f"Shipping {order_id} to {address.data.city}"

注意两种声明式的差异:create_shipment中Annotated[Address, Resolve(...)]在用户拒绝时抛ToolError;maybe_ship中Annotated[ElicitationResult[Address], Resolve(...)]携带完整结果(action+data),使工具可以优雅处理 "cancelled" 分支。

命令式ctx.elicit的去向

命令式ctx.elicit不会被重新接回以在现代时代存活:

  • 在旧时代(legacy eras)通过会话后向通道工作;
  • 在2026-07-28前台调用上被时代门控为抛出清晰错误(在 #4448 中发布),指引用户改用 guard 形式。
  • 早期通过后台任务中继让命令式ctx.elicit在现代连接上存活的计划"双重死亡":guard 模型取而代之,且中继依赖的 2025 任务机制已被列入移除(见 known-gaps.md)。

为什么启发式交互幸存而推送式采样没有

文档给出一个决定性事实:SDK 为启发式交互构建了服务端发射器(Elicit/Resolve),而没有为采样构建。线路携带全部三种输入请求类型(sampling / elicitation / roots),客户端也分发全部三种;但只有启发式交互能由服务端产生。这就是启发式交互通过 MRTR 在 4.0 幸存、而推送式采样没有的原因。

客户端侧已有基础:FastMCP 客户端通过其启发式交互回调分发输入请求;client/client.py 的_resolve_input_required(input_required_max_rounds上限控制)驱动 SEP-2322 的InputRequiredResult直至终态,这印证了"剩余声明式工作只需确认 FastMCP 客户端像 SDK 自身客户端一样驱动 input-required driver"的论断。

中间件根分发(Middleware root dispatch)——Shipped(#4553)

迁移已通过FastMCPServerMiddleware将initialize拦截路由到 SDK 的ServerMiddleware列表。#4553使该入口成为中间件分发的根:

  • FastMCP 的方法无关钩子(on_message、on_request、on_notification)现在对每一条入站消息触发——客户端取消、进度通知,以及路由或验证失败请求——而不仅是到达组件 handler 的消息。
  • 组件方法继续运行自己的内部链;方法集合加分发标志(dispatch flag)使两条通路互不重叠,每个钩子对每条消息恰好触发一次。

从源码核验:FastMCPServerMiddleware定义于 server/low_level.py,其_INTERIOR_METHODS(同文件 L45)方法集合用于区分"内部已分发"与"根分发"两条通路;server/middleware/middleware.py 中的MiddlewarePhase与mark_interior_dispatched实现去重;server/server.py 的_dispatch_component_middleware保持组件内部链。验证测试位于tests/server/middleware/test_message_visibility.py。全部十三个内置中间件在改动后无需修改即通过其测试套件。

这一特性在 change-register.md 中被标记为New (coverage)。

一等公民 2026 客户端(First-class 2026 client)——部分发布,完整组合被上游阻塞

已发布的客户端部分

fastmcp.Client现在默认mode="auto"(#4572):

  • 探测server/discover,回退到经典握手;
  • 通过现有 handlers 应答多轮input_required请求;
  • 同一 PR 还浮出extensions=和result_claims=(SEP-2133)。

客户端还放弃了其派生的协议辅助函数——扩展折叠、驱逐消息 handler、discover 综合——改用 SDK 自己的实现(#4574)。

从源码核验 client/client.py:mode参数文档明确"auto(默认)探测server/discover并协商现代时代,对任何不是现代对端正面证据的服务器按拒绝列表回退到 initialize 握手";"legacy"强制握手、与 v4 前行为逐字节一致;现代版本字符串(如"2026-07-28")直接采纳该版本而无需探测。

决策:组合而非包装(D16)

此处决策是compose, not wrap(D16):在 SDK 高层mcp.Client之上重建fastmcp.Client,而非包装mcp.ClientSession。可干净组合的部分已发布;其余部分在上游被双重阻塞:

  1. mcp.Client在单一硬编码位置构造其ClientSession,无注入钩子;而 FastMCP 的session_class是承重设计(ProxyClient替换为跳过结果验证的会话,使后端 schema 违规在终端客户端暴露而非变成代理错误)——需要一个与早前添加的notification_bindings=参数同形的session_factory=钩子。
  2. mcp.Client.__aenter__拒绝重入,而 FastMCP 客户端刻意可重入(其引用计数的上下文管理器用于修复代理会话复用死锁)——重建还需 SDK 客户端容忍可重入进入。

两者都必须先在上游落地;session_factory=单独是必要但不充分的。

该工作流还拥有服务端无状态设计空洞——ctx.session_id/set_state往返和状态化代理亲和性——因为它们都归结为同一个问题:"没有会话时什么是会话?" 完整核算见 known-gaps.md 与 change-register.md。

无状态时代的三个事实层级(Known Gaps)

known-gaps.md 给出了诚实的三分法(仅针对2026-07-28连接;当今所有客户端协商握手时代,行为与以往完全一致):

  • 协议构造上仅旧时代可用(记录而非构建):按会话日志级别、EventStore/可恢复性、ping 保活。
  • 构造上已无状态(在现代可用):tasks/get轮询(按task_id键控、Docket/Redis 支撑)、OAuth bearer 验证(每次 POST 携带并重新验证凭据)、请求内进度与日志通知(随该 POST 自身的 SSE 汇送达)。
  • 推迟到多协议工作流的设计空洞:ctx.session_id/ctx.set_state/ctx.get_state(现代请求上session_id铸造新鲜uuid4、缓存在请求返回即弃的connection.state上,因此 set/get 静默不往返——无错误,只是丢数据);状态化代理亲和性退化(代理_caches按请求级Connection键控,现代连接上塌缩为无状态代理)。危险在于代码当前不报错地返回,读起来像"可用"实为静默降级。

订阅、缓存提示、扩展、OTel(Subscriptions, cache hints, extensions, OTel)——状态分化

一组为 v4 跟踪的协议特性群,状态已分化:

  • 缓存提示——已发布(#4464):服务端级作者体验FastMCP(cache_ttl=..., cache_scope=...)(SEP-2549)为每个可缓存结果盖章;FastMCP 客户端通过选择加入的响应缓存遵循提示。源码位置:server/server.py 的cache_ttl/cache_scope参数经build_cache_hints(server/caching.py)生成提示。cache_ttl以秒为单位,cache_scope为"public"或"private"(设 TTL 时默认"private");仅有 scope 而无 TTL 会在构造时被拒绝(scope 单独不启用缓存,因为客户端以 TTL 存在为门控)。验证测试:tests/server/test_cache_hints.py(单元验证 + 与fastmcp.Client(cache=True)的端到端互操作)。
  • OpenTelemetry——已发布(#4481):span 默认开启(无 exporter 时为空操作),属性与 SDK 对齐,FASTMCP_TELEMETRY_MODE设置支持native/propagation_only/off。对应 settings.py 的telemetry_mode与 telemetry.py 的实现。
  • 扩展——客户端侧已发布(#4572):Client(extensions=..., result_claims=...)广告选择加入的客户端扩展(SEP-2133)。服务端侧是独立的 Designed 工作流(见下节)。extensions/MCP Apps 能力广告的跨时代调和仍待定(该能力在 pre-2026 协商版本被剥离——sdk-feedback #2)。
  • 订阅——未启动(Not started):计划为subscriptions/listen表面,由订阅总线支撑。

FastMCP 原生扩展 API(FastMCP-native extension API)——Shipped(#4602)

扩展是什么

MCP 扩展(SEP-2133)是可选的、能力协商的协议特性,由反向 DNS 字符串标识——io.modelcontextprotocol/ui(MCP Apps)、io.modelcontextprotocol/tasks(SEP-2663)。它们是 SDK v2 中真正的新抽象;v1 中不存在。SDK 通过Extension服务器类暴露它们:贡献一个能力、加法请求方法、一个tools/call拦截器,外加对称的ClientExtension(含结果声明与通知绑定)。

现状与设计

FastMCP 已原生转发ClientExtension(Client(extensions=...),#4572)。服务端则完全不使用 SDK 的Extension类:MCP Apps 先于该抽象存在,因此 FastMCP 在低层服务器上手工拼接ui能力到get_capabilities()并直接遍历工具元数据。这对一个扩展有效,但每个新协议扩展都意味着对核心的定制手术(bespoke surgery)。

设计中的工作是 FastMCP 原生服务器扩展 API——单一注册点mcp.add_extension(...),贡献协商能力、请求方法与tools/call拦截器,并可访问 SDK 的Extension未提供的 FastMCP 级构造(组件注册表、Context、auth scope)。它以 SEP-2663 tasks 扩展为设计目标,因为 tasks 锻炼了完整表面——能力和方法和拦截和客户端声明/通知——而 MCP Apps 只锻炼子集。Tasks 是探路者(pathfinder);MCP Apps 作为快速跟进迁移到扩展 API,删除手工拼接,并确认设计可泛化。

扩展与中间件的判别器

扩展 API 与中间件的区分标准:扩展是客户端必须理解的协商契约变更;中间件是客户端永远看不到的单边服务器行为。删除能力广告而客户端行为不变——那才是中间件。PII 检测、认证、限流属于中间件;Tasks、Apps 属于扩展。判定测试:删除能力广告——如果客户端行为毫无变化,它就是中间件。

后台任务(SEP-2663)——Shipped(#4603)

重新构建的形态

后台任务以fastmcp-tasks(仓库内可选包)形式回到现代时代,重建于io.modelcontextprotocol/tasks扩展之上(SEP-2663,Final,2026-05-15 上游合并)。SEP-2663 取代 SEP-1686 但保留其轮询核心:

  1. 广告 tasks 能力的客户端发出增强的tools/call;
  2. 服务器决定是否作为任务运行,返回携带服务器生成任务 id 的CreateTaskResult;
  3. 客户端轮询tasks/get直至终态,结果内联在该响应中。

FastMCP 现有 SEP-1686 线路层被移除,而底下的 Docket/Redis 执行引擎原封不动移入fastmcp-tasks——规范朝着 FastMCP 已构建的方向移动,因此重建主要是删除加薄线路适配器。task=True保持作者表面(由fastmcp[tasks]extra 与显式mcp.add_extension(TasksExtension(...))门控,是上述扩展 API 的第一个消费者),因此已使用 tasks 的服务器无需改代码。v1 范围为仅轮询、仅tools/call。

完整设计(线路差异、引擎/线路拆分、打包、客户端体验、排序、风险、五个已解决决策)见专门的 background-tasks.md 页面。核心要点:

  • add_extension是必须的,task=True不会被自动检测。扩展需要配置(后端 URL、worker 并发、TTL 默认值),add_extension(TasksExtension(...))是其自然归宿;同时保持能力广告诚实——服务器广告tasks能力当且仅当扩展已注册,并消除最危险的坑:生产环境因没人配置 Redis 而在内存后端上静默运行工具。
  • 客户端体验:友好接口call_tool透明驱动轮询循环并返回完成结果(服务器是否任务化不可见);低层call_tool_mcp交回原始CreateTaskResult;友好接口上的"快速返回"标志产出Task句柄(.status()、.wait()、.cancel()、可 await)而不阻塞。
  • 仓库中fastmcp-tasks/fastmcp_tasks/目录(含worker_cli.py、input_store.py、wire_production.py等模块)即该可选包的落地位置。

SDK 委托第二轮(SDK delegation, round two)——Planned(以上游为门控)

真正的 HTTP 简化是 v4 项目而非单个 PR。FastMCP 可以在上游补充三件事后,将create_streamable_http_app折叠到 SDK 的Server.streamable_http_app()上:

  1. 按会话的事件存储作用域(per-session event-store scoping);
  2. 用户中间件注入钩子(user-middleware injection hook);
  3. 生命周期钩子(lifespan hook)。

回报不仅是更少的代码——FastMCP 还将继承 SDK 的会话所有者凭据强制(session-owner credential enforcement),这是它今天缺少的安全增益。这是要提交的三项上游特性请求(连同 known-gaps.md 描述的咨询卷宗)。在它们落地之前,change-register.md 中记录的四个 HTTP 覆盖(事件存储会话作用域、生命周期调和、优雅传输终止、用户 ASGI 中间件钩子)继续保留。

一个值得在 FastMCP 侧浮出的潜在能力:session_idle_timeout被管理器接受但从未被create_streamable_http_app设置——如果 FastMCP 想暴露它,这是一行接线。

全貌总结:v4 特性状态一览

特性状态关键 PR / 依据
采样移除Shipped(4.0)#4448;server/context.py、server/server.py
MRTR 启发式交互(guard 形式)Shipped(4.0)guard 形式已发布;Resolve声明式层 Designed;server/mixins/mcp_operations.py
中间件根分发Shipped#4553;server/low_level.py
一等公民 2026 客户端部分 Shipped;完整组合被上游阻塞#4572、#4574;client/client.py
缓存提示Shipped#4464;server/server.py
OpenTelemetryShipped#4481;settings.py、telemetry.py
客户端扩展(SEP-2133)客户端侧 Shipped;服务端侧 Designed#4572;#4602
订阅Not startedsubscriptions/listen+ 订阅总线
FastMCP 原生扩展 APIShipped#4602;mcp.add_extension(...)
后台任务(SEP-2663)Shipped#4603;fastmcp-tasks包、task=True+mcp.add_extension(TasksExtension(...))
SDK 委托第二轮Planned(以上游为门控)三项上游特性请求;四个 HTTP 覆盖保留

阅读本文后,你可以将每项特性与其仓库落地位置一一对应:需要验证服务端 API 移除时查 server/context.py 与_REMOVED_KWARGS;需要编写现代启发式交互工具时参考 guard 形式的InputRequiredResult与ctx.input_responses/ctx.request_state;需要启用后台任务时按 background-tasks.md 配置mcp.add_extension(TasksExtension(...))与task=True。所有状态与依据均可在此仓库的 v4 笔记体系(index.md、change-register.md、known-gaps.md、protocol-2026.md、background-tasks.md)中交叉核验。

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

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