告别重复代码!FastMCP工具装饰器的高级方法封装指南
·
告别重复代码!FastMCP工具装饰器的高级方法封装指南
你是否还在为Python类方法与FastMCP装饰器的兼容性问题头疼?是否遇到过self参数暴露给LLM导致的调用失败?本文将通过3种实用模式+5个代码示例,带你掌握方法级别装饰的最佳实践,让工具封装既优雅又安全。
装饰器困境:为什么直接装饰会失败?
FastMCP的@tool、@resource等装饰器会将函数转换为特定对象(如Tool实例),而非保留原函数引用。直接装饰类方法会导致两个严重问题:
- 方法不可调用:装饰后方法变为Tool对象,导致
obj.method()调用失败 - 参数暴露异常:
self/cls参数被错误暴露给LLM,引发工具调用错误
class Calculator: @mcp.tool # 错误:直接装饰实例方法 def add(self, x: int, y: int) -> int: return x + y
calc = Calculator() calc.add(1, 2) # 运行时错误:add是Tool对象而非函数
</Warning>
官方文档详细说明了这一限制:[docs/patterns/decorating-methods.mdx](https://link.gitcode.com/i/ea3aac04f4cf7cbe9e75cb302baa5394)。解决之道在于采用"先定义后注册"的装饰模式。
## 实例方法:动态绑定的正确姿势
实例方法需要先创建对象实例,再通过实例引用注册工具,确保`self`参数被正确绑定:
<Check>
**正确示范**:实例方法注册模式
```python
from fastmcp import FastMCP
mcp = FastMCP()
class DatabaseClient:
def __init__(self, conn_str: str):
self.connection = create_db_connection(conn_str)
def query(self, sql: str) -> dict:
"""执行SQL查询并返回结果"""
return self.connection.execute(sql).fetchall()
# 关键步骤:创建实例后注册方法
db_client = DatabaseClient("sqlite:///data.db")
mcp.tool(db_client.query) # 注册绑定后的实例方法
此模式优势在于:
self参数被自动隐藏,LLM仅需提供sql参数- 实例化时可注入依赖(如数据库连接)
- 支持多实例注册不同配置的工具
类方法:装饰器顺序与延迟注册
类方法装饰需特别注意装饰器顺序,错误顺序会导致难以调试的问题:
**错误示范**:错误的装饰器顺序 ```python class DataParser: @classmethod @mcp.tool # 危险:@classmethod在前会隐藏错误 def from_json(cls, data: str) -> "DataParser": return cls(**json.loads(data))@mcp.tool
@classmethod # 正确顺序但仍不推荐
def from_csv(cls, data: str) -> "DataParser":
return cls(** csv.DictReader(data.splitlines()))
</Warning>
正确做法是采用延迟注册模式:
<Check>
**推荐方案**:类方法延迟注册
```python
class DataParser:
@classmethod
def from_json(cls, data: str) -> "DataParser":
return cls(**json.loads(data))
@classmethod
def from_csv(cls, data: str) -> "DataParser":
return cls(** csv.DictReader(data.splitlines()))
# 类定义后统一注册
mcp.tool(DataParser.from_json)
mcp.tool(DataParser.from_csv)
这种方式确保:
cls参数被正确绑定- 支持继承场景下的方法重写
- 注册逻辑与类定义分离,便于维护
静态方法:看似可行的陷阱
静态方法虽然可以直接装饰,但会失去方法的可调用性,官方文档明确不推荐:
**不推荐做法**:直接装饰静态方法 ```python class StringUtils: @mcp.tool @staticmethod def to_uppercase(text: str) -> str: return text.upper()问题:无法通过类调用原始方法
StringUtils.to_uppercase("test") # 返回Tool对象而非字符串
</Warning>
<Check>
**替代方案**:显式注册静态方法
```python
class StringUtils:
@staticmethod
def to_uppercase(text: str) -> str:
return text.upper()
# 保持方法可调用性
mcp.tool(StringUtils.to_uppercase)
高级模式:初始化时自动注册
对于复杂组件,可在__init__中实现自动注册,将工具封装与注册逻辑内聚:
from fastmcp import FastMCP
class FileProcessor:
def __init__(self, mcp: FastMCP, base_dir: str = "data/"):
self.base_dir = base_dir
# 初始化时自动注册所有工具方法
self._register_tools(mcp)
def read_file(self, filename: str) -> str:
with open(f"{self.base_dir}/{filename}") as f:
return f.read()
def write_file(self, filename: str, content: str) -> None:
with open(f"{self.base_dir}/{filename}", "w") as f:
f.write(content)
def _register_tools(self, mcp: FastMCP):
mcp.tool(self.read_file)
mcp.tool(self.write_file)
# 使用时自动完成注册
processor = FileProcessor(mcp, "documents/")
该模式在examples/config_server.py中有实际应用,特别适合:
- 多工具组件封装
- 需要依赖注入的场景
- 模块化工具集管理
方法装饰决策指南
选择合适的装饰模式可参考以下决策树:
通过本文介绍的模式,你可以:
- 避免90%的装饰器使用错误
- 保持类方法的OOP封装优势
- 实现工具注册与业务逻辑分离
完整装饰器使用规范可参考官方文档:docs/patterns/decorating-methods.mdx,更多实际案例可查看examples/smart_home/中的设备控制类实现。
更多推荐
所有评论(0)