New in version: 2.2.0

随着您的MCP应用程序不断发展,您可能希望将工具、资源和提示组织成逻辑模块,或复用现有的服务器组件。FastMCP通过两种方式支持组合:

  • import_server: 用于带前缀的组件一次性复制(静态组合)。
  • mount: 用于创建实时链接,主服务器将请求委托给子服务器(动态组合)。

为什么要组合服务器?

  • 模块化: 将大型应用拆分为更小、功能专注的服务器(例如WeatherServerDatabaseServerCalendarServer)。
  • 可复用性: 创建通用的工具服务器(例如TextProcessingServer),并在需要的地方挂载它们。
  • 团队协作: 不同团队可以在独立的FastMCP服务器上工作,之后再合并。
  • 组织: 将相关功能按逻辑分组在一起。

导入与挂载

选择导入还是挂载取决于您的使用场景和需求。

功能导入挂载
MethodFastMCP.import_server()FastMCP.mount()
组合类型一次性复制(静态)实时链接(动态)
更新情况子服务器变更不会立即生效子服务器变更立即生效
最佳适用场景打包最终组件模块化运行时组合

代理服务器

FastMCP支持MCP代理功能,允许您在本地FastMCP实例中镜像本地或远程服务器。代理完全兼容导入和挂载两种操作模式。

导入(静态组合)

import_server() 方法将所有组件(工具、资源、模板、提示)从一个 FastMCP 实例(子服务器)复制到另一个实例(主服务器)。添加 prefix 前缀以避免命名冲突。

from fastmcp import FastMCP
import asyncio

# Define subservers
weather_mcp = FastMCP(name="WeatherService")

@weather_mcp.tool()
def get_forecast(city: str) -> dict:
    """Get weather forecast."""
    return {"city": city, "forecast": "Sunny"}

@weather_mcp.resource("data://cities/supported")
def list_supported_cities() -> list[str]:
    """List cities with weather support."""
    return ["London", "Paris", "Tokyo"]

# Define main server
main_mcp = FastMCP(name="MainApp")

# Import subserver
async def setup():
    await main_mcp.import_server("weather", weather_mcp)

# Result: main_mcp now contains prefixed components:
# - Tool: "weather_get_forecast"
# - Resource: "weather+data://cities/supported" 

if __name__ == "__main__":
    asyncio.run(setup())
    main_mcp.run()

导入工作原理

当你调用await main_mcp.import_server(prefix, subserver)时:

  1. Tools: All tools from subserver are added to main_mcp with names prefixed using {prefix}_.
    • subserver.tool(name="my_tool") 变为 main_mcp.tool(name="{prefix}_my_tool")
  2. Resources: All resources are added with URIs prefixed using {prefix}+.
    • subserver.resource(uri="data://info") 变为 main_mcp.resource(uri="{prefix}+data://info")
  3. Resource Templates: Templates are prefixed similarly to resources.
    • subserver.resource(uri="data://{id}") 变为 main_mcp.resource(uri="{prefix}+data://{id}")
  4. Prompts: All prompts are added with names prefixed like tools.
    • subserver.prompt(name="my_prompt") 变为 main_mcp.prompt(name="{prefix}_my_prompt")

请注意,import_server执行的是组件的一次性复制。导入后对subserver所做的更改不会反映在main_mcp中。subserverlifespan上下文也不会由主服务器执行。

挂载(实时链接)

mount()方法在main_mcp服务器和subserver之间创建了一个实时链接。不同于复制组件,运行时对匹配prefix的组件请求会被委派subserver处理。

import asyncio
from fastmcp import FastMCP, Client

# Define subserver
dynamic_mcp = FastMCP(name="DynamicService")

@dynamic_mcp.tool()
def initial_tool():
    """Initial tool demonstration."""
    return "Initial Tool Exists"

# Mount subserver (synchronous operation)
main_mcp = FastMCP(name="MainAppLive")
main_mcp.mount("dynamic", dynamic_mcp)

# Add a tool AFTER mounting - it will be accessible through main_mcp
@dynamic_mcp.tool()
def added_later():
    """Tool added after mounting."""
    return "Tool Added Dynamically!"

# Testing access to mounted tools
async def test_dynamic_mount():
    tools = await main_mcp.get_tools()
    print("Available tools:", list(tools.keys()))
    # Shows: ['dynamic_initial_tool', 'dynamic_added_later']
    
    async with Client(main_mcp) as client:
        result = await client.call_tool("dynamic_added_later")
        print("Result:", result[0].text)
        # Shows: "Tool Added Dynamically!"

if __name__ == "__main__":
    asyncio.run(test_dynamic_mount())

挂载机制工作原理

当配置挂载时:

  1. 实时链接: 父服务器与挂载服务器建立连接。
  2. 动态更新: 通过父级访问时,挂载服务器的变更会立即反映出来。
  3. 前缀访问:父服务器使用前缀将请求路由到挂载的服务器。
  4. 委托: 运行时将匹配前缀的组件请求委托给已挂载的服务器。

import_server相同的命名前缀规则适用于工具、资源、模板和提示词。

直接挂载 vs 代理挂载

New in version: 2.2.7

FastMCP支持两种挂载模式:

  1. Direct Mounting (default): The parent server directly accesses the mounted server’s objects in memory.
    • 已挂载服务器上不会发生客户端生命周期事件
    • 挂载服务器的生命周期上下文未执行
    • 通信通过直接方法调用处理
  2. Proxy Mounting: The parent server treats the mounted server as a separate entity and communicates with it through a client interface.
    • 完整的客户端生命周期事件发生在挂载的服务器上
    • 挂载服务器的生命周期在客户端连接时执行
    • 通信通过内存中的Client传输实现
# Direct mounting (default when no custom lifespan)
main_mcp.mount("api", api_server)

# Proxy mounting (preserves full client lifecycle)
main_mcp.mount("api", api_server, as_proxy=True)

当挂载的服务器具有自定义生命周期时,FastMCP会自动使用代理挂载,但您可以通过as_proxy参数覆盖此行为。

与代理服务器的交互

当使用FastMCP.from_client()创建代理服务器时,挂载该服务器将始终使用代理挂载方式:

# Create a proxy for a remote server
remote_proxy = FastMCP.from_client(Client("http://example.com/mcp"))

# Mount the proxy (always uses proxy mounting)
main_server.mount("remote", remote_proxy)

自定义分隔符

无论是import_server()还是mount(),都允许您自定义用于组件前缀的分隔符。默认情况下,工具和提示使用_,资源使用+

await main_mcp.import_server(
    prefix="api",
    app=some_subserver,
    tool_separator="_",       # Tool name becomes: "api_sub_tool_name"
    resource_separator="+",   # Resource URI becomes: "api+data://sub_resource"
    prompt_separator="_"      # Prompt name becomes: "api_sub_prompt_name"
)

在选择分隔符时需谨慎。某些MCP客户端(如Claude Desktop)可能对工具名称中允许使用的字符有限制(例如,可能不支持/)。默认设置(名称使用_,URI使用+)通常是安全的。

要"干净地"导入或挂载服务器,请将前缀和所有分隔符设置为""(空字符串)。这通常是不必要的,但可以节省几个token,代价是可能发生名称冲突!