[FastMCP设计、原理与应用-15]挂载一个MCP服务器就像挂载一个目录一样容易

随着应用程序的增长,我们需要将其拆分为多个功能专一的服务器(例如一个用于天气,一个用于日历,一个用于管理后台),并通过挂载(Mount)的方式将它们合并成一个客户端连接的单一服务器。挂载服务器后,其所有工具、资源和提示都会通过父进程变为可用状态。连接是实时的,挂载后向子进程添加工具,该工具会立即在父进程中可见。

1. 挂载一个FastMCP对象

以如下这个演示程序为例,我们创建的名为ServerFastMCP对象通过调用mount方法挂载了两个FastMCP对象:命名为Weather的服务器提供了返回天气信息的工具get_weather,命名为OA的服务器提供的工具is_available用于确定某个人在指定的日志是否由任务安排。

python 复制代码
from fastmcp import FastMCP
from fastmcp.client import Client
from datetime import date
import asyncio

weather = FastMCP("Weather")
@weather.tool()
async def get_weather(location: str) -> str:
    """Get the current weather for a given location."""
    return f"The current weather in {location} is sunny."

oa = FastMCP("OA")
@oa.tool()
async def is_available(name: str, date: date) -> bool:
    """Check if a person is available on a given date."""
    return True

async def main():
    server = FastMCP("Server")
    server.mount(server = weather)
    server.mount(server = oa)
    async with Client(server) as client:
        tools = await client.list_tools()
        tool_names = set([tool.name for tool in tools])
        assert tool_names == {"get_weather", "is_available"}

    server = FastMCP("Server")
    server.mount(server=weather,namespace="weather")
    server.mount(server=oa,namespace="oa")
    async with Client(server) as client:
        tools = await client.list_tools()
        tool_names = set([tool.name for tool in tools])
        assert tool_names == {"weather_get_weather", "oa_is_available"}

asyncio.run(main())  

上面的例子演示了针对mount方法的两次调用,第一次仅仅指定被挂载的FastMCP对象,所以工具名称保持不变。第二次指定了命名空间(weatheros),它会作为工具的名称前缀(weather_get_weatheroa_is_available)。

python 复制代码
class FastMCP(
    AggregateProvider,
    LifespanMixin,
    MCPOperationsMixin,
    TransportMixin,
    Generic[LifespanResultT],
):
    def mount(
        self,
        server: FastMCP[LifespanResultT],
        namespace: str | None = None,
        as_proxy: bool | None = None,
        tool_names: dict[str, str] | None = None,
        prefix: str | None = None, 
    ) -> None

上面给出了mount方法的定义,它的参数说明如下:

  • server:被挂载的作为MCP服务器的FastMCP对象;
  • namespace:命名空间。工具和提示词的名称将以{namespace}_为前缀,资源将其作为前置路径;
  • as_proxy:以代理的形式被挂载,这个参数已经废弃,因为我们倾向于自行创建代理;
  • tool_names: 提供一个字典来修改工具的名称;
  • prefix: 命名前缀,已经废弃,并使用namespace代替;

2. 挂载一个代理

由于mount方法只支持针对FastMCP对象的挂载。对于一个需要通过客户端远程调用MCP服务器,我们可以采用代理的形式进行挂载。作为MCP服务器代理的FastMCPProxy对象之所以能够被挂载,是因为它自身就是一个FastMCP对象。FastMCPProxy对象利用ProxyProvider提供组件,后者又利用Client远程获取组件,构造函数参数client_factory用于提供创建Client的工厂。出于性能的考虑,远程获取的组件会在本地进行缓存,缓存的过期时间默认为300秒。如果手工创建ProxyProvider,可以利用构造函数的cache_ttl参数对缓存过期时间进行设置。

python 复制代码
class FastMCPProxy(FastMCP):
    def __init__(
        self,
        *,
        client_factory: ClientFactoryT,
        **kwargs,
    ):
        super().__init__(**kwargs)
        self.client_factory = client_factory
        provider: Provider = ProxyProvider(client_factory)
        self.add_provider(provider)

class ProxyProvider(Provider):
    def __init__(
        self,
        client_factory: ClientFactoryT,
        cache_ttl: float | None = None,
    )

ClientFactoryT = Callable[[], Client] | Callable[[], Awaitable[Client]]

FastMCPProxy可以通过如下这个全局函数fastmcp.server.create_proxy来创建。

python 复制代码
def create_proxy(
    target: (
        Client[ClientTransportT]
        | ClientTransport
        | FastMCP[Any]
        | FastMCP1Server
        | AnyUrl
        | Path
        | MCPConfig
        | dict[str, Any]
        | str
    ),
    **settings: Any,
) -> FastMCPProxy

如下的程序演示了以代理的形式挂载远程MCP服务器。

python 复制代码
from fastmcp import FastMCP
from fastmcp.client import Client
from fastmcp.server import create_proxy
from datetime import date
import asyncio

server = FastMCP("Server")
@server.tool()
async def get_weather(location: str) -> str:
    """Get the current weather for a given location."""
    return f"The current weather in {location} is sunny."

@server.tool()
async def is_available(name: str, date: date) -> bool:
    """Check if a person is available on a given date."""
    return True

async def main():
    asyncio.create_task(server.run_async(transport="streamable-http", host="0.0.0.0", port=3721))
    await asyncio.sleep(5)  # Wait for the server to start
    
    mcp = FastMCP()
    mcp.mount(create_proxy("http://localhost:3721/mcp"))
    async with Client(mcp) as client:      
        tools = await client.list_tools()
        tool_names = set([tool.name for tool in tools])
        print(f"Tools: {tool_names}")
        assert tool_names == {"get_weather", "is_available"} 

asyncio.run(main())    
相关推荐
从零开始学习人工智能3 小时前
【踩坑实录】WSL2 解决 onnxruntime\-gpu ImportError: libcudart\.so\.13 无 CUDA13 运行库问题
python
鱼日先生4 小时前
Dify 中级实验(17):调试监控与性能优化——响应慢和 Token 超支如何定位?
工作流·dify·ai agent·大模型应用·ai 训练
卷无止境5 小时前
在 awesome-fastapi 里,哪些库值得一看?
后端·python
zhanghaha13145 小时前
Python进阶教程:6_JSON 数据解析 —— 新手完全指南
开发语言·python·json
Python私教5 小时前
API 输出模型怎么设计:从 model_dump() 到显式展示层
python·fastapi
Python私教6 小时前
本地 AI 工具服务该绑定 127.0.0.1 还是 0.0.0.0?
python·fastapi
卷无止境6 小时前
FastAPI 的Admin面板生态
后端·python
小田学Python6 小时前
重新定义 Agent:为什么大模型不能直接干活,需要一层“壳”
大模型·api·ai agent
ctlover6 小时前
Streamlit 框架
python
不吃辣4907 小时前
vibe coding | 如何做一个AI制图小程序?
人工智能·小程序·ai编程