跳到主要内容

第 4 章:Provider 抽象与服务器组合

本章讲 FastMCP 的「可扩展性内核」:一个叫 Provider 的统一抽象,让「工具从哪来」变成可插拔的。挂载别的服务器、代理远端、从 OpenAPI 生成——底下都是同一套 provider 机制。

4.1 核心抽象:Provider = 「组件的来源」

把问题倒过来想:FastMCP 需要回答「我有哪些工具/资源/提示」。这些组件不一定都来自装饰器——可能来自:

  • 装饰器注册的本地组件;
  • 挂载的另一台 FastMCP 服务器;
  • 一个远端 MCP 服务器(代理);
  • 一份 OpenAPI 规范转出来的工具;
  • 文件系统扫描出来的组件;
  • 你自己写的动态来源(比如从数据库读工具定义)。

FastMCP 把「组件的来源」统一抽象成 Provider(server/providers/base.py:57)。它的契约很干净(见类 docstring):

方法语义
list_tools() / list_resources() / list_prompts()「我能提供哪些」;出错就返回空(优雅降级,不拖垮别人)
get_tool(name)「我有没有这个」——返回 None 表示「我没有,继续问下一个」

两条铁律(providers/base.py:64 的 docstring):

  1. 静态组件(装饰器注册的)永远优先于 provider;
  2. 多个 provider 按注册顺序查,第一个返回非 None 的赢。

4.2 FastMCP 自己就是个聚合 provider

回忆第 1 章:class FastMCP(AggregateProvider, ...)AggregateProvider(server/providers/aggregate.py:47)是「把多个子 provider 的结果合并起来」的 provider。它内部持有:

  • 一个 LocalProvider(server/providers/local_provider/local_provider.py:51)装所有装饰器注册的组件;
  • 一列外部 provider(通过 add_provider 加进来,server/server.py:530)。

所以「FastMCP 有哪些工具」= 聚合(本地注册表 + 所有外部 provider),按优先级和顺序合并去重。这解释了第 3 章的 get_tool 为什么要「逐个 provider 问」。

FastMCP (AggregateProvider)
├── LocalProvider ← @mcp.tool 注册的(最高优先级)
├── FastMCPProvider ← mount 进来的另一台服务器
├── ProxyProvider ← as_proxy 的远端服务器
└── OpenAPIProvider ← from_openapi 生成的

4.3 变换(Transform):给组件「过一层加工」

组件从 provider 流出来时可以被 Transform 加工(server/providers/base.py:86add_transform)。最常用的是 Namespace(命名空间前缀):

# 示意:给一个 provider 的所有工具加前缀
provider.add_transform(Namespace("api"))
# 工具 get_weather → api_get_weather

还有个不可变版本 wrap_transform(providers/base.py:106),返回一个包了变换的 provider 而不改原来的——这样同一个 provider 能以不同命名空间挂到多个地方而互不干扰。

为什么需要命名空间: 挂载两台都有 search 工具的服务器时,不加前缀就会撞名。加上 api_search / db_search 就区分开了。

4.4 三种组合方式

mount:动态挂载(实时转发)

server/server.py:2124(def mount)。把另一台 FastMCP 动态挂上来:客户端访问挂载服务器的工具时,请求实时转发过去。挂载服务器之后的改动会立即反映。带命名空间时:工具变 namespace_toolname,资源变 weather://namespace/path。底层就是往聚合里加一个 FastMCPProvider(server/providers/fastmcp_provider.py)。挂载服务器的 lifespan 和中间件链都会被父服务器启动/调用。

import_server:静态导入(拷贝一份)

server/server.py:2230(async def import_server)。与 mount 相对:它把另一台服务器的组件当场拷贝进本地注册表(server/server.py:2287for tool in await server.list_tools())。之后两者不再联动——是一次性快照。

mount vs import_server 一句话: mount 是「实时链接」(改了会同步),import 是「拍照拷贝」(改了不同步)。

as_proxy:代理远端服务器

server/server.py:2436(def as_proxy)。把一个远端 MCP 服务器(URL / 另一个 Client / 配置)包成本地的 FastMCP 门面。你的本地服务器成了远端的透明代理,返回 FastMCPProxy。用途:给远端服务器加中间件、改命名、聚合多个远端。

from_openapi / from_fastapi:从 HTTP API 生成

server/server.py:2330(def from_openapi)。喂一份 OpenAPI 规范 + 一个 httpx.AsyncClient,自动把每个 HTTP 端点变成一个 MCP 工具或资源。靠 route_maps(RouteMap 列表)决定哪个端点映射成工具、哪个映射成资源模板。from_fastapi(:2381)是它的便捷封装——直接吃一个 FastAPI 应用(FastAPI 本身能导出 OpenAPI)。

回到第 1 章的类比: FastMCP 之于 MCP 就像 FastAPI 之于 HTTP。from_fastapi 把这个类比闭环了——一个 FastAPI 应用可以「原地」变成 MCP 服务器。

4.5 巧妙之处

  • 一套抽象吃掉所有扩展点。 本地工具、挂载、代理、OpenAPI 生成、文件系统发现——表面是五种功能,底下都是「往 AggregateProvider 加一个 Provider + 叠一层 Transform」。想加新来源,只要实现 Provider 接口。
  • get_* 返回 None = 责任链模式。 每个 provider 只管「我有没有」,不知道也不关心别人,聚合层负责按序问。这让 provider 之间零耦合。
  • 优雅降级写进契约。 list_* 出错返回空而不是抛异常(providers/base.py:71 docstring),所以一个坏掉的远端代理不会让整个 tools/list 挂掉,其它 provider 照常贡献。

代码地图

主题文件路径符号名
Provider 抽象契约fastmcp_slim/fastmcp/server/providers/base.pyProviderlist_toolsget_tool
聚合 providerfastmcp_slim/fastmcp/server/providers/aggregate.pyAggregateProvider
本地注册表fastmcp_slim/fastmcp/server/providers/local_provider/local_provider.pyLocalProvider
加 providerfastmcp_slim/fastmcp/server/server.pyFastMCP.add_provider
变换 / 命名空间fastmcp_slim/fastmcp/server/providers/base.pyadd_transformwrap_transform
动态挂载fastmcp_slim/fastmcp/server/server.pyFastMCP.mount
静态导入fastmcp_slim/fastmcp/server/server.pyFastMCP.import_server
代理fastmcp_slim/fastmcp/server/server.pyFastMCP.as_proxy
从 OpenAPI/FastAPI 生成fastmcp_slim/fastmcp/server/server.pyFastMCP.from_openapiFastMCP.from_fastapi