如何用 FastMCP 组件版本化与 VersionFilter 从同一份代码同时提供 v1 和 v2 API?
2026/9/13 11:46:30 网站建设 项目流程

如何用 FastMCP 组件版本化与 VersionFilter 从同一份代码同时提供 v1 和 v2 API?

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

如果你的同一个工具(tool)、资源(resource)或提示词(prompt)有多个实现,且 v2 在 v1 的基础上新增了参数,又不想为 v1、v2 客户端维护两套独立部署,FastMCP 的组件版本化(component versioning)可以解决这个问题:给每个组件注册多个版本,再用VersionFilter从同一份组件定义中切出不同的 API 表面。v1 表面只暴露低版本实现,v2 表面只暴露高版本实现,两个服务共享同一个 provider。

这套机制适用于 FastMCP 3.0.0 及以上版本(文档以<VersionBadge version="3.0.0" />标注)。以下是完整的操作路径,相关文档见 docs/servers/versioning.mdx。

准备环境

FastMCP 要求 Python 3.10 及以上(见 pyproject.toml 中的requires-python = ">=3.10")。安装方式见 docs/getting-started/installation.mdx:

uv add fastmcp

或者用 pip:

pip install fastmcp

安装完成后运行以下命令确认安装成功:

fastmcp version

能打印出 FastMCP version、MCP version、Python version 等信息即为正常(安装文档中展示的输出版本号仅作为文档示例,你的实际版本号以安装结果为准)。

第一步:在同一 provider 上注册 v1 和 v2 组件

给组件装饰器加version参数即可声明版本。FastMCP 把版本存为字符串,并按组件标识符分组——工具(tool)和提示词(prompt)按名称分组,资源(resource)按 URI 分组。

推荐的组织方式是:把版本化组件定义在一个共享的LocalProvider上,而不是直接挂在各个服务上:

from fastmcp import FastMCP from fastmcp.server.providers import LocalProvider from fastmcp.server.transforms import VersionFilter # 定义版本化组件到共享 provider 上 components = LocalProvider() @components.tool(version="1.0") def calculate(x: int, y: int) -> int: """Add two numbers.""" return x + y @components.tool(version="2.0") def calculate(x: int, y: int, z: int = 0) -> int: """Add two or three numbers.""" return x + y + z

同一个名称calculate注册了两个版本,两个实现都保留在 provider 中,由后续的过滤器决定每个服务暴露哪一个。资源和提示词的写法同理,例如@mcp.resource("config://app", version="1.0")@mcp.prompt(version="1.0")

注意一条硬约束:同一个名称下要么全部版本化,要么全部不版本化。把一个未版本化的calculate和一个带versioncalculate注册到同一个服务上,会在注册时抛出ValueError,错误信息为 "Cannot add versioned tool 'calculate' (version='2.0'): an unversioned tool with this name already exists. Either version all components or none."

第二步:用 VersionFilter 创建两个 API 表面

创建两个FastMCP服务,让它们共享同一个 provider,各自挂载不同的VersionFilter

# 创建共享 provider、但过滤器不同的两个服务 api_v1 = FastMCP("API v1", providers=[components]) api_v1.add_transform(VersionFilter(version_lt="2.0")) api_v2 = FastMCP("API v2", providers=[components]) api_v2.add_transform(VersionFilter(version_gte="2.0"))

VersionFilter只有两个关键字参数(均为 keyword-only),对应比较运算符:

  • version_gte:版本大于等于该值时通过;
  • version_lt:版本小于该值时通过;
  • 两者至少指定一个,都不指定会在构造时抛ValueError
  • 两个参数可以同时使用以表示闭开区间,例如VersionFilter(version_gte="2.0", version_lt="3.0")表示[2.0, 3.0),只命中 v2.x。

一个容易被忽略的行为:未版本化的组件默认不会被过滤掉。也就是说,如果 provider 里同时有版本化和未版本化的组件,加不加VersionFilter,未版本化的组件在两个表面上都可见。这是为了避免给混合了版本化/未版本化组件的服务加过滤器时意外隐藏组件。如果你的 API 表面要求严格的版本隔离,需要显式传入include_unversioned=False把它们排除。

这样,连接api_v1的客户端看到的是两参数版本的calculate,连接api_v2的客户端看到的是三参数版本。两个服务共享同一份组件定义。

第三步:验证两个表面暴露的版本

用 FastMCP 的Client分别连接两个服务,先列组件、再实际调用,即可核对过滤是否生效:

from fastmcp import Client async with Client(api_v1) as client_v1: tools = await client_v1.list_tools() for tool in tools: if tool.meta: fastmcp_meta = tool.meta.get("fastmcp", {}) # 当前返回的版本(默认为过滤后的最高版本) print(f"Version: {fastmcp_meta.get('version')}") # 该组件的全部可用版本 print(f"Available: {fastmcp_meta.get('versions')}") async with Client(api_v1) as client_v1, Client(api_v2) as client_v2: r1 = await client_v1.call_tool("calculate", {"x": 1, "y": 2}) r2 = await client_v2.call_tool("calculate", {"x": 1, "y": 2, "z": 10})

验证时看两点:

  1. 列表元数据:客户端列出组件时,每个版本化组件的meta.fastmcp中,version字段是当前返回的版本(默认是可见范围内最高版本),versions字段按从高到低列出全部已注册版本,例如["2.0", "1.0"]。未版本化的组件完全没有这两个字段。
  2. 实际调用:对同一个calculate调用,通过api_v1走的是 v1.0 实现(不接受z参数),通过api_v2走的是 v2.0 实现(z有默认值 0)。如果 v1 表面意外返回了三参数版本,说明过滤器没有挂上或参数写错。

仓库中带完整输出的示例脚本是 examples/versioning/version_filters.py,它定义了process的 1.0/2.0/3.0 三个版本加一个未版本化的health工具,创建version_lt="2.0"version_gte="2.0", version_lt="3.0"version_gte="3.0"三个表面,最后用Client逐个打印每个表面可见的工具及其版本,并对每个表面执行同一个调用做对照。运行方式:

uv run python examples/versioning/version_filters.py

脚本会打印三个表格(每个表面对应的 Tool / Version 列表)和一段 "Same call through different APIs" 对照输出,你可以直接用它核对过滤行为是否符合预期。另一个更完整的版本化示例(工具、资源、提示词三类组件各两个版本)是 examples/versioning/versioned_components.py。

版本比较规则与限制

配置过滤器时,版本字符串的比较方式值得了解,否则区间可能切错:

  • PEP 440 风格的版本(如"1.0""2.1.3""1.0a1")按语义比较,数字段按数值比较:"1.9" < "1.10",预发布版本排在正式版之前("1.0a1" < "1.0b1" < "1.0")。
  • 其他格式(如日期、自定义方案)按字符串字典序比较,ISO 日期这类天然可排序的格式结果正确:"2025-01-15" < "2025-02-01"
  • 比较前会去掉v前缀,因此"v1.0""1.0"视为相等。

此外还有一个针对挂载(mount)场景的说明:如果你在父服务上挂载了子服务,再对父服务加VersionFilter,过滤器同样作用于挂载服务的组件——区间过滤在 provider 层完成,子服务不需要知道父服务的版本约束;父服务按命名空间(namespace)后的组件名做过滤,但仍基于版本本身。

可选的后续操作

完成双版本上线后,文档给出了两个直接的延伸操作,均见 docs/servers/versioning.mdx:

  • 按版本调用特定实现:FastMCP 客户端的call_toolread_resourceget_prompt都接受可选version参数,例如await client.call_tool("calculate", {"x": 1, "y": 2}, version="1.0");请求不存在的版本会抛NotFoundError,不会静默回退到别的版本。对于没有内建版本支持的通用 MCP 客户端,可以在请求参数的_meta.fastmcp.version字段里传版本号,组件实现本身看不到_meta
  • 迁移完成后清理旧版本mcp.local_provider.remove_tool("process_data", version="1.0")只删除指定版本,其余版本保持注册;不带version参数则删除该组件的全部版本。

这两个操作加上本文的双表面配置,就构成 FastMCP 文档中描述的完整迁移流程:旧实现标上 v1.0、新实现作为 v2.0 并行上线、客户端默认看到 v2.0、确认无误后移除 v1.0。

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

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

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

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

立即咨询