FastMCP MCPMixin 组件化指南:用类方法与装饰器批量注册 Tool / Resource / Prompt
【免费下载链接】fastmcp🚀 The fast, Pythonic way to build MCP servers and clients.项目地址: https://gitcode.com/GitHub_Trending/fa/fastmcp
本文以 fastmcp_slim/fastmcp/contrib/mcp_mixin/README.md 为核心,讲解 FastMCP 提供的
MCPMixin基类与@mcp_tool、@mcp_resource、@mcp_prompt三个装饰器:开发者可以在普通类中定义方法,然后一次性把它们注册为 MCP Server 上的工具、资源和提示词。读完本文,你将掌握基于类的组件化开发方式、enabled/exclude_args/annotations/meta等高级配置,以及用prefix和分隔符解决多实例命名冲突的完整方案。
MCPMixin 解决什么问题
常规 FastMCP 开发中,工具、资源和提示词分散在模块级函数上,通过FastMCP.tool()、FastMCP.resource()、FastMCP.prompt()装饰器逐个挂到 server 上。当组件数量变多,或者需要把一组相关能力(例如一个"数据组件"同时暴露查询工具、配置资源与分析提示词)组织成可复用的单元时,Mixin 模式更具优势:
- 用类把相互关联的方法聚合在一起,天然获得命名空间与复用能力;
- 用装饰器在方法定义处声明注册意图,无需手动调用 server 的注册 API;
- 通过
register_all()等注册方法,一次把整类的方法批量挂到FastMCP实例上; - 支持
prefix前缀,多个同类实例可以共存而不产生名称冲突。
MCPMixin的实现位于 mcp_mixin.py,对外导出(见init.py)为:
from fastmcp.contrib.mcp_mixin import MCPMixin, mcp_tool, mcp_resource, mcp_prompt快速上手:第一个 MCPMixin 组件
继承MCPMixin,在方法上使用装饰器,然后调用register_all()注册即可:
from mcp_types import ToolAnnotations from fastmcp import FastMCP from fastmcp.contrib.mcp_mixin import MCPMixin, mcp_tool, mcp_resource, mcp_prompt class MyComponent(MCPMixin): @mcp_tool(name="my_tool", description="Does something cool.") def tool_method(self): return "Tool executed!" @mcp_resource(uri="component://data") def resource_method(self): return {"data": "some data"} @mcp_prompt(name="greeting") def prompt_method(self, name): return f"What's up {name}?" mcp_server = FastMCP() component = MyComponent() component.register_all(mcp_server, prefix="my_comp") # 注册后: # - 工具名为 my_comp_my_tool # - 资源 URI 为 my_comp+component://data # - 提示词名为 my_comp_greeting如果不传prefix,则直接使用装饰器中指定的原始名称 / URI:
component.register_all(mcp_server) # 注册为 my_tool / component://data / greeting装饰器 API 与参数说明
三个装饰器在行为上保持一致的设计:它们并不立即把方法注册到任何 server,而是把注册参数以属性形式"标记"在函数对象上,等待register_*方法在遍历类方法时读取。因此所有参数最终都会转发给底层工厂方法:
| 装饰器 | 底层工厂方法 | 必填参数 | 关键可选参数 |
|---|---|---|---|
@mcp_tool | Tool.from_function | 无(默认使用方法名) | name、description、enabled、exclude_args、annotations、meta、tags、auth、timeout、version、title、icons、output_schema、task、run_in_thread |
@mcp_resource | Resource.from_function | uri | name、enabled、title、description、mime_type、tags、annotations、meta、auth、version、icons |
@mcp_prompt | Prompt.from_function | 无(默认使用方法名) | name、enabled、title、description、tags、meta、auth、version、icons |
其中enabled是 Mixin 体系新增的标志位(底层工厂方法并没有这个参数),用来在注册阶段过滤方法;其余参数直接透传给from_function。三个工厂方法的完整签名可分别查看 Tool.from_function、Resource.from_function 与 Prompt.from_function。
装饰器参数校验:未知参数立即报错
三个装饰器都会在装饰时校验关键字参数是否合法,而不是等到注册时才报错。它们依据底层from_function的签名自动推导合法参数集合,例如 mcp_mixin.py 中的实现:
_TOOL_VALID_KWARGS: frozenset[str] = frozenset( p for p in inspect.signature(Tool.from_function).parameters if p != "fn" )一旦传入未识别的参数,立即抛出TypeError,错误信息会列出所有合法参数。相关行为在 tests/contrib/test_mcp_mixin.py 的TestMCPMixinValidation中有完整覆盖:
with pytest.raises(TypeError, match="unexpected keyword argument"): @mcp_tool(definitely_not_a_real_param="oops") def my_tool(self): passTool:注解、禁用与参数排除
使用enabled=False禁用工具
enabled=False的方法不会在注册阶段被挂载,客户端自然也无法调用:
@mcp_tool(name="my_tool", description="Does something cool.", enabled=False) def disabled_tool_method(self): return "You'll never get here!"从源码看,enabled被存放在一个专用的哨兵键_mixin_enabled中(避免与from_function的任何参数冲突),注册时通过registration_info.pop(_MIXIN_ENABLED_KEY, True)取出并判断,见 mcp_mixin.py。enabled默认值为True(不传即正常注册),这一行为在TestMCPMixinEnabled测试类中得到了验证。
使用exclude_args排除危险参数
某些内部参数不希望暴露给 MCP 客户端时,用exclude_args把它们从工具的 JSON Schema 中剔除:
@mcp_tool( name="my_tool", description="Does something cool.", enabled=False, exclude_args=['delete_everything'], ) def excluded_param_tool_method(self, delete_everything=False): # MCP 客户端永远无法传入 "delete_everything" 参数 if delete_everything: return "Nothing to delete, I bet you're not a tool :)" return "You might be a tool if..."使用annotations指导 LLM 行为
ToolAnnotations用于向 LLM 客户端声明工具的调用语义(是否只读、是否破坏性、是否幂等),帮助模型决定调用顺序与方式:
@mcp_tool( name="my_tool", description="Does something cool.", annotations=ToolAnnotations( title="Attn LLM, use this tool first!", readOnlyHint=False, destructiveHint=False, idempotentHint=False, ) ) def tool_method(self): return "Tool executed!"组合全部能力
enabled、exclude_args与annotations可以同时使用:
@mcp_tool( name="my_tool", description="Does something cool.", enabled=True, exclude_args=['delete_all'], annotations=ToolAnnotations( title="Attn LLM, use this tool first!", readOnlyHint=False, destructiveHint=False, idempotentHint=False, ) ) def tool_method(self, delete_all=False): if delete_all: return "99 records deleted. I bet you're not a tool :)" return "Tool executed, but you might be a tool!"使用meta附加自定义元数据
meta可以携带任意键值对(版本、分类、作者等),会原样写入工具对象,供服务端或客户端消费:
@mcp_tool( name="data_tool", description="Fetches user data from database", meta={"version": "2.0", "category": "database", "author": "dev-team"} ) def data_tool_method(self, user_id: int): return f"Fetching data for user {user_id}"测试 test_tool_with_title_and_meta 验证了annotations.title与meta都会完整透传到注册后的工具对象上:
assert tool.annotations.title == "My Tool Title" assert tool.meta == {"version": "1.0", "author": "test"}Resource:URI 注册、禁用与元数据
资源通过uri标识,支持enabled、title、meta等配置:
# 基础资源 @mcp_resource(uri="component://data") def resource_method(self): return {"data": "some data"} # 禁用资源 @mcp_resource(uri="component://data", enabled=False) def resource_method(self): return {"data": "some data"} # 带 title 与 meta 的资源 @mcp_resource( uri="component://config", title="Data resource Title", meta={"internal": True, "cache_ttl": 3600, "priority": "high"} ) def config_resource_method(self): return {"config": "data"}注意资源注册时的enabled=False处理与工具一致:通过pop(_MIXIN_ENABLED_KEY, True)判断后直接continue跳过,见 mcp_mixin.py。test_resource_with_meta测试则验证了meta与title会写入最终的FunctionResource对象。
Prompt:名称、禁用与上下文提示
提示词用于定义可复用的 prompt 模板,方法返回值将作为用户消息内容:
# 基础 prompt @mcp_prompt(name="A prompt") def prompt_method(self, name): return f"What's up {name}?" # 禁用 prompt @mcp_prompt(name="A prompt", enabled=False) def prompt_method(self, name): return f"What's up {name}?" # 带 title、description 与 meta 的 prompt @mcp_prompt( name="analysis_prompt", title="Data Analysis Prompt", description="Analyzes data patterns", meta={"complexity": "high", "domain": "analytics", "requires_context": True} ) def analysis_prompt_method(self, dataset: str): return f"Analyze the patterns in {dataset}"test_prompt_with_title_and_meta测试确认了title与meta会被正确透传。
用 prefix 与分隔符解决多实例命名冲突
register_all的prefix参数是可选的。当同一个类被实例化多次并需要同时注册时(例如为两组配置分别暴露组件),前缀可以避免工具名、资源 URI 与提示词名互相覆盖:
component.register_all(mcp_server, prefix="my_comp")register_all的完整签名如下(见 mcp_mixin.py):
def register_all( self, mcp_server: "FastMCP", prefix: str | None = None, tool_separator: str = "_", # 工具名默认分隔符 resource_separator: str = "+", # 资源名/URI 默认分隔符 prompt_separator: str = "_", # 提示词名默认分隔符 ) -> None它内部依次调用register_tools、register_resources、register_prompts,并把各自的separator传递下去。各注册方法对前缀的处理方式为:
- 工具:
f"{prefix}{separator}{original_name}",例如my_comp_my_tool; - 资源:名称与 URI 都会被加前缀,
f"{prefix}{separator}{original_name}"与f"{prefix}{separator}{original_uri}",例如my_comp+component://data; - 提示词:
f"{prefix}{separator}{original_name}",例如my_comp_greeting。
三种类型使用不同的默认分隔符(工具_、资源+、提示词_),既保持了各自生态的惯例(资源 URI 通常用+连接),也可以通过register_all的tool_separator/resource_separator/prompt_separator参数按类型单独定制。
仓库自带的 example.py 演示了多实例场景——同一个Sample类创建两个实例,用不同前缀注册,组件名称互不干扰:
first_sample = Sample("First") second_sample = Sample("Second") first_sample.register_all(mcp_server=mcp, prefix="first") second_sample.register_all(mcp_server=mcp, prefix="second")TestMCPMixin测试类用参数化用例系统验证了三种组合:无前缀、默认分隔符、自定义分隔符(工具-、资源::、提示词.),并断言前缀后的名称/URI 精确匹配预期值,见 test_mcp_mixin.py。
注册流程:MCPMixin 是如何工作的
从源码结构看,注册机制分三步:
- 标记阶段:
@mcp_tool/@mcp_resource/@mcp_prompt在装饰时把注册参数(含name、uri及全部透传 kwargs)以属性形式附加到方法上,分别对应_mcp_tool_registration、_mcp_resource_registration、_mcp_prompt_registration三个属性,见 mcp_mixin.py。 - 收集阶段:
_get_methods_to_register()遍历dir(self),筛选出带有对应标记属性的可调用方法,返回(方法, 注册信息副本)列表。 - 注册阶段:
register_tools/register_resources/register_prompts依次完成——应用前缀与分隔符 → 弹出enabled哨兵键并跳过False的方法 → 调用Tool/Resource/Prompt.from_function(fn=method, **registration_info)构建组件 → 通过mcp_server.add_tool/add_resource/add_prompt挂载。
由于合法 kwargs 集合在导入时直接从from_function签名推导(_TOOL_VALID_KWARGS等),当底层工厂方法新增参数(如auth、timeout、version)时,装饰器无需手工同步即可透传新参数。TestMCPMixinKwargsSync测试类专门断言了这两者始终一致;TestMCPMixinNewParams则端到端验证了auth=require_scopes(...)、timeout=5.0、version="2.0"等参数都能经装饰器正确转发到注册后的组件上(见 tests/contrib/test_mcp_mixin.py)。
进阶组合与完整示例
把上述能力组合起来,一个完整的"数据服务组件"可以同时提供带注解的安全工具、带元数据的资源与带描述的分析提示词:
from fastmcp import FastMCP from fastmcp.contrib.mcp_mixin import MCPMixin, mcp_tool, mcp_resource, mcp_prompt from mcp_types import ToolAnnotations class DataComponent(MCPMixin): @mcp_tool( name="query_users", description="Query users from the database", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False), meta={"version": "1.0", "owner": "data-team"}, timeout=30.0, ) def query_users(self, limit: int = 10): return f"SELECT * FROM users LIMIT {limit}" @mcp_resource( uri="component://config", title="Component Configuration", meta={"cache_ttl": 3600}, ) def config(self): return {"feature_flags": ["search", "export"]} @mcp_prompt( name="audit_report", title="Audit Report Prompt", description="Generates an audit report for a user", ) def audit_report(self, user_id: int): return f"Generate an audit report for user {user_id}" server = FastMCP("data-service") DataComponent().register_all(server) # 无前缀注册若需要同时挂载多个同类实例,改用prefix即可避免命名空间冲突,再配合tool_separator/resource_separator/prompt_separator调整命名风格。
小结
MCPMixin把 FastMCP 的"函数式注册"升级为"类化组件化注册",核心收益有三点:方法即组件(装饰器声明 + 批量注册)、声明即配置(enabled/exclude_args/annotations/meta全部在装饰器内声明)、实例即命名空间(prefix+ 按类型分隔符解决多实例冲突)。配合装饰器在导入期自动与from_function签名同步的能力,新参数可以零成本透传,开发者只需要关注类方法本身的实现即可。
- 核心实现:mcp_mixin.py
- 完整示例:example.py
- 测试覆盖:tests/contrib/test_mcp_mixin.py
- 相关能力文档:Tools、Resources、Prompts
【免费下载链接】fastmcp🚀 The fast, Pythonic way to build MCP servers and clients.项目地址: https://gitcode.com/GitHub_Trending/fa/fastmcp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考