☰
FastMCP 3.0 组件发现方法整合:从 get_* 到统一 list_* API 的重构实践
2026/10/11 17:03:01 网站建设 项目流程

FastMCP 3.0 组件发现方法整合:从 get_* 到统一 list_* API 的重构实践

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

FastMCP 在 3.0 版本中对组件发现(component discovery)API 做了一次系统性重构:将服务端并行的get_tools()/_list_tools()、get_resources()/_list_resources()、get_prompts()/_list_prompts()、get_resource_templates()/_list_resource_templates()两套几乎重复的实现,合并为单一、规范的list_*方法族,并彻底删除旧的复数get_*方法与内部_list_*方法。这篇技术文章基于 dev-docs/v3-notes/get-methods-consolidation.md 的设计记录,结合当前仓库源码,拆解这次重构的背景、两阶段演进过程、返回类型与中间件语义的变化,以及带给开发者的实际收益,帮助你快速掌握 v3 API 并平滑完成迁移。

背景:两套并行的组件列举实现

在 FastMCP 2.x 及更早版本中,服务端针对每一类 MCP 组件(工具、资源、提示词、资源模板)都维护了两套列举方法:

  • 公开 API:get_tools()、get_resources()、get_prompts()、get_resource_templates(),面向应用开发者;
  • 内部实现:_list_tools()、_list_resources()、_list_prompts()、_list_resource_templates(),被 MCP 协议处理器内部调用。

问题在于这两套方法的功能几乎完全相同——都要做组件聚合、去重、可见性过滤,最后返回组件集合——却在去重键(dedup key)、日志输出、返回类型等细节上存在微妙的差异。这带来三个直接后果:

  1. 重复维护成本:任何行为调整(如新增可见性过滤规则)都要同步修改两处实现;
  2. 行为漂移风险:去重键、日志、返回类型的不一致导致公开 API 与协议处理器看到的结果可能不同;
  3. 心智负担:开发者需要理解get_*与_list_*的差异才能正确使用。

解决方案:两阶段演进为统一的 list_* 方法

这次整合并非一步到位,而是在源码中分两个阶段推进,最终对齐到新的Provider抽象接口:

第一阶段:Consolidation(2025 年 12 月)——先合并为一套 get_*

将get_*与_list_*合并为单一的get_*方法,同时引入apply_middleware参数,让公开方法可以显式选择是否执行中间件链,取代原先独立的_list_*_middleware()内部方法。

第二阶段:Rename(2026 年 1 月)——对齐 Provider 接口重命名为 list_*

当FastMCP被重构为继承Provider基类之后,方法名从get_*改为list_*,与Provider接口保持一致;apply_middleware参数也随之更名为run_middleware,默认值为True。

最终形态的规范方法签名如下:

async def list_tools(self, *, run_middleware: bool = True) -> Sequence[Tool]: """Canonical method for listing tools.""" ...

关键变更一:返回类型从 dict 改为 list

旧版get_*返回按名称索引的字典(dict),而dict的键实际上是冗余信息——组件对象本身已经带有.name(工具、提示词)或.uri(资源、资源模板)属性。因此 v3.0 统一改为返回Sequence(列表),需要按名称查找时由调用方自行完成:

# Before (v2.x) tools = await server.get_tools() tool = tools["my_tool"] # After (v3.0) tools = await server.list_tools() tool = next(t for t in tools if t.name == "my_tool")

这一改动消除了键与对象属性不一致的可能,也让返回类型在四类组件间保持统一(Sequence[Tool]、Sequence[Resource]、Sequence[Prompt]、Sequence[ResourceTemplate])。

关键变更二:中间件通过参数显式控制

run_middleware: bool = True参数(默认开启)负责是否执行中间件链,替代了旧版独立的_list_*_middleware()方法。这让“列举组件”这一行为也进入统一的中间件体系。

从当前仓库源码可以看到,四个list_*方法在 fastmcp_slim/fastmcp/server/server.py 中遵循完全一致的模板:

  1. 创建MiddlewareContext,携带对应的 MCP 协议方法名(如tools/list、resources/list、resources/templates/list、prompts/list);
  2. 若run_middleware=True,调用_dispatch_component_middleware(),将“不执行中间件的自身调用”作为call_next传入;
  3. 中间件执行完毕后进入核心逻辑:调用super().list_*()从 Provider 聚合组件,应用会话级 transforms,过滤is_enabled()的组件,最后执行组件级鉴权检查。

以list_tools为例(server.py 中第 834 行起):

async def list_tools(self, *, run_middleware: bool = True) -> Sequence[Tool]: """List all enabled tools from providers. Overrides Provider.list_tools() to add enabled filtering, auth filtering, and middleware execution. Returns all versions (no deduplication). Protocol handlers deduplicate for MCP wire format. """ async with fastmcp.server.context.Context(fastmcp=self) as ctx: if run_middleware: mw_context = MiddlewareContext( message=mcp_types.ListToolsRequest(method="tools/list"), source="client", type="request", method="tools/list", fastmcp_context=ctx, ) return await self._dispatch_component_middleware( context=mw_context, call_next=lambda context: self.list_tools(run_middleware=False), ) # ... 核心逻辑:聚合、会话 transforms、enabled 过滤、鉴权

这里有一个值得注意的实现细节:_dispatch_component_middleware的call_next是对自身方法以run_middleware=False的递归调用。这样做既保证了中间件链(on_message→on_request→ 对应协议方法钩子)能够完整观察这一次列举操作,又避免了无限递归——核心逻辑只在run_middleware=False的分支执行一次。_dispatch_component_middleware定义于 server.py 第 579 行,其 docstring 说明它会一次性跑完整个 FastMCP 中间件链,并通过mark_interior_dispatched()防止根分发对同一 wire 消息二次观察。

关键变更三:去重职责的明确分工

合并后的list_*方法文档中明确写明:“返回所有版本(不去重),协议处理器负责为 MCP wire 格式去重”(Returns all versions (no deduplication). Protocol handlers deduplicate for MCP wire format.)。

这意味着职责边界被清晰化:

  • FastMCP.list_*()(公开 API 层):提供全量、经过过滤与鉴权的组件视图,供应用代码使用;
  • 协议处理器(_on_list_tools、_on_list_resources等):在 fastmcp_slim/fastmcp/server/mixins/mcp_operations.py 中负责按name、uri或uri_template去重后再序列化为 wire 消息。
# mcp_operations.py 中的协议处理器调用模式 list(await self.list_tools()), lambda t: t.name # tools/list 按 name 去重 list(await self.list_resources()), lambda r: str(r.uri) # resources/list 按 uri 去重

Provider 基类:list_* 接口的定义与重写

重构的另一个关键点在于FastMCP继承自Provider,四个list_*方法实际上是在重写 Provider 接口:

  • Provider.list_tools()等接口定义于 fastmcp_slim/fastmcp/server/providers/base.py(第 142、238、278、321 行);
  • FastMCP在 server.py 中重写这些方法,叠加 FastMCP 特有的行为:enabled 过滤、会话级 transforms、Prefab 渲染器 URI 重写(_rewrite_prefab_uris)、合成资源追加(synthesize_prefab_resources)以及组件级鉴权(run_auth_checks)。

这一设计让FastMCP.list_tools()与Provider.list_tools()构成清晰的“接口 + 增强实现”关系:任何实现了Provider接口的组件源(如文件系统 Provider、SQLite Provider、OpenAPI Provider 等)都能复用统一的列举语义,而FastMCP作为聚合层(AggregateProvider)负责汇总与增强。

重构收益

设计文档总结了这次整合的五点收益,结合源码可以进一步印证:

收益说明源码印证
单一事实来源每个组件类型只有一套列举方法,不再存在双实现漂移四个list_*方法均位于 server.py,无_list_*残留
行为一致四类组件共享相同的去重语义、可见性过滤与鉴权流程每个方法体遵循同一模板:super().list_*()→ 会话 transforms →is_enabled()→run_auth_checks()
API 更清晰公开方法带显式run_middleware开关,中间件行为可预期签名统一为*, run_middleware: bool = True
对齐 ProviderFastMCP.list_*()重写Provider.list_*(),接口统一见 providers/base.py 与 server.py 中的 override
更少代码删除了约 200 行重复实现合并前每个组件类型有两套方法,合并后仅剩一套

迁移指南:从 v2 到 v3

对升级到 FastMCP 3.0 的开发者,代码迁移路径非常明确:

1. 方法重命名

  • get_tools()→list_tools()
  • get_resources()→list_resources()
  • get_prompts()→list_prompts()
  • get_resource_templates()→list_resource_templates()

2. 返回类型适配旧版按名称索引的dict访问方式需要改为线性查找。如果代码中大量使用了tools["my_tool"]这种访问模式,可以封装一个小的辅助函数:

def find_tool(tools, name): return next((t for t in tools if t.name == name), None)

3. 中间件语义确认默认run_middleware=True会执行完整的中间件链,行为与旧版协议路径一致;若在自定义流程中希望跳过中间件直接获取原始组件列表,显式传入run_middleware=False即可。这与旧版直接调用内部_list_*方法的效果等价,但现在是通过公开 API 的参数完成的,不再需要访问私有方法。

涉及文件一览

  • fastmcp_slim/fastmcp/server/server.py:四个规范的list_*方法(list_tools位于第 834 行、list_resources位于第 971 行、list_resource_templates位于第 1106 行、list_prompts位于第 1242 行)以及_dispatch_component_middleware(第 579 行);
  • fastmcp_slim/fastmcp/server/providers/base.py:Provider基类定义list_*接口;
  • fastmcp_slim/fastmcp/server/mixins/mcp_operations.py:协议层_on_list_*处理器,负责按name/uri/uri_template去重后返回 wire 格式;
  • dev-docs/v3-notes/get-methods-consolidation.md:本次重构的设计决策记录(本文的直接依据);
  • dev-docs/v3-notes/v3-features.md:FastMCP 3.0 整体特性笔记,可对照了解重构在版本中的定位。

总结

FastMCP 3.0 的组件发现方法整合是一次典型的“接口收敛”重构:通过两阶段演进(先合并为get_*、再随Provider抽象重命名为list_*),消除了公开 API 与内部协议路径的双实现,统一了返回类型(dict→Sequence)、中间件语义(run_middleware参数)与去重职责(协议层负责 wire 去重)。对开发者而言,迁移成本集中在方法重命名与返回类型适配上,而收益是更一致、更可预期、更易维护的组件列举 API。

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

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

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

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

立即咨询