4.3 ToolAnnotations:声明工具的行为特征


4.3 ToolAnnotations:声明工具的行为特征

本节摘要:类型注解描述了「工具需要什么、返回什么」,但没描述「工具会干什么」——它只读吗?有破坏性吗?对外公开吗?这些行为特征影响着宿主的授权策略与模型的调用谨慎度。ToolAnnotations 就是用来声明这些元信息的机制。本节讲清它表达的几个关键行为标志(只读、破坏性、开放等),它们如何被宿主用于授权决策,以及模型如何据此调整调用策略。读完本节,你能让你的工具不只是「能被调用」,还能「被正确地对待」。

一、为什么类型注解不够

类型注解回答了「参数和返回的结构」,但没回答这些行为问题:

  • 这个工具会改世界吗?(写库、发邮件、删文件)
  • 它是只读的吗?(查询、计算、读取)
  • 它是否幂等?(重复调用结果一样吗)
  • 它能在无人监督下自动调用吗?

这些问题的答案,不影响工具能不能跑,但影响它「该不该被调」「该怎么被调」。考虑两个工具:

工具 A:get_balance(user_id) → int (只读,查余额) 工具 B:transfer(from, to, amount) → bool (破坏性,转账)

光看签名,它们都是「传参数、拿结果」。但宿主对待它们的方式天差地别——get_balance 可以放心自动调,transfer 该让用户确认。这个差别,需要一种机制来表达,这就是 ToolAnnotations

二、ToolAnnotations 的关键标志

ToolAnnotations 是一组布尔标志,描述工具的行为特征:

标志 含义 典型工具
read_only 只读,不改世界 查询、计算、读取
destructive 破坏性,不可逆 删除、覆盖、转账
idempotent 幂等,重复调用结果一致 设置配置、创建(已存在则不动)
open_world 与外部世界交互 调外部 API、发邮件、写文件

用法:

from mcp.server import MCPServer from mcp.server.mcpserver import ToolAnnotations mcp = MCPServer("Bank") @mcp.tool(annotations=ToolAnnotations( read_only=True, # 查余额是只读 )) def get_balance(user_id: str) -> int: """Get account balance.""" ... @mcp.tool(annotations=ToolAnnotations( destructive=True, # 转账是破坏性 open_world=True, # 涉及外部账本 )) def transfer(from_id: str, to_id: str, amount: float) -> bool: """Transfer money between accounts.""" ...

三、宿主如何用这些标志

ToolAnnotations 不是装饰——它会被宿主用于实际决策:

模型决定调用 transfer(...) │ ▼ 宿主检查 ToolAnnotations: destructive=True → 触发「破坏性操作确认」 │ ▼ 宿主弹窗:「确认转账 $100 从 A 到 B?」 │ ▼ 用户确认 → 执行;用户拒绝 → 取消,告知模型

不同标志触发的宿主行为:

标志组合 宿主的典型反应
read_only=True 放心自动调,无需确认
destructive=True 必须用户确认才执行
idempotent=True 可安全重试(失败了再调一次没事)
open_world=True 提示「这会与外部交互」,可能需额外授权

这就是为什么准确标注 ToolAnnotations 重要——它直接决定你的工具在宿主里是被「放心自动调」还是「谨慎确认」。

⚠️ 注意:宿主是否真的按这些标志行事,取决于宿主实现。Claude Desktop 可能对 destructive 强制确认,另一个宿主可能忽略。但作为工具作者,你的责任是准确声明——这样支持这些标志的宿主能正确对待你的工具,不支持的至少拿到了准确信息。

四、模型如何用这些标志

除了宿主,模型也会读 ToolAnnotations(它会出现在发给模型的工具描述里)。模型据此调整调用策略:

标志 模型的典型策略
read_only=True 放心调,失败了大不了再查
destructive=True 谨慎调,先确认参数对,必要时先调只读工具核实
open_world=True 知道这会动外部世界,调前会权衡

举个例子:模型要删一个文件,如果它看到删除工具标了 destructive=True,可能会先调用一个只读的 file_exists 工具核实,再调删除——这种谨慎行为部分来自 ToolAnnotations 的提示。

五、准确标注的实践建议

ToolAnnotations 的价值依赖准确。错误标注(把破坏性标成只读)会让宿主与模型做出错误决策。实践建议:

工具行为 建议标注
纯查询、计算 read_only=True
创建(可能覆盖) destructive=True(若会覆盖)
创建(已存在则不动) idempotent=True
删除 destructive=True
调外部 API open_world=True
改本地配置 idempotent=True(若幂等)

💡 技巧:拿不准时,优先标「保守」——比如不确定是否破坏性,标 destructive=True 让宿主谨慎对待,比标错成只读导致误操作安全。标注的目的是「让工具被正确对待」,保守一点代价小,激进一点代价大。

六、ToolAnnotations 与错误处理的协作

ToolAnnotations 还能与错误处理协作,提升工具的健壮性。一个常见模式:幂等工具支持安全重试:

@mcp.tool(annotations=ToolAnnotations( idempotent=True, # 幂等,可安全重试 )) def set_config(key: str, value: str) -> bool: """Set a config value (idempotent).""" save_config(key, value) # 同样的 key/value 重复调,结果一样 return True

幂等标志告诉宿主与模型:「这个工具失败了可以重试,不会有副作用累积」。这样当网络抖动导致失败时,宿主能放心重试,而不是「失败就放弃」。

本节要点回顾

  1. 类型注解描述结构,ToolAnnotations 描述行为——后者回答「会干什么」。
  2. 四个关键标志:read_only(只读)、destructive(破坏性)、idempotent(幂等)、open_world(与外部交互)。
  3. 宿主用这些标志做授权决策——破坏性触发确认、只读放心自动调。
  4. 模型也读这些标志调整策略——破坏性前先核实、只读放心调。
  5. 准确标注是关键,错误标注会让宿主与模型做错误决策。
  6. 拿不准时优先保守标注(破坏性标 True 比标错成只读安全)。
  7. 幂等标志支持安全重试,失败时宿主能放心重调。

行为特征清楚了,下一节讲工具失败时如何把错误回传模型。


作者与出处
原作者: 灏天文库
来源:modelcontextprotocol
许可证:MIT
整理: 灏天文库整理
由灏天文库结构化整理,提供目录导航、全文检索与在线阅读,便于系统化学习
发布者: 作者: 灏天文库 转发
评论区 (0)
U