如何在 FastMCP 服务器上按标签动态启用和禁用组件做运行时可见性控制?
【免费下载链接】fastmcp🚀 The fast, Pythonic way to build MCP servers and clients.项目地址: https://gitcode.com/GitHub_Trending/fa/fastmcp
在一个 FastMCP 服务器上,工具、资源、模板和 Prompt 默认对客户端全部可见。当服务器承载了 admin 工具、beta 功能或不同角色才需要用的能力时,就需要在运行时按标签(tag)把这些组件动态隐藏或恢复:被禁用的组件会从所有客户端查询中消失,也无法被调用;重新启用后它们再次出现在列表里。这个能力在 Component Visibility 文档中说明,自 FastMCP 3.0.0 起可用。本文的任务路径是:给组件打标签 → 用enable()/disable()按标签控制可见性 → 用Client的list_tools()验证客户端实际看到的内容。
准备:安装并确认版本
按 Installation 文档安装:
uv add fastmcp也可以用 pip:
pip install fastmcp用下面命令确认安装成功:
fastmcp version安装文档给出的示例输出(文档示例,版本和平台信息以实际环境为准):
$ fastmcp version FastMCP version: 4.0.0 MCP version: 2.0.0 Python version: 3.12.2可见性控制功能需要 3.0.0 及以上版本,4.x 已包含该功能。
第一步:在创建组件时用tags参数打标签
标签要在组件注册时定义,之后才能按标签做批量操作。以工具为例,@mcp.tool装饰器接受tags参数,类型是字符串集合(Tools 文档):
from fastmcp import FastMCP mcp = FastMCP("Server") @mcp.tool(tags={"public", "read"}) def get_data() -> str: return "data" @mcp.tool(tags={"admin", "write"}) def set_data(value: str) -> str: return f"Set: {value}" @mcp.tool(tags={"admin", "dangerous"}) def delete_data() -> str: return "Deleted" @mcp.tool def get_status() -> str: return "OK"资源、模板和 Prompt 同理:它们的注册接口也支持tags参数,可见性规则对所有组件类型生效。
第二步:按标签禁用与恢复组件
每个 FastMCP 服务器都提供enable()和disable()方法,用来控制组件可见性:
# 禁用所有带 admin 标签的工具 mcp.disable(tags={"admin"}) # 此时客户端只能看到 get_data 和 get_status # 恢复带 admin 标签的工具 mcp.enable(tags={"admin"}) # 此时客户端又能看到全部四个工具判定规则是:只要组件拥有任意一个被禁用的标签,它就会被禁用,不需要同时拥有多个标签。上面的delete_data同时带admin和dangerous两个标签,两次独立的disable(tags={...})调用都能命中它:
# 禁用所有 admin 工具 mcp.disable(tags={"admin"}) # 禁用所有 dangerous 工具(与 admin 有重叠) mcp.disable(tags={"dangerous"})需要注意过滤参数的组合语义:同一次调用里的多个条件互相收窄(取交集),而要做并集得分别调用多次。比如mcp.disable(names={"debug_info"}, tags={"dangerous"})只有在debug_info恰好也带dangerous标签时才生效,否则什么都不做——这是一个不会报错的静默失败。如果要禁用debug_info和所有dangerous标签组件,应拆成两次调用:
mcp.disable(names={"debug_info"}) mcp.disable(tags={"dangerous"})多次调用中后执行的规则覆盖先执行的规则,所以连续调用可以叠加出"宽泛规则 + 例外"的效果。
可选分支:白名单模式
默认是黑名单模式——未显式禁用的组件全部可见。如果想反过来,只放行指定标签的组件,用only=True切换为白名单模式(Visibility 文档的 Allowlist Mode 一节):
@mcp.tool(tags={"safe"}) def read_only_operation() -> str: return "Read" @mcp.tool(tags={"dangerous"}) def delete_all() -> str: return "Deleted" # 只启用 safe 标签的组件,其余全部禁用 mcp.enable(tags={"safe"}, only=True)调用enable(only=True)后:默认可见性状态切换为 disabled,之前的白名单被清空,只有命中的标签/名称被启用。之后再调用一次enable(..., only=True)就切换到新的白名单,比如从names白名单切到tags={"production"}白名单。白名单适合需要显式 opt-in 的受限环境;如果只是想关掉一组组件,用黑名单模式的disable()更直接。
验证:用 Client 确认客户端实际看到的组件
验证方式是走一次真实客户端会话,调用list_tools()查看过滤后的列表。仓库里的 namespace_activation 示例 展示了完整流程:服务端定义带标签的工具并全局禁用,客户端用 Client 依次查看初始列表、激活、再查看。核心逻辑是:
import asyncio from fastmcp import Client async def main(): async with Client(server) as client: # 初始状态下客户端看到的工具 tools = await client.list_tools() print([t.name for t in tools]) # 调用激活工具后,本会话的可见性变化 result = await client.call_tool("activate_finance", {}) print(result.data) tools = await client.list_tools() print([t.name for t in tools])可见性状态变化时 FastMCP 会自动向客户端发送tools/list_changed、resources/list_changed、prompts/list_changed等通知,支持通知协议的客户端会刷新组件列表,不需要手动触发。
进阶:让不同会话看到不同组件
服务器级的enable()/disable()影响所有连接的客户端。如果需要"同一个服务器、不同客户端看到不同组件"(例如按角色放行),文档给出 per-session 可见性:在工具里通过Context调用ctx.enable_components()、ctx.disable_components()、ctx.reset_visibility(),规则只作用于当前会话,其他会话继续看到全局默认值(Context 与 Visibility 文档)。
仓库提供了完整的可运行示例 examples/namespace_activation/server.py,结构是:
from fastmcp import FastMCP from fastmcp.server.context import Context server = FastMCP("Multi-Domain Assistant") # Finance 命名空间:用带前缀的标签标记 @server.tool(tags={"namespace:finance"}) def get_market_data(symbol: str) -> dict: """Get current market data for a symbol.""" return {"symbol": symbol, "price": 150.25} # 始终可见的激活工具 @server.tool async def activate_finance(ctx: Context) -> str: await ctx.enable_components(tags={"namespace:finance"}) return "Finance tools activated" @server.tool async def deactivate_all(ctx: Context) -> str: await ctx.reset_visibility() return "All namespaces deactivated" # 全局禁用命名空间工具,会话默认看不到 server.disable(tags={"namespace:finance"})会话内规则的处理顺序是:先应用全局 enable/disable 规则,再叠加会话规则;会话内规则按加入顺序累积,同一组件上后加入的规则生效。
运行这个示例的验证方法(README 给出的启动方式):
# 终端一 uv run python server.py # 终端二 uv run python client.pyREADME 中给出的示例输出(文档示例):
Namespace Activation Demo ╭─────────────────── Initial Tools ───────────────────╮ │ activate_finance, activate_admin, deactivate_all │ ╰─────────────────────────────────────────────────────╯ → Calling activate_finance() Finance tools activated ╭─────────────── After Activating Finance ────────────╮ │ analyze_portfolio, get_market_data, execute_trade, │ │ activate_finance, activate_admin, deactivate_all │ ╰─────────────────────────────────────────────────────╯对照这个输出可以核对三点:初始只看到激活类工具;调用activate_finance后 finance 工具进入列表;调用deactivate_all后回到初始状态。
限制与排查要点
- 组合条件取交集:一次调用里
names+tags是 AND 关系,不满足全部条件就静默不生效。想表达并集时拆成多次调用,多次调用是累积的。 - 覆盖顺序:多次
enable()/disable()调用按顺序应用,后面的覆盖前面的;白名单only=True会清空之前的白名单并把默认状态切为 disabled。 - 服务器级 vs Provider 级:
mcp.enable()/mcp.disable()作用于最终视图;LocalProvider上也可以各自设置可见性,Provider 的 transform 先执行,服务器级的后执行并拥有最终决定权,因此服务器可以对 Provider 禁用的组件重新enable()。 - 组件挂载:服务器级可见性对所有来源的组件生效,包括通过
mount()挂载进来的子服务器组件。
组件被禁用后,它会从列表查询中消失且无法被调用;需要恢复时再执行一次对应的enable()即可。如果还需要按单个组件精确定位,Visibility 文档的 Component Keys 一节介绍了keys过滤器和VersionSpec版本过滤的用法。
【免费下载链接】fastmcp🚀 The fast, Pythonic way to build MCP servers and clients.项目地址: https://gitcode.com/GitHub_Trending/fa/fastmcp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考