第 1 章:服务器对象与三个装饰器
本章讲清:
FastMCP这个对象是怎么拼出来的,以及@mcp.tool/@mcp.resource/@mcp.prompt三个装饰器在注册期到底做了什么。看完你会知道「装饰器不是魔法,只是一条流水线」。
1.1 FastMCP 是什么组成的
FastMCP 不是一个从零写的大类,而是多个能力拼装出来的门面(server/server.py:314):
class FastMCP(
AggregateProvider, # 组件注册表 + 从多个来源聚合工具/资源/提示
LifespanMixin, # 启动/关闭生命周期
MCPOperationsMixin, # 把 tools/list、tools/call 等接到低层 SDK
TransportMixin, # run / run_stdio / run_http
Generic[LifespanResultT],
):
这个继承表就是一张「FastMCP 会做哪几件事」的目录:
| 基类 | 带来的能力 | 定义处 |
|---|---|---|
AggregateProvider | 「我有哪些工具/资源/提示」——聚合本地注册表 + 外部 providers | server/providers/aggregate.py:47 |
LifespanMixin | lifespan 上下文:启动时初始化、关闭时清理 | server/mixins/lifespan.py |
MCPOperationsMixin | 把 MCP 协议方法接到官方 SDK 的低层服务器 | server/mixins/mcp_operations.py |
TransportMixin | run() / run_stdio_async() / run_http_async() | server/mixins/transport.py |
构造函数在 server/server.py:321(def __init__),关键一步是建了个官方 SDK 的低层服务器实例:self._mcp_server = LowLevelServer[...](server/server.py:412),并在末尾调 self._setup_handlers()(server/server.py:458)把协议处理函数接上去。
直觉:
FastMCP是「用户友好的外壳」,LowLevelServer(官方 MCP SDK)是「真正收发 JSON-RPC 的引擎」。FastMCP 负责把你的函数翻译成引擎认识的处理函数。
1.2 组件的共同基类:FastMCPComponent
工具、资源、资源模板、提示——四种东西共享一个基类 FastMCPComponent(utilities/components.py:74)。它规定了每个组件都有:
name/version/title/description/icons/tags/meta(都是 pydantic 字段);enabled开关,配enable()/disable()(utilities/components.py:209、:216)——可以运行时下线一个工具;- 一个带类型前缀的
key(utilities/components.py:139),形如tool:add、resource:weather://x,用来在注册表里避免不同类型重名撞车(make_key在:130,前缀由子类KEY_PREFIX定义)。
为什么要类型前缀 key: 工具叫 add、资源 URI 也可能叫 add,如果用裸名字做注册表 key 就会撞。KEY_PREFIX 把它们隔到 tool:add 和 resource:add 两个命名空间里。
1.3 @mcp.tool 做了什么(注册期流水线)
@mcp.tool 定义在 server/server.py:1737(def tool)。它支持五种调用写法(@tool、@tool()、@tool("name")、@tool(name=...)、tool(fn, name=...)),但核心逻辑很短——它自己几乎不干活,而是委托给 LocalProvider:
# server/server.py:1737 def tool —— 简化后的核心
def tool(self, name_or_fn=None, *, name=None, ...):
# (先把 app/ui 配置塞进 meta,略)
result = self._local_provider.tool( # ← 委托给本地注册表
name_or_fn, name=name, version=version, ...
)
return result
真正的三步流水线发生在下游:
@mcp.tool
│
▼
① FunctionTool.from_function(fn) 解析函数 → 建 Tool 对象
│ └─ 内部调 ParsedFunction.from_function() 抽 schema(见第 2 章)
▼
② LocalProvider._add_component(tool) 存进注册表(按 key 去重/版本检查)
│
▼
③ 返回 FunctionTool(不是原函数!)
- 第 ① 步在
tools/function_tool.py:211(FunctionTool.from_function):把函数变成一个FunctionTool运行时对象,顺带生成输入/输出 schema。这是第 2 章的主题。 - 第 ② 步在
server/providers/local_provider/local_provider.py:178(_add_component):存进内部字典,并做版本混用检查(_check_version_mixing,:134)——同一个名字要么全带版本、要么全不带,不能混。 - 第 ③ 步:装饰器返回的是
FunctionTool实例,不是你原来的函数。所以add这个名字之后指向的是工具对象,而工具对象仍可当函数调用(FunctionTool实现了__call__,见tools/function_tool.py:134的DecoratedTool协议)。
关键细节: 装饰器「吃掉」你的函数换成工具对象,这也是为什么给 lambda 注册工具会报错——
from_function里显式检查func_name == "<lambda>"并要求你显式起名(tools/function_tool.py:317)。
1.4 @mcp.resource 与「资源 vs 资源模板」
资源装饰器在 server/server.py:1862(def resource)。资源是「模型能读的数据」(相当于 GET 端点)。这里有个巧妙的自动分流:
- URI 里没有
{参数}→ 生成一个静态FunctionResource(固定内容); - URI 里带
{参数}(如weather://{city}/current)→ 生成一个ResourceTemplate(参数化,按需实例化)。
分流逻辑在 resources/function_resource.py:278(create_resource)。模板的 URI 匹配靠 resources/template.py:233(matches)→ match_uri_template(resources/template.py:80),它把 weather://{city}/current 编译成正则去匹配来访 URI 并抽出 city 参数。
类比: 资源模板之于资源,就像 Web 框架里
/users/{id}之于/about——一个是带路径参数的动态路由,一个是静态页面。
1.5 @mcp.prompt 简述
提示装饰器在 server/server.py:1993(def prompt)。「提示(prompt)」是 MCP 里可复用的消息模板:函数返回一段(或多段)消息,客户端可以把它渲染出来喂给模型。机制和工具同构——都走「解析函数 → 建组件 → 存 LocalProvider」,只是产物是 Prompt 而非 Tool。