一、服务器组合
通过挂载和导入将多个 FastMCP 服务器组合成单个更大的应用程序。
新版本 2.2.0 功能
随着 MCP 应用程序的增长,您可能希望将工具、资源和提示组织到逻辑模块中,或重用现有的服务器组件。FastMCP 通过两种方法支持组合:
- import_server:用于带前缀的组件一次性复制(静态组合)。
- mount:用于创建主服务器将请求委托给子服务器的实时链接(动态组合)。
二、为什么需要组合服务器?
模块化:将大型应用程序分解为更小、专注的服务器(例如,WeatherServer、DatabaseServer、CalendarServer)。
- 可重用性:创建通用工具服务器(例如,TextProcessingServer)并在需要的地方挂载它们。
- 团队协作:不同的团队可以分别处理独立的 FastMCP 服务器,之后再进行组合。
- 组织性:将相关功能按逻辑分组。
三、导入 vs 挂载
选择导入还是挂载取决于您的用例和需求。

四、代理服务器
FastMCP 支持 MCP 代理,允许您在本地 FastMCP 实例中镜像本地或远程服务器。代理与导入和挂载完全兼容。
新版本 2.4.0 功能
您还可以从遵循 MCPConfig 模式的配置字典创建代理,这对于快速连接到一个或多个远程服务器非常有用。有关基于配置的代理的详细信息,请参阅代理服务器文档。请注意,MCPConfig 遵循一个新兴标准,其格式可能会随时间演变。
工具、提示、资源和模板的前缀规则在导入、挂载和代理中都是相同的。
五、导入(静态组合)
import_server() 方法将一个 FastMCP 实例(子服务器)的所有组件(工具、资源、模板、提示)复制到另一个实例(主服务器)中。可以提供可选的 prefix 以避免命名冲突。如果未提供前缀,组件将不经修改地导入。当多个服务器以相同的前缀(或无前缀)导入时,最近导入的服务器的组件优先。
python
from fastmcp import FastMCP
import asyncio
# 定义子服务器
weather_mcp = FastMCP(name="WeatherService")
@weather_mcp.tool
def get_forecast(city: str) -> dict:
"""获取天气预报。"""
return {"city": city, "forecast": "Sunny"}
@weather_mcp.resource("data://cities/supported")
def list_supported_cities() -> list[str]:
"""列出支持天气的城市。"""
return ["London", "Paris", "Tokyo"]
# 定义主服务器
main_mcp = FastMCP(name="MainApp")
# 导入子服务器
async def setup():
await main_mcp.import_server(weather_mcp, prefix="weather")
# 结果:main_mcp 现在包含带前缀的组件:
# - 工具:"weather_get_forecast"
# - 资源:"data://weather/cities/supported"
if __name__ == "__main__":
asyncio.run(setup())
main_mcp.run()
5.1 导入的工作原理
当您调用 await main_mcp.import_server(subserver, prefix={whatever}) 时:
工具:subserver 的所有工具都添加到 main_mcp 中,名称使用 {prefix}_ 前缀。
- subserver.tool(name="my_tool") 变为 main_mcp.tool(name="{prefix}_my_tool")。
资源:所有资源都添加了前缀的 URI 和名称。 - URI:subserver.resource(uri="data://info") 变为 main_mcp.resource(uri="data://{prefix}/info")。
- 名称:resource.name 变为 "{prefix}_{resource.name}"。
资源模板:模板的前缀方式与资源类似。 - URI:subserver.resource(uri="data://{id}") 变为 main_mcp.resource(uri="data://{prefix}/{id}")。
- 名称:template.name 变为 "{prefix}{template.name}"。
提示:所有提示都添加了使用 {prefix} 前缀的名称。 - subserver.prompt(name="my_prompt") 变为 main_mcp.prompt(name="{prefix}_my_prompt")。
请注意,import_server 执行组件的一次性复制。在导入之后对 subserver 所做的更改不会反映在 main_mcp 中。subserver 的 lifespan 上下文也不会由主服务器执行。
提示:prefix 参数是可选的。如果省略,组件将不经修改地导入。
5.2 无前缀导入
新版本 2.9.0 功能
您也可以导入服务器而不指定前缀,这会使用其原始名称复制组件:
python
from fastmcp import FastMCP
import asyncio
# 定义子服务器
weather_mcp = FastMCP(name="WeatherService")
@weather_mcp.tool
def get_forecast(city: str) -> dict:
"""获取天气预报。"""
return {"city": city, "forecast": "Sunny"}
@weather_mcp.resource("data://cities/supported")
def list_supported_cities() -> list[str]:
"""列出支持天气的城市。"""
return ["London", "Paris", "Tokyo"]
# 定义主服务器
main_mcp = FastMCP(name="MainApp")
# 导入子服务器
async def setup():
# 无前缀导入 - 组件保持原始名称
await main_mcp.import_server(weather_mcp)
# 结果:main_mcp 现在包含:
# - 工具:"get_forecast"(保留原始名称)
# - 资源:"data://cities/supported"(保留原始 URI)
if __name__ == "__main__":
asyncio.run(setup())
main_mcp.run()
5.3 冲突解决
新版本 2.9.0 功能
当导入多个具有相同前缀或无前缀的服务器时,最近导入的服务器的组件优先。
六、挂载(实时链接)
mount() 方法在 main_mcp 服务器和 subserver 之间创建实时链接。不是复制组件,而是将对匹配可选 prefix 的组件的请求在运行时委托给 subserver。如果未提供前缀,子服务器的组件可以在没有前缀的情况下访问。当多个服务器以相同的前缀(或无前缀)挂载时,对于冲突的组件名称,最近挂载的服务器优先。
python
import asyncio
from fastmcp import FastMCP, Client
# 定义子服务器
dynamic_mcp = FastMCP(name="DynamicService")
@dynamic_mcp.tool
def initial_tool():
"""初始工具演示。"""
return "Initial Tool Exists"
# 挂载子服务器(同步操作)
main_mcp = FastMCP(name="MainAppLive")
main_mcp.mount(dynamic_mcp, prefix="dynamic")
# 在挂载后添加工具 - 它将通过 main_mcp 可访问
@dynamic_mcp.tool
def added_later():
"""挂载后添加的工具。"""
return "Tool Added Dynamically!"
# 测试对挂载工具的访问
async def test_dynamic_mount():
tools = await main_mcp.get_tools()
print("Available tools:", list(tools.keys()))
# 显示:['dynamic_initial_tool', 'dynamic_added_later']
async with Client(main_mcp) as client:
result = await client.call_tool("dynamic_added_later")
print("Result:", result.data)
# 显示:"Tool Added Dynamically!"
if __name__ == "__main__":
asyncio.run(test_dynamic_mount())
6.1 挂载的工作原理
当配置挂载时:
- 实时链接:父服务器建立到挂载服务器的连接。
- 动态更新:对挂载服务器的更改在通过父服务器访问时立即反映。
- 带前缀的访问:父服务器使用前缀将请求路由到挂载服务器。
- 委托:对匹配前缀的组件的请求在运行时委托给挂载服务器。
相同的命名工具、资源、模板和提示的前缀规则适用于 import_server。这包括为资源和模板的 URI/键和名称添加前缀,以便在多服务器配置中更好地识别。
提示:prefix 参数是可选的。如果省略,组件将不经修改地挂载。
性能考虑
由于"实时链接",父服务器上的操作(如 list_tools())将受到最慢的挂载服务器速度的影响。特别是,基于 HTTP 的挂载服务器可能会引入显著的延迟(300-400ms 对比本地工具的 1-2ms),并且这种减速会影响整个服务器,而不仅仅是与 HTTP 代理工具的交互。如果性能很重要,通过 import_server() 导入工具可能是更合适的解决方案,因为它在启动时复制组件一次,而不是在运行时委托请求。
无前缀挂载
新版本 2.9.0 功能
您也可以挂载服务器而不指定前缀,这使得组件可以在没有前缀的情况下访问。这与无前缀导入的工作方式相同,包括冲突解决。
6.2 直接挂载 vs. 代理挂载
新版本 2.2.7 功能
FastMCP 支持两种挂载模式:
- 直接挂载(默认):父服务器直接访问内存中挂载服务器的对象。
- 挂载服务器上不会发生客户端生命周期事件
- 挂载服务器的生命周期上下文不会执行
- 通信通过直接方法调用处理
- 代理挂载:父服务器将挂载服务器视为独立实体,并通过客户端接口与其通信。
- 挂载服务器上发生完整的客户端生命周期事件
- 当客户端连接时执行挂载服务器的生命周期
- 通信通过内存中的客户端传输进行
python
# 直接挂载(当没有自定义生命周期时默认)
main_mcp.mount(api_server, prefix="api")
# 代理挂载(保留完整的客户端生命周期)
main_mcp.mount(api_server, prefix="api", as_proxy=True)
# 无前缀挂载(组件可以在没有前缀的情况下访问)
main_mcp.mount(api_server)
当挂载服务器具有自定义生命周期时,FastMCP 会自动使用代理挂载,但您可以使用 as_proxy 参数覆盖此行为。
6.3 与代理服务器的交互
当使用 FastMCP.as_proxy() 创建代理服务器时,挂载该服务器将始终使用代理挂载:
python
# 为远程服务器创建代理
remote_proxy = FastMCP.as_proxy(Client("http://example.com/mcp"))
# 挂载代理(始终使用代理挂载)
main_server.mount(remote_proxy, prefix="remote")
七、组合中的标签过滤
新版本 2.9.0 功能
当在父服务器上使用 include_tags 或 exclude_tags 时,这些过滤器递归地应用于所有组件,包括来自挂载或导入服务器的组件。这允许您控制在父级别公开哪些组件,无论您的应用程序是如何组合的。
python
import asyncio
from fastmcp import FastMCP, Client
# 创建具有不同环境标签工具的子服务器
api_server = FastMCP(name="APIServer")
@api_server.tool(tags={"production"})
def prod_endpoint() -> str:
"""生产就绪的端点。"""
return "Production data"
@api_server.tool(tags={"development"})
def dev_endpoint() -> str:
"""仅开发使用的端点。"""
return "Debug data"
# 在父级别使用生产标签过滤挂载子服务器
prod_app = FastMCP(name="ProductionApp", include_tags={"production"})
prod_app.mount(api_server, prefix="api")
# 测试过滤
async def test_filtering():
async with Client(prod_app) as client:
tools = await client.list_tools()
print("Available tools:", [t.name for t in tools])
# 显示:['api_prod_endpoint']
# 'api_dev_endpoint' 被过滤掉
# 调用被过滤的工具会引发错误
try:
await client.call_tool("api_dev_endpoint")
except Exception as e:
print(f"Filtered tool not accessible: {e}")
if __name__ == "__main__":
asyncio.run(test_filtering())
递归过滤的工作原理
标签过滤器按以下顺序应用:
- 子服务器过滤器:每个挂载/导入的服务器首先将其自己的 include_tags/exclude_tags 应用于其组件。
- 父服务器过滤器:然后父服务器将其自己的 include_tags/exclude_tags 应用于所有组件,包括来自子服务器的组件。
这确保了父服务器标签策略作为父服务器公开的所有内容的全局策略,无论您的应用程序是如何组合的。
注意:此过滤适用于列出(例如,list_tools())和执行(例如,call_tool())。过滤的组件在父服务器上既不可见也不可执行。
八、资源前缀格式
新版本 2.4.0 功能
当挂载或导入服务器时,资源 URI 通常会被添加前缀以避免命名冲突。FastMCP 支持两种不同的资源前缀格式:
8.1 路径格式(默认)
在路径格式中,前缀被添加到 URI 的路径部分:
python
resource://prefix/path/to/resource
这是自 FastMCP 2.4 以来的默认格式。推荐使用此格式,因为它避免了 URI 协议限制的问题(如下划线不允许在协议名称中使用)。
8.2 协议格式(传统)
在协议格式中,前缀作为协议的一部分添加:
python
prefix+resource://path/to/resource
这是在 FastMCP 2.4 之前的默认格式。虽然仍受支持,但不推荐用于新代码,因为它可能会导致前缀名称在 URI 协议中无效的问题。
8.3 配置前缀格式
您可以在代码中全局配置前缀格式:
python
import fastmcp
fastmcp.settings.resource_prefix_format = "protocol"
或通过环境变量:
python
FASTMCP_RESOURCE_PREFIX_FORMAT=protocol
或按服务器配置:
python
from fastmcp import FastMCP
# 创建使用传统协议格式的服务器
server = FastMCP("LegacyServer", resource_prefix_format="protocol")
# 创建使用新路径格式的服务器
server = FastMCP("NewServer", resource_prefix_format="path")
当挂载或导入服务器时,使用父服务器的前缀格式。
注意:当挂载服务器时,使用 @server.custom_route() 定义的自定义 HTTP 路由也会转发到父服务器,使它们可以通过父服务器的 HTTP 应用程序访问。