如何用 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和一个带version的calculate注册到同一个服务上,会在注册时抛出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})验证时看两点:
- 列表元数据:客户端列出组件时,每个版本化组件的
meta.fastmcp中,version字段是当前返回的版本(默认是可见范围内最高版本),versions字段按从高到低列出全部已注册版本,例如["2.0", "1.0"]。未版本化的组件完全没有这两个字段。 - 实际调用:对同一个
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_tool、read_resource、get_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),仅供参考