深入 FastMCP 源码:认识 tool()、resource() 和 prompt() 装饰器
在使用 FastMCP 开发 MCP 服务器时经常会用到 @mcp.tool()
等装饰器。虽然它们用起来很简单,但当作黑匣子 总让人感觉"不得劲"。接下来我们将深入相关的源码实现,别担心,不会钻没有意义的“兔子洞 ”,你可以通过这篇文章了解到:
如何简单启动本地的 MCP Server 和 MCP Inspector
这些装饰器具体做了什么
@mcp.tool()
@mcp.resource()
@mcp.prompt()
MCP 官方 Python SDK 地址:https://github.com/modelcontextprotocol/python-sdk 。
代码文件下载 :server.py ,debug_func_metadata.py
安装库
需要注意的是,Python>=3.10 才可以安装 MCP:
下面是一个简化的 server.py 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 from mcp.server.fastmcp import FastMCPmcp = FastMCP( name="weather" , ) @mcp.tool() def get_weather (city: str ) -> str : """获取指定城市的天气信息""" weather_data = { "北京" : "晴天,温度 22°C" , "上海" : "多云,温度 25°C" , "广州" : "小雨,温度 28°C" , "深圳" : "阴天,温度 26°C" } return weather_data.get(city, f"{city} 的天气数据暂不可用" ) @mcp.prompt() def weather (city: str = "北京" ) -> list : """提供天气查询的对话模板""" return [ { "role" : "user" , "content" : f"请帮我查询{city} 的天气情况,并提供详细的天气信息。" } ] @mcp.resource("resource://cities" ) def get_cities (): """返回支持查询天气的城市列表""" cities = ["北京" , "上海" , "广州" , "深圳" ] return f"Cities: {', ' .join(cities)} " @mcp.resource("resource://{city}/weather" ) def get_city_weather (city: str ) -> str : return f"Weather for {city} " if __name__ == "__main__" : mcp.run(transport="stdio" )
将其保存为 server 后,可以使用以下命令直接进行调试:
mcp dev
会在运行MCP服务器的同时启动 MCP Inspector:
MCP Inspector 界面配置如下图左框:
配置项(Command + Arguments)实际对应于能够运行服务器的命令,所以并不局限,有很多组合可以使用:
最后三行命令实际只是 uv 对前两行命令的封装(uv 可以替代 pip/conda,目前已经被广泛使用)。
连接成功后,你可以在 MCP Inspector 中看到注册的 Resources、Prompts 和 Tools:
什么是 FastMCP?
官方仓库中对应的路径为 src/mcp/server/fastmcp :
从 from mcp.server.fastmcp import FastMCP
开始,既然能够直接 import FastMCP,那先查看 __init__.py
:
1 2 3 4 5 6 7 8 9 """FastMCP - 一个更人性化的 MCP 服务器接口。""" from importlib.metadata import versionfrom .server import Context, FastMCPfrom .utilities.types import Image__version__ = version("mcp" ) __all__ = ["FastMCP" , "Context" , "Image" ]
可以看到 FastMCP 是从当前文件夹的 server.py
中导入的,所以接下来查看 server.py (省略部分初始化逻辑):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class FastMCP : def __init__ ( self, name: str | None = None , instructions: str | None = None , auth_server_provider: OAuthAuthorizationServerProvider[Any , Any , Any ] | None = None , token_verifier: TokenVerifier | None = None , event_store: EventStore | None = None , *, tools: list [Tool] | None = None , **settings: Any , ): ... self ._tool_manager = ToolManager(tools=tools, warn_on_duplicate_tools=self .settings.warn_on_duplicate_tools) self ._resource_manager = ResourceManager(warn_on_duplicate_resources=self .settings.warn_on_duplicate_resources) self ._prompt_manager = PromptManager(warn_on_duplicate_prompts=self .settings.warn_on_duplicate_prompts) ...
初始化(__init__
)的代码中有三个很眼熟的部分:
_tool_manager
:管理工具(Tools)
_resource_manager
:管理资源(Resources)
_prompt_manager
:管理提示词(Prompts)
这三者分别对应于之后要介绍的装饰器。
FastMCP 初始化的具体解析不会在本文进行,后续相关文章完结时此行会替换为索引链接。
装饰器
什么是装饰器?
装饰器可以理解为一个接受函数作为参数,并返回一个新函数的函数。这可以让我们在不修改原函数 代码的情况下添加通用的行为,通过一个简单的例子来理解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def decorator (func ): """一个简单的装饰器示例""" def wrapper (*args, **kwargs ): print (f"调用函数 {func.__name__} 之前" ) result = func(*args, **kwargs) print (f"调用函数 {func.__name__} 之后\n" ) return result return wrapper @decorator def say_hello (name ): print (f"Hello, {name} !" ) say_hello("Xiaoming" ) def say_hello (name ): print (f"Hello, {name} !" ) say_hello = decorator(say_hello) say_hello("Xiaoming" )
输出 :
1 2 3 4 5 6 7 调用函数 say_hello 之前 Hello, Xiaoming! 调用函数 say_hello 之后 调用函数 say_hello 之前 Hello, Xiaoming! 调用函数 say_hello 之后
在 FastMCP 中,装饰器的工作方式类似,但并不是简单地 print,而是将函数注册到对应的管理器中:
@mcp.tool()
- 将函数注册为工具
@mcp.resource()
- 将函数注册为资源
@mcp.prompt()
- 将函数注册为提示词模板
接下来会着重讲解 tool() 装饰器,resource() 和 prompt() 的处理逻辑基本是 tool() 的简化版本,所以部分逻辑会带过。
[!note]
使用 @mcp.*
的格式是因为初始化 mcp=FastMCP()
,如果变量名从 mcp
改为了 server
,即:server=FastMCP()
,那么装饰器就应该使用 @server.*
的格式。
@mcp.tool()
装饰器用于将 Python 函数自动注册为当前 mcp
服务器中的工具。摘选之前的片段:
1 2 3 4 5 6 7 8 9 10 11 @mcp.tool() def get_weather (city: str ) -> str : """获取指定城市的天气信息""" weather_data = { "北京" : "晴天,温度 22°C" , "上海" : "多云,温度 25°C" , "广州" : "小雨,温度 28°C" , "深圳" : "阴天,温度 26°C" } return weather_data.get(city, f"{city} 的天气数据暂不可用" )
这段代码实际上会:
自动提取函数的参数类型信息,以及文档字符串(下面的信息由之后的 debug_func_metadata 打印)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ============================================================ 开始解析函数: get_weather 文档字符串: 获取指定城市的天气信息 ============================================================ 函数签名分析: 完整签名: (city: str) -> str 返回类型: <class 'str'> 参数数量: 1 参数名: city 原始注解: <class 'str'> 参数种类: POSITIONAL_OR_KEYWORD 默认值: <class 'inspect._empty'> 类型化注解: <class 'str'> 字段信息: annotation=<class 'str'>, default=PydanticUndefined
生成参数的 JSON Schema(函数名+Arguments)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ️ 创建 Pydantic 模型: 模型名称: get_weatherArguments 基类: <class 'mcp.server.fastmcp.utilities.func_metadata.ArgModelBase'> ✅ 模型创建成功: <class '__main__.get_weatherArguments'> get_weatherArguments JSON Schema: { "properties": { "city": { "title": "City", "type": "string" } }, "required": [ "city" ], "title": "get_weatherArguments", "type": "object" }
将函数注册为 MCP 工具(self._tools[tool.name] = tool
)。
mcp.tool()
可以接受以下参数(此处参数解释参考 tool 和 func_metadata ):
name : 可选的工具名称,默认为函数名
title : 可选的工具标题(用于人类阅读)
description : 可选的工具功能描述,默认使用函数的文档字符串
annotations : 可选的 ToolAnnotations,提供额外的工具信息
structured_output :控制工具输出是结构化还是非结构化的
None
: 基于函数的返回类型注解自动检测
True
: 无条件创建结构化工具(在返回类型注解允许的情况下)
如果是结构化,会根据函数的返回类型注释创建 Pydantic 模型。支持各种返回类型:
BaseModel 子类(直接使用)
原始类型(str、int、float、bool、bytes、None)- 包装在带有 ‘result’ 字段的模型中
TypedDict - 转换为具有相同字段的 Pydantic 模型
数据类和其他带注释的类 - 转换为 Pydantic 模型
泛型类型(list、dict、Union 等)- 包装在带有 ‘result’ 字段的模型中
False
: 无条件创建非结构化工具
[!note]
如果你只想了解如何使用 @mcp.tool()
,可以跳过下面的源码部分。
追溯源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class FastMCP : ... def tool ( self, name: str | None = None , title: str | None = None , description: str | None = None , annotations: ToolAnnotations | None = None , structured_output: bool | None = None , ) -> Callable [[AnyFunction], AnyFunction]: """用于注册工具的装饰器。 工具可以通过添加 Context 类型注解的参数来可选地请求一个 Context 对象。 Context 提供对 MCP 功能的访问,包括日志记录、进度报告和资源访问。 """ if callable (name): raise TypeError( "The @tool decorator was used incorrectly. Did you forget to call it? Use @tool() instead of @tool" ) def decorator (fn: AnyFunction ) -> AnyFunction: self .add_tool( fn, name=name, title=title, description=description, annotations=annotations, structured_output=structured_output, ) return fn return decorator
这里的 self.add_tool()
就是 mcp.add_tool()
,所以也可以不使用装饰器达到一样的目的:
1 2 3 4 5 def get_weather (): pass mcp = FastMCP(name="weather" ) mcp.add_tool(get_weather)
这一行为最终调用的是self._tool_manager.add_tool()
,self._tool_manager
在__init__()
中对应的是 tools/tool_manager.py
中的 ToolManager
类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class ToolManager : """管理 FastMCP 工具.""" def __init__ ( self, warn_on_duplicate_tools: bool = True , *, tools: list [Tool] | None = None , ): self ._tools: dict [str , Tool] = {} if tools is not None : for tool in tools: if warn_on_duplicate_tools and tool.name in self ._tools: logger.warning(f"Tool already exists: {tool.name} " ) self ._tools[tool.name] = tool self .warn_on_duplicate_tools = warn_on_duplicate_tools def add_tool ( self, fn: Callable [..., Any ], name: str | None = None , title: str | None = None , description: str | None = None , annotations: ToolAnnotations | None = None , structured_output: bool | None = None , ) -> Tool: """添加 tool 到 server。""" tool = Tool.from_function( fn, name=name, title=title, description=description, annotations=annotations, structured_output=structured_output, ) existing = self ._tools.get(tool.name) if existing: if self .warn_on_duplicate_tools: logger.warning(f"Tool already exists: {tool.name} " ) return existing self ._tools[tool.name] = tool return tool
我们不需要关注 ToolManager 是怎么进行管理的,这不重要,重要的是装饰器怎么处理我们自定义的函数。
整个 @mcp.tool()
装饰器的工作按执行顺序可以拆分为:
装饰器检查调用方式是否正确(必须带括号),然后将被装饰的函数传递给 ToolManager.add_tool()
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class FastMCP : ... def tool (self, name, ... ): if callable (name): raise TypeError( "The @tool decorator was used incorrectly. Did you forget to call it? Use @tool() instead of @tool" ) def decorator (fn: AnyFunction ) -> AnyFunction: self .add_tool( fn, name=name, title=title, description=description, annotations=annotations, structured_output=structured_output, ) return fn return decorator
2.Tool.from_function()
处理函数元数据 。
1 2 3 4 5 6 7 8 9 10 11 12 class ToolManager : ... def add_tool (self, ... ): tool = Tool.from_function( fn, name=name, title=title, description=description, annotations=annotations, structured_output=structured_output, ) ...
[!note]
tool = Tool.from_function(...)
是当前最重要的处理部分,对应代码位于 tools/base.py ,其主要步骤如下(代码按顺序拼接等价于 Tool.from_function() ,出于讲解目的将其进行了拆分):
a. 解析函数签名,提取参数信息 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Tool (BaseModel ):... @classmethod def from_function ( cls, fn: Callable [..., Any ], name: str | None = None , title: str | None = None , description: str | None = None , context_kwarg: str | None = None , annotations: ToolAnnotations | None = None , structured_output: bool | None = None , ) -> Tool: """从函数创建工具.""" from mcp.server.fastmcp.server import Context func_name = name or fn.__name__ if func_name == "<lambda>" : raise ValueError("You must provide a name for lambda functions" ) func_doc = description or fn.__doc__ or "" is_async = _is_async_callable(fn) ...
b. 自动检测 Context 参数 , inspect.signature()
会遍历函数的所有参数,检查参数类型是否为 Context 的子类,如果是的话会记录为 context_kwarg
,这个参数会被传入 func_metadata()
的 skip_names
(可以跳过这一步的理解,等真正涉及到的时候再探究),不会出现在工具对应的 JSON Schema 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Tool (BaseModel ):... @classmethod def from_function (... ): ... if context_kwarg is None : sig = inspect.signature(fn) for param_name, param in sig.parameters.items(): if get_origin(param.annotation) is not None : continue if issubclass (param.annotation, Context): context_kwarg = param_name break
c. 生成参数的 JSON Schema :
1 2 3 4 5 6 7 8 9 10 11 12 13 class Tool (BaseModel ):... @classmethod def from_function (... ): ... func_arg_metadata = func_metadata( fn, skip_names=[context_kwarg] if context_kwarg is not None else [], structured_output=structured_output, ) parameters = func_arg_metadata.arg_model.model_json_schema(by_alias=True )
func_metadata
相关源代码 位于 utilities/func_metadata.py ,这里我们进行主体逻辑的抽取打印(完整 debug_func_metadata 函数见[附录](file:///Users/home/Downloads/agent/blog版/深入 FastMCP 源码:认识 tool()、resource() 和 prompt() 装饰器 (copy).md#附录)):
1 2 3 4 5 6 7 8 9 10 def func ( data, format : str = "json" , count: Optional [int ] = None , validate: bool = True ): """展示各种注解情况""" return data debug_func_metadata(func, skip_names="count" )
输出 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 ============================================================ 开始解析函数: func 文档字符串: 展示各种注解情况 ============================================================ 函数签名分析: 完整签名: (data, format: str = 'json', count: Optional[ int] = None, validate: bool = True) 返回类型: <class 'inspect._empty'> 参数数量: 4 参数处理详情: 跳过的参数名: [ 'c', 'o', 'u', 'n', 't'] [ 1 ] 参数名: data 原始注解: <class 'inspect._empty'> 参数种类: POSITIONAL_OR_KEYWORD 默认值: <class 'inspect._empty'> ⚠️ 处理: 无类型注解,默认为 Any 类型化注解: typing.Annotated[ typing.Any, FieldInfo(annotation=NoneType, required=True), WithJsonSchema(json_schema={ 'title': 'data', 'type': 'string'} , mode=None)] ✅ 字段信息: annotation=typing.Any, default=PydanticUndefined [ 2 ] 参数名: format 原始注解: <class 'str'> 参数种类: POSITIONAL_OR_KEYWORD 默认值: json 类型化注解: <class 'str'> ✅ 字段信息: annotation=<class 'str'>, default=json [ 3 ] 参数名: count 原始注解: typing.Optional[ int] 参数种类: POSITIONAL_OR_KEYWORD 默认值: None ⏭️ 跳过此参数 [ 4 ] 参数名: validate 原始注解: <class 'bool'> 参数种类: POSITIONAL_OR_KEYWORD 默认值: True 类型化注解: <class 'bool'> ✅ 字段信息: annotation=<class 'bool'>, default=True ⚠️ 冲突处理: 参数名 'validate' 与 BaseModel 方法冲突 -> 使用内部名称: field_validate 参数处理总结: 总参数数: 4 处理参数数: 3 模型字段: [ 'data', 'format', 'field_validate'] ️ 创建 Pydantic 模型: 模型名称: funcArguments 基类: <class 'mcp.server.fastmcp.utilities.func_metadata.ArgModelBase'> ✅ 模型创建成功: <class '__main__.funcArguments'> funcArguments JSON Schema: { "properties" : { "data" : { "title" : "data" , "type" : "string" } , "format" : { "default" : "json" , "title" : "Format" , "type" : "string" } , "validate" : { "default" : true , "title" : "Validate" , "type" : "boolean" } } , "required" : [ "data" ] , "title" : "funcArguments" , "type" : "object" } 返回值处理: structured_output 参数: None 返回注解: <class 'inspect._empty'> 经过_get_typed_annotation处理后的类型: <class 'inspect._empty'> ℹ️ 未创建输出模型 wrap_output: False ✨ func_metadata 处理完成! 最终结果: arg_model=<class '__main__.funcArguments'> output_schema=None output_model=None wrap_output=False ============================================================ FuncMetadata(arg_model=<class '__main__.funcArguments'>, output_schema=None, output_model=None, wrap_output=False)
d. 创建 Tool 实例(Tool.from_function
的 return cls(...)
) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Tool (BaseModel ):... @classmethod def from_function (... ): return cls( fn=fn, name=func_name, title=title, description=func_doc, parameters=parameters, fn_metadata=func_arg_metadata, is_async=is_async, context_kwarg=context_kwarg, annotations=annotations, )
将 Tool 实例注册到工具管理器中 。
1 2 3 4 5 6 7 8 9 10 11 12 class ToolManager : ... def add_tool (... ) -> Tool: """添加 tool 到 server。""" tool = Tool.from_function(...) existing = self ._tools.get(tool.name) if existing: if self .warn_on_duplicate_tools: logger.warning(f"Tool already exists: {tool.name} " ) return existing self ._tools[tool.name] = tool return tool
@mcp.resource()
@mcp.resource()
装饰器用于定义可供访问的资源,需要注意的是:
必须 提供一个资源 URI(如 @mcp.resource("resource://cities")
)
资源可以是静态的(每次调用返回相同内容)或动态的(根据参数填充内容)。
静态对应于 MCP Inspector 中的 Resources
,动态对应于 Resources Templates
,以下面两个资源为例进行展示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from mcp.server.fastmcp import FastMCPmcp = FastMCP("cities" ) @mcp.resource("resource://cities" ) def get_cities (): """返回支持查询天气的城市列表""" cities = ["北京" , "上海" , "广州" , "深圳" ] return f"Cities: {', ' .join(cities)} " @mcp.resource("resource://{city}/weather" ) def get_city_weather (city: str ) -> str : return f"Weather for {city} " if __name__ == "__main__" : mcp.run(transport="stdio" )
此时 MCP Inspector 的 Resources 模块显示如下:
追溯源码
查看 server.py 中的 resource
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 class FastMCP : ... def resource ( self, uri: str , *, name: str | None = None , title: str | None = None , description: str | None = None , mime_type: str | None = None , ) -> Callable [[AnyFunction], AnyFunction]: """用于将函数注册为资源的装饰器。 当资源被读取时,将调用被装饰的函数来动态生成资源内容。 函数可以返回: - str: 文本内容 - bytes: 二进制内容 - 其他类型: 将自动转换为 JSON 格式 如果 URI 包含参数占位符(如 "resource://{param}")或者函数本身有参数, 该资源将被注册为模板资源。 参数: uri: 资源的 URI(如 "resource://my-resource" 或 "resource://{param}") name: 可选的资源名称 title: 可选的资源标题(用于人类阅读) description: 可选的资源描述 mime_type: 可选的 MIME 类型 使用示例: # 静态资源 @server.resource("resource://my-resource") def get_data() -> str: return "Hello, world!" # 参数化模板资源 @server.resource("resource://{city}/weather") def get_weather(city: str) -> str: return f"Weather for {city}" """ if callable (uri): raise TypeError( "The @resource decorator was used incorrectly. Did you forget to call it? Use @resource('uri') instead of @resource" ) def decorator (fn: AnyFunction ) -> AnyFunction: has_uri_params = "{" in uri and "}" in uri has_func_params = bool (inspect.signature(fn).parameters) if has_uri_params or has_func_params: uri_params = set (re.findall(r"{(\w+)}" , uri)) func_params = set (inspect.signature(fn).parameters.keys()) if uri_params != func_params: raise ValueError( f"Mismatch between URI parameters {uri_params} and function parameters {func_params} " ) self ._resource_manager.add_template( fn=fn, uri_template=uri, name=name, title=title, description=description, mime_type=mime_type, ) else : resource = FunctionResource.from_function( fn=fn, uri=uri, name=name, title=title, description=description, mime_type=mime_type, ) self .add_resource(resource) return fn return decorator
ResourceManager
的实现位于 resources/resource_manager.py :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class ResourceManager : """管理 FastMCP 资源。""" def __init__ (self, warn_on_duplicate_resources: bool = True ): self ._resources: dict [str , Resource] = {} self ._templates: dict [str , ResourceTemplate] = {} self .warn_on_duplicate_resources = warn_on_duplicate_resources def add_resource (self, resource: Resource ) -> Resource: """向管理器中添加资源。 参数: resource: 要添加的 Resource 实例 返回: 当前添加的资源。如果具有相同 URI 的资源已存在,则返回现有的资源。 """ logger.debug( "Adding resource" , extra={ "uri" : resource.uri, "type" : type (resource).__name__, "resource_name" : resource.name, }, ) existing = self ._resources.get(str (resource.uri)) if existing: if self .warn_on_duplicate_resources: logger.warning(f"Resource already exists: {resource.uri} " ) return existing self ._resources[str (resource.uri)] = resource return resource def add_template ( self, fn: Callable [..., Any ], uri_template: str , name: str | None = None , title: str | None = None , description: str | None = None , mime_type: str | None = None , ) -> ResourceTemplate: """根据函数添加模版。""" template = ResourceTemplate.from_function( fn, uri_template=uri_template, name=name, title=title, description=description, mime_type=mime_type, ) self ._templates[template.uri_template] = template return template ...
对于静态资源,add_resource
方法会直接将 FunctionResource
实例存储在 _resources
字典中。对于动态资源,add_template
方法会创建 ResourceTemplate
实例并存储在 _templates
字典中。
静态 :FunctionResource
位于 resources/types.py :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class FunctionResource (Resource ): """通过包装函数来延迟加载数据的资源。 函数只有在资源被读取时才会被调用,允许对可能昂贵的数据进行延迟加载。 这在列出资源时特别有用,因为函数不会被调用,直到资源被实际访问。 函数可以返回: - str 表示文本内容(默认) - bytes 表示二进制内容 - 其他类型将被转换为 JSON """ fn: Callable [[], Any ] = Field(exclude=True ) ... @classmethod def from_function ( cls, fn: Callable [..., Any ], uri: str , name: str | None = None , title: str | None = None , description: str | None = None , mime_type: str | None = None , ) -> "FunctionResource" : """从函数创建 FunctionResource。""" func_name = name or fn.__name__ if func_name == "<lambda>" : raise ValueError("You must provide a name for lambda functions" ) fn = validate_call(fn) return cls( uri=AnyUrl(uri), name=func_name, title=title, description=description or fn.__doc__ or "" , mime_type=mime_type or "text/plain" , fn=fn, )
动态 :ResourceTemplate
位于 resources/templates.py :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class ResourceTemplate (BaseModel ): """动态创建资源的模板。""" uri_template: str = Field(description="URI template with parameters (e.g. weather://{city}/current)" ) name: str = Field(description="Name of the resource" ) title: str | None = Field(description="Human-readable title of the resource" , default=None ) description: str | None = Field(description="Description of what the resource does" ) mime_type: str = Field(default="text/plain" , description="MIME type of the resource content" ) fn: Callable [..., Any ] = Field(exclude=True ) parameters: dict [str , Any ] = Field(description="JSON schema for function parameters" ) @classmethod def from_function ( cls, fn: Callable [..., Any ], uri_template: str , name: str | None = None , title: str | None = None , description: str | None = None , mime_type: str | None = None , ) -> ResourceTemplate: """从函数创建模板。""" func_name = name or fn.__name__ if func_name == "<lambda>" : raise ValueError("You must provide a name for lambda functions" ) parameters = TypeAdapter(fn).json_schema() fn = validate_call(fn) return cls( uri_template=uri_template, name=func_name, title=title, description=description or fn.__doc__ or "" , mime_type=mime_type or "text/plain" , fn=fn, parameters=parameters, )
@mcp.prompt()
@mcp.prompt()
装饰器用于定义提示词模板,这部分的实现只是简单维护了一个字典。
追溯源码
查看 server.py 中的 prompt
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 class FastMCP : ... def prompt ( self, name: str | None = None , title: str | None = None , description: str | None = None , annotations: PromptAnnotations | None = None , ) -> Callable [[AnyFunction], AnyFunction]: """注册提示词的装饰器。 参数: name: 可选的提示词名称(默认使用函数名) title: 可选的提示词人类可读标题 description: 可选的提示词功能描述 使用示例: @server.prompt() def analyze_table(table_name: str) -> list[Message]: schema = read_table_schema(table_name) return [ { "role": "user", "content": f"Analyze this schema:\n{schema}" } ] @server.prompt() async def analyze_file(path: str) -> list[Message]: content = await read_file(path) return [ { "role": "user", "content": { "type": "resource", "resource": { "uri": f"file://{path}", "text": content } } } ] """ if callable (name): raise TypeError( "The @prompt decorator was used incorrectly. Did you forget to call it? Use @prompt() instead of @prompt" ) def decorator (func: AnyFunction ) -> AnyFunction: prompt = Prompt.from_function( func, name=name, title=title, description=description ) self .add_prompt(prompt) return func return decorator
PromptManager
的实现位于 prompts/prompt_manager.py :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class PromptManager : """管理 FastMCP 提示词。""" def __init__ (self, warn_on_duplicate_prompts: bool = True ): self ._prompts: dict [str , Prompt] = {} self .warn_on_duplicate_prompts = warn_on_duplicate_prompts def add_prompt (self, prompt: Prompt ) -> Prompt: """添加提示词到管理器。""" logger.debug(f"Adding prompt: {prompt.name} " ) existing = self ._prompts.get(prompt.name) if existing: if self .warn_on_duplicate_prompts: logger.warning(f"Prompt already exists: {prompt.name} " ) return existing self ._prompts[prompt.name] = prompt return prompt def get_prompt (self, name: str ) -> Prompt | None : """根据名称获取提示词。""" return self ._prompts.get(name) def list_prompts (self ) -> list [Prompt]: """列出所有已注册的提示词。""" return list (self ._prompts.values())
附录
官方源码:utilities/func_metadata.py
调试文件下载 :debug_func_metadata.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 """ func_metadata 调试工具,可以保存为 debug_func_metadata.py 执行 """ import inspectimport jsonfrom typing import Any , Callable , Sequence , Optional , List , Dict from pydantic import BaseModel, Field, create_modelfrom pydantic.fields import FieldInfofrom typing_extensions import Annotatedfrom mcp.server.fastmcp.utilities.func_metadata import ( FuncMetadata, ArgModelBase, _get_typed_signature, _get_typed_annotation, _try_create_model_and_schema, InvalidSignature, PydanticUndefined, WithJsonSchema ) def print_json (data, title="JSON数据" ): """打印JSON数据""" try : print (f"\n {title} :" ) print (json.dumps(data, indent=2 , ensure_ascii=False )) except Exception as e: print (f"❌ JSON打印失败: {e} " ) print (f"原始数据类型: {type (data)} " ) print (f"原始数据: {data} " ) def debug_func_metadata ( func: Callable [..., Any ], skip_names: Sequence [str ] = ( ), structured_output: bool | None = None , ) -> Any : """ 调试版本的 func_metadata 实现,会用到一些 from_function 中的逻辑,比如:func.__name__,func.__doc__ ... """ print (f"\n{'=' *60 } " ) print (f" 开始解析函数: {func.__name__} " ) print (f" 文档字符串: {func.__doc__} " ) print (f"{'=' *60 } " ) try : sig = _get_typed_signature(func) params = sig.parameters print (f"\n 函数签名分析:" ) print (f" 完整签名: {sig} " ) print (f" 返回类型: {sig.return_annotation} " ) print (f" 参数数量: {len (params)} " ) dynamic_pydantic_model_params: dict [str , Any ] = {} globalns = getattr (func, "__globals__" , {}) print (f"\n 参数处理详情:" ) print (f" 跳过的参数名: {list (skip_names)} " ) processed_count = 0 for idx, param in enumerate (params.values()): print (f"\n [{idx+1 } ] 参数名: {param.name} " ) print (f" 原始注解: {param.annotation} " ) print (f" 参数种类: {param.kind} " ) print (f" 默认值: {param.default} " ) if param.name.startswith("_" ): print (f" ❌ 错误: 参数名不能以 '_' 开头" ) raise InvalidSignature(f"{func.__name__} 的参数 {param.name} 不能以 '_' 开头" ) if param.name in skip_names: print (f" ⏭️ 跳过此参数" ) continue processed_count += 1 annotation = param.annotation if annotation is None : print (f" 处理: 类型为 None,添加默认值字段" ) annotation = Annotated[ None , Field(default=param.default if param.default is not inspect.Parameter.empty else PydanticUndefined), ] if annotation is inspect.Parameter.empty: print (f" ⚠️ 处理: 无类型注解,默认为 Any" ) annotation = Annotated[ Any , Field(), WithJsonSchema({"title" : param.name, "type" : "string" }), ] typed_annotation = _get_typed_annotation(annotation, globalns) print (f" 类型化注解: {typed_annotation} " ) field_info = FieldInfo.from_annotated_attribute( typed_annotation, param.default if param.default is not inspect.Parameter.empty else PydanticUndefined, ) print (f" ✅ 字段信息: annotation={field_info.annotation} , default={field_info.default} " ) if hasattr (BaseModel, param.name) and callable (getattr (BaseModel, param.name)): print (f" ⚠️ 冲突处理: 参数名 '{param.name} ' 与 BaseModel 方法冲突" ) field_info.alias = param.name field_info.validation_alias = param.name field_info.serialization_alias = param.name internal_name = f"field_{param.name} " dynamic_pydantic_model_params[internal_name] = (field_info.annotation, field_info) print (f" -> 使用内部名称: {internal_name} " ) else : dynamic_pydantic_model_params[param.name] = (field_info.annotation, field_info) print (f"\n 参数处理总结:" ) print (f" 总参数数: {len (params)} " ) print (f" 处理参数数: {processed_count} " ) print (f" 模型字段: {list (dynamic_pydantic_model_params.keys())} " ) arguments_model_name = f"{func.__name__} Arguments" print (f"\n ️ 创建 Pydantic 模型:" ) print (f" 模型名称: {arguments_model_name} " ) print (f" 基类: {ArgModelBase} " ) arguments_model = create_model( arguments_model_name, **dynamic_pydantic_model_params, __base__=ArgModelBase, ) print (f" ✅ 模型创建成功: {arguments_model} " ) try : schema = arguments_model.model_json_schema(by_alias=True ) print_json(schema, f"{arguments_model_name} JSON Schema" ) except Exception as e: print (f"❌ Schema 生成失败: {e} " ) print (f"\n 返回值处理:" ) print (f" structured_output 参数: {structured_output} " ) print (f" 返回注解: {sig.return_annotation} " ) if structured_output is False : print (f" 明确不需要结构化输出" ) result = FuncMetadata(arg_model=arguments_model) print (f" ✅ 返回元数据: {result} " ) return result if sig.return_annotation is inspect.Parameter.empty and structured_output is True : print (f" ❌ 错误: 要求结构化输出但无返回注解" ) raise InvalidSignature(f"函数 {func.__name__} : 结构化输出需要返回注释" ) output_info = FieldInfo.from_annotation(_get_typed_annotation(sig.return_annotation, globalns)) annotation = output_info.annotation print (f" 经过_get_typed_annotation处理后的类型: {annotation} " ) output_model, output_schema, wrap_output = _try_create_model_and_schema( annotation, func.__name__, output_info ) if output_model: print (f" ✅ 输出模型创建成功: {output_model} " ) if output_schema: print_json(output_schema, "返回值 JSON Schema" ) else : print (f" ℹ️ 未创建输出模型" ) print (f" wrap_output: {wrap_output} " ) if output_model is None and structured_output is True : print (f" ❌ 结构化输出失败: 返回类型不可序列化" ) raise InvalidSignature( f"函数 {func.__name__} : 返回类型 {annotation} 不支持结构化输出" ) result = FuncMetadata( arg_model=arguments_model, output_schema=output_schema, output_model=output_model, wrap_output=wrap_output, ) print (f"\n✨ func_metadata 处理完成!" ) print (f" 最终结果: {result} " ) print (f"{'=' *60 } \n" ) return result except Exception as e: print (f"❌ 处理过程中出错: {e} " ) import traceback print (f"详细错误信息:\n{traceback.format_exc()} " ) return None def test (): """测试各种类型的函数""" print ("\n\n 测试1: 混合类型注解" ) def func ( data, format : str = "json" , count: Optional [int ] = None , validate: bool = True ): """展示各种注解情况""" return data debug_func_metadata(func, skip_names="count" ) print ("\n\n 测试2: 前缀参数冲突" ) def prefix_func (_private: str , field_test: int ) -> str : """前缀参数""" return "test" debug_func_metadata(prefix_func) print ("\n\n 测试3: 结构化输出对比" ) def add (a: int , b: int ) -> str : return a + b print (" 无结构化" ) debug_func_metadata(add, structured_output=False ) print ("\n\n 结构化" ) debug_func_metadata(add, structured_output=True ) if __name__ == "__main__" : test()
输出 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 测试1 : 混合类型注解 ============================================================ 开始解析函数: func 文档字符串: 展示各种注解情况 ============================================================ 函数签名分析: 完整签名: (data, format: str = 'json', count: Optional[ int] = None, validate: bool = True) 返回类型: <class 'inspect._empty'> 参数数量: 4 参数处理详情: 跳过的参数名: [ 'c', 'o', 'u', 'n', 't'] [ 1 ] 参数名: data 原始注解: <class 'inspect._empty'> 参数种类: POSITIONAL_OR_KEYWORD 默认值: <class 'inspect._empty'> ⚠️ 处理: 无类型注解,默认为 Any 类型化注解: typing.Annotated[ typing.Any, FieldInfo(annotation=NoneType, required=True), WithJsonSchema(json_schema={ 'title': 'data', 'type': 'string'} , mode=None)] ✅ 字段信息: annotation=typing.Any, default=PydanticUndefined [ 2 ] 参数名: format 原始注解: <class 'str'> 参数种类: POSITIONAL_OR_KEYWORD 默认值: json 类型化注解: <class 'str'> ✅ 字段信息: annotation=<class 'str'>, default=json [ 3 ] 参数名: count 原始注解: typing.Optional[ int] 参数种类: POSITIONAL_OR_KEYWORD 默认值: None ⏭️ 跳过此参数 [ 4 ] 参数名: validate 原始注解: <class 'bool'> 参数种类: POSITIONAL_OR_KEYWORD 默认值: True 类型化注解: <class 'bool'> ✅ 字段信息: annotation=<class 'bool'>, default=True ⚠️ 冲突处理: 参数名 'validate' 与 BaseModel 方法冲突 -> 使用内部名称: field_validate 参数处理总结: 总参数数: 4 处理参数数: 3 模型字段: [ 'data', 'format', 'field_validate'] ️ 创建 Pydantic 模型: 模型名称: funcArguments 基类: <class 'mcp.server.fastmcp.utilities.func_metadata.ArgModelBase'> ✅ 模型创建成功: <class '__main__.funcArguments'> funcArguments JSON Schema: { "properties" : { "data" : { "title" : "data" , "type" : "string" } , "format" : { "default" : "json" , "title" : "Format" , "type" : "string" } , "validate" : { "default" : true , "title" : "Validate" , "type" : "boolean" } } , "required" : [ "data" ] , "title" : "funcArguments" , "type" : "object" } 返回值处理: structured_output 参数: None 返回注解: <class 'inspect._empty'> 经过_get_typed_annotation处理后的类型: <class 'inspect._empty'> ℹ️ 未创建输出模型 wrap_output: False ✨ func_metadata 处理完成! 最终结果: arg_model=<class '__main__.funcArguments'> output_schema=None output_model=None wrap_output=False ============================================================ 测试2 : 前缀参数冲突 ============================================================ 开始解析函数: prefix_func 文档字符串: 前缀参数 ============================================================ 函数签名分析: 完整签名: (_private: str, field_test: int) -> str 返回类型: <class 'str'> 参数数量: 2 参数处理详情: 跳过的参数名: [ ] [ 1 ] 参数名: _private 原始注解: <class 'str'> 参数种类: POSITIONAL_OR_KEYWORD 默认值: <class 'inspect._empty'> ❌ 错误: 参数名不能以 '_' 开头 ❌ 处理过程中出错: prefix_func 的参数 _private 不能以 '_' 开头 详细错误信息: Traceback (most recent call last): File "/tmp/ipython-input-16-54014459.py" , line 78 , in debug_func_metadata raise InvalidSignature(f"{func.__name__} 的参数 {param.name} 不能以 '_' 开头" ) mcp.server.fastmcp.exceptions.InvalidSignature: prefix_func 的参数 _private 不能以 '_' 开头 测试3 : 结构化输出对比 无结构化 ============================================================ 开始解析函数: add 文档字符串: None ============================================================ 函数签名分析: 完整签名: (a: int, b: int) -> str 返回类型: <class 'str'> 参数数量: 2 参数处理详情: 跳过的参数名: [ ] [ 1 ] 参数名: a 原始注解: <class 'int'> 参数种类: POSITIONAL_OR_KEYWORD 默认值: <class 'inspect._empty'> 类型化注解: <class 'int'> ✅ 字段信息: annotation=<class 'int'>, default=PydanticUndefined [ 2 ] 参数名: b 原始注解: <class 'int'> 参数种类: POSITIONAL_OR_KEYWORD 默认值: <class 'inspect._empty'> 类型化注解: <class 'int'> ✅ 字段信息: annotation=<class 'int'>, default=PydanticUndefined 参数处理总结: 总参数数: 2 处理参数数: 2 模型字段: [ 'a', 'b'] ️ 创建 Pydantic 模型: 模型名称: addArguments 基类: <class 'mcp.server.fastmcp.utilities.func_metadata.ArgModelBase'> ✅ 模型创建成功: <class '__main__.addArguments'> addArguments JSON Schema: { "properties" : { "a" : { "title" : "A" , "type" : "integer" } , "b" : { "title" : "B" , "type" : "integer" } } , "required" : [ "a" , "b" ] , "title" : "addArguments" , "type" : "object" } 返回值处理: structured_output 参数: False 返回注解: <class 'str'> 明确不需要结构化输出 ✅ 返回元数据: arg_model=<class '__main__.addArguments'> output_schema=None output_model=None wrap_output=False 结构化 ============================================================ 开始解析函数: add 文档字符串: None ============================================================ 函数签名分析: 完整签名: (a: int, b: int) -> str 返回类型: <class 'str'> 参数数量: 2 参数处理详情: 跳过的参数名: [ ] [ 1 ] 参数名: a 原始注解: <class 'int'> 参数种类: POSITIONAL_OR_KEYWORD 默认值: <class 'inspect._empty'> 类型化注解: <class 'int'> ✅ 字段信息: annotation=<class 'int'>, default=PydanticUndefined [ 2 ] 参数名: b 原始注解: <class 'int'> 参数种类: POSITIONAL_OR_KEYWORD 默认值: <class 'inspect._empty'> 类型化注解: <class 'int'> ✅ 字段信息: annotation=<class 'int'>, default=PydanticUndefined 参数处理总结: 总参数数: 2 处理参数数: 2 模型字段: [ 'a', 'b'] ️ 创建 Pydantic 模型: 模型名称: addArguments 基类: <class 'mcp.server.fastmcp.utilities.func_metadata.ArgModelBase'> ✅ 模型创建成功: <class '__main__.addArguments'> addArguments JSON Schema: { "properties" : { "a" : { "title" : "A" , "type" : "integer" } , "b" : { "title" : "B" , "type" : "integer" } } , "required" : [ "a" , "b" ] , "title" : "addArguments" , "type" : "object" } 返回值处理: structured_output 参数: True 返回注解: <class 'str'> 经过_get_typed_annotation处理后的类型: <class 'str'> ✅ 输出模型创建成功: <class 'mcp.server.fastmcp.utilities.func_metadata.addOutput'> 返回值 JSON Schema: { "properties" : { "result" : { "title" : "Result" , "type" : "string" } } , "required" : [ "result" ] , "title" : "addOutput" , "type" : "object" } wrap_output: True ✨ func_metadata 处理完成! 最终结果: arg_model=<class '__main__.addArguments'> output_schema={ 'properties': { 'result': { 'title': 'Result', 'type': 'string'} } , 'required': [ 'result'] , 'title': 'addOutput', 'type': 'object'} output_model=<class 'mcp.server.fastmcp.utilities.func_metadata.addOutput'> wrap_output=True ============================================================
1 2 3 4 5 chrome.runtime.sendMessage( { type: "retrieveTaskResult", taskId: "task_1756914557023_1jlccqpyv" }, (response) => { console.log(response) })