一句话结论
MCP 工具注解(ToolAnnotations)是服务端给工具贴的四张「行为提示」标签——readOnlyHint 、 destructiveHint 、 idempotentHint 、 openWorldHint,帮客户端决定「调之前要不要问用户」,但它们只是提示不是安全机制,来自不可信服务器时应当整个忽略。
写 MCP 服务端的人大多见过注册工具时的 annotations 参数,但真正用对的不多:要么全留空让客户端按最坏情况处理,要么乱标 readOnlyHint 反而把自己工具的风险掩盖掉。 2026 年 3 月 MCP 官方博客专门发文梳理了注解的现状与边界,这篇按「是什么、默认值、怎么用、什么时候不可信」四步讲清(还不了解 MCP 协议本身的,先看我们的 MCP 入门教程)。
四个提示分别管什么
注解随 2025-03-26 版规范落地,当前接口一共五个字段(含纯展示用的 title):
| 注解 | 回答的问题 | 默认值 |
|---|---|---|
| readOnlyHint | 工具会不会修改环境? | false(默认会改) |
| destructiveHint | 如果会改,是破坏性的还是追加性的? | true(默认破坏性) |
| idempotentHint | 同一参数重复调用是否无副作用? | false(默认不幂等) |
| openWorldHint | 与封闭域交互还是开放的外部世界? | true(默认开放) |
| title | 给 UI 看的人类可读名称 | 无 |
默认值的设计原则是「未声明即按最坏情况」:没有注解的工具被视为会改环境、可能破坏、不幂等、开放世界。这套悲观默认让漏标的服务端不会获得危险的高估。
前三个提示回答的基本是同一个前置问题:客户端要不要在调用前向用户确认。第四个 openWorldHint 不一样——它描述工具的触达范围和输出可能带回什么,这在调用之后同样重要,且对部署环境高度敏感(「外部」指公司内网之外还是本机之外,取决于服务器跑在哪)。
代码里怎么标注
Python SDK 的装饰器写法(官方文档示例的工具):
@mcp.tool(
title="Search the catalog",
annotations={
"readOnlyHint": True, # 只读:不改任何东西
"openWorldHint": False, # 封闭域:只查这个目录,不碰开放网络
},
)
def search_books(query: str) -> str:
...
destructiveHint 和 idempotentHint 只对「会写」的工具才有意义——规范定义它们仅在 readOnlyHint == false 时有效,一个纯查询工具标这两个字段没有语义。一个删除接口的合理标注是 destructiveHint: True;一个追加日志的接口则是 destructiveHint: False 加 idempotentHint: False。
核心纪律:提示不是担保
规范原文说得毫不含糊:所有注解属性都是 hints,不保证忠实描述工具行为(包括 title 这种描述性字段);客户端绝不应基于来自不可信服务器的注解做工具使用决策。这段话的来源是规范评审时的真实争论——MCP 联合创始人 Justin Spahr-Summers 当时就提出:如果信息不可信,客户端拿它有什么用?最终的折中方案是:全部叫 hint 、默认按不可信对待、由每个客户端根据自己对服务器的了解决定采信程度。 Basil Hosmer 的立场更激进:对不可信服务器的注解应该整个忽略,包括 title 。
落成实践守则就是一句话:
现状:覆盖率不均,社区仍在演进
官方博客披露,注解推出以来社区已提交五份独立 SEP(规范增强提案)试图新增注解,背后是对「智能体工作流里风险到底在哪」的共识仍在深化。同时大量存量服务器根本没标注解,各家客户端对悲观默认的执行宽严也不一——这正是当前波 SEP 想收敛的差距。另外早年讨论过的一些方案(stateless 、 streaming 、 async 注解、安全类注解)有的被否决,有的换了形态(taskHint 最终以 Tool.execution 落地),如果看到旧资料提到这些,以现行规范为准。
工具注解要和另外两件事配合看:一是工具描述(description)写给模型读、注解写给客户端读,两者互补;二是真实风险控制要落在审批点设计上——客户端把 readOnlyHint 为 true 的工具设为静默执行、其余弹确认,是目前的常见实现。更系统的防泄漏与防投毒思路可以参考我们的 MCP 安全风险清单,动手写服务端则从 MCP Server 搭建教程起步。









评论 (0)