[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())    
相关推荐
X5661几秒前
什么是Bootstrap的移动优先响应式设计
jvm·数据库·python
m0_47085764几秒前
实现一个可精确定位、支持左右移动与删除的文本光标系统
jvm·数据库·python
m0_591364733 分钟前
mysql如何通过索引减少行锁范围_mysql索引与加锁逻辑
jvm·数据库·python
川冰ICE4 分钟前
Python爬虫实战⑲|Pandas数据合并与重塑,多数据源整合
爬虫·python·pandas
acanab6 分钟前
isaaclab资产打包的一种方式
vscode·python·机器人·isaac lab
Be reborn8 分钟前
从一行 CSV 到一次浏览器操作:关键字驱动执行引擎设计
python·自动化·pytest
青衫码上行9 分钟前
如何接入AI大模型
java·人工智能·ai·langchain·ai编程
创意岛10 分钟前
AI时代,你的品牌在城市发展中“被消失”了吗?
人工智能·python
weixin_4440129314 分钟前
CSS如何实现单选按钮自定义样式_利用伪元素隐藏默认UI
jvm·数据库·python
X566115 分钟前
CSS如何利用Grid重写老旧的表格布局
jvm·数据库·python