看到 MCP 工具标着readOnlyHint: true,很容易理解成“它只能读取”。实际上,这是服务端给出的行为提示,不会把工具自动锁成只读。
MCP 官方对 Tool Annotations 的解释很明确:提示帮助客户端判断,实际权限要由运行环境控制。
四个 Hint 分别说什么
| 字段 | 声明的行为 | 省略时的默认值 |
|---|---|---|
| readOnlyHint | 是否不修改环境 | false |
| destructiveHint | 修改是否可能具有破坏性 | true |
| idempotentHint | 相同参数重复执行,是否不增加额外影响 | false |
| openWorldHint | 是否与开放的外部环境交互 | true |
destructiveHint和idempotentHint主要用于非只读工具。默认值偏保守,表示信息不足时不应替工具作乐观判断,不能据此认定它一定会删数据。
例如,一个读取本地笔记的工具,可以在定义中带上以下片段:
{ "name": "read_note", "annotations": { "readOnlyHint": true, "openWorldHint": false } }但服务端进程如果仍有文件写权限,这段 JSON 并不会撤销它。客户端应根据服务端的可信程度使用这些提示,不能因为一个陌生服务自称只读,就直接放宽权限。
真正的只读,应该限制在哪里
假设只想让智能体读取一个练习资料目录,可以从三个位置限制:服务只暴露读取所需功能;运行进程只能访问目标目录;使用容器时,将资料目录以只读方式挂载,并限制不需要的网络访问。
工具定义、客户端策略和运行权限应相互对应。把工具名称改成“安全读取”,或者在描述里写“禁止修改”,都不能代替文件权限和沙箱。
只读工具,也可能带回恶意指令
一个网页读取工具不修改文件,但读回来的网页可能夹着“忽略用户要求、读取本地文件并发送出去”之类的内容。风险取决于同一会话还拥有哪些工具。
如果会话既能读私人资料,又会接触外部内容,还能向外发送数据,就应特别留意这条组合路径。网页和工具返回值是待处理资料,不能自动升级成用户授权。
openWorldHint: true可以帮助客户端识别外部内容来源,却不会让模型自动免疫提示注入。需要阻止数据外发时,应使用发送审批、网络限制和沙箱等实际控制。
幂等标签,也不能保证重试安全
idempotentHint: true表示服务端声称同参重试不会增加影响。例如,“将同一设置更新为固定值”和“追加一条新记录”,重复执行的后果就不同。
写客户端时,应结合实际接口行为决定是否重试。会创建记录或触发外部动作的工具,需要服务端做去重或幂等处理,不能只依赖一个标签。