工具进阶与最佳实践
上一章的 add 能跑,但真实工具要面对"模型会不会选错、参数会不会填错、执行失败怎么办"。本章讲描述与参数 Schema、错误返回约定、长任务、鉴权与工具组织。
描述与参数 Schema 是"说明书"
模型靠工具名、描述与参数 Schema 决定何时调用、怎么填参。描述写清"做什么、何时用、不要用于什么",能显著减少误调用:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("calc-server")
@mcp.tool
def divide(a: float, b: float) -> float:
"""计算 a 除以 b。b 为 0 时抛出错误。仅用于数值除法,不用于取整。"""
if b == 0:
raise ValueError("除数不能为 0") # 参数非法:抛异常
return a / b
参数结构由类型注解与 pydantic 校验自动生成 Schema;类型要具体(int/float/str 分开),注释里写明单位与取值范围。复杂入参建议抽成 pydantic 模型,写法以 SDK 文档为准。
错误返回约定
约定越统一,模型越会正确处理失败:
- 调用本身不合法(参数错、工具不存在):抛异常,让宿主把错误展示给用户。
- 执行了但业务失败(余额不足、记录不存在):返回结构化结果,并明确置 isError 语义。
@mcp.tool
def transfer(from_acct: str, to_acct: str, amount: float) -> dict:
"""转账:从 from_acct 向 to_acct 转 amount 元(敏感操作)"""
if amount <= 0:
raise ValueError("金额必须大于 0")
if from_acct == to_acct:
return {"ok": False, "error": "转入转出账户不能相同"} # 业务失败
# ……真实转账逻辑……
return {"ok": True, "txid": "T2026..."}
错误文案要模型可读:直接告诉它"怎么修"比"失败"二字有用。
长任务、进度与采样
- 耗时工具(批量处理、外部调用):利用进度通知上报完成百分比,宿主可显示给用户(进度机制细节见官方规范)。
- 需要服务器再问一次模型(如"生成一段摘要再入库"):规范提供 sampling 能力——服务器可请求宿主代为调用模型并回传结果,须经用户同意。 这两块更新较快,落地前以 modelcontextprotocol.io 官方规范为准。
鉴权与敏感工具
- 删除、写文件、执行命令、花钱类工具要低调:命名与描述中标注破坏性(如 delete_file),宿主会在调用前请求用户确认。
- 服务器侧也要做最小权限:只授予完成任务所需的数据与凭据,不把万能密钥暴露给模型。
- 敏感输入(口令、Token)不要放进工具参数长期留存。
工具数量与组织
- 一"组"能力放一台服务器,工具控制在可管理的规模;太多就按领域拆分服务器。
- 用统一前缀分组,避免同名冲突:github_create_issue、github_list_repos 比裸 create/list 更清晰。
- 工具要内聚且可独立调用;宁可少而精,不要大量重叠工具让模型选择困难。
小结
好的工具 = 清楚的描述与 Schema + 统一错误约定 + 明确的敏感边界 + 克制的数量。工具层做扎实,模型调用准确率与安全性才有保障。下一章讲第二种原语:给模型注入上下文的资源。