What are MCP's four ToolAnnotations and what does each default to?
answer
- Four booleans, all named Hint
- Defaults assume the worst, not the best
- Only one of the four defaults to false-as-safe
- destructiveHint is meaningless when read-only
- Idempotency is about retry safety
basics
~20 sToolAnnotations are readOnlyHint (default false), destructiveHint (default true), idempotentHint (default false) and openWorldHint (default true). Omitting them therefore describes the most dangerous tool: one that writes, destroys, is unsafe to repeat, and touches the outside world.
solid answer
~50 sA tool's optional `annotations` object carries four behavioural fields. `readOnlyHint` says the tool does not modify its environment; it defaults to `false`. `destructiveHint` says the tool may perform destructive updates rather than purely additive ones, and is only meaningful when `readOnlyHint` is false; it defaults to `true`. `idempotentHint` says repeating the call with the same arguments has no additional effect; it defaults to `false`. `openWorldHint` says the tool interacts with an open set of external entities — the web, a third-party API — rather than a closed world; it defaults to `true`. Every default is the pessimistic value, so a server that annotates nothing is treated as offering write-capable, destructive, non-idempotent, open-world tools, and a host can safely gate them behind confirmation. They are hints that shape UI and consent prompts, not protocol-enforced guarantees. Unchanged in MCP revision 2026-07-28.
code
json · 10 lines{
"name": "list_projects",
"description": "List projects visible to the caller.",
"inputSchema": { "type": "object", "properties": {} },
"annotations": {
"readOnlyHint": true,
"idempotentHint": true,
"openWorldHint": false
}
}go deeper
Learn the four names and their defaults: readOnlyHint false, destructiveHint true, idempotentHint false, openWorldHint true — omitting them describes the most dangerous tool, not the safest.
Explain what each hint tells a host — confirmation strength, retry safety, blast radius — and note that destructiveHint only means anything when readOnlyHint is false.
Show that you annotate your own servers honestly and pessimistically, and connect idempotentHint to retry policy now that a broken stream must be re-issued as a brand-new request.
Own annotation policy across an internal server fleet: a review rule that every tool is annotated, what the host does with each combination, and where annotations stop and real authorization begins.
## The four fields `ToolAnnotations` is an optional object on a `Tool` entry in `tools/list`. It carries four booleans: | Field | Meaning | Default | |---|---|---| | `readOnlyHint` | The tool does not modify its environment | `false` | | `destructiveHint` | The tool may perform destructive updates (only meaningful when `readOnlyHint` is false) | `true` | | `idempotentHint` | Repeating the call with the same arguments has no additional effect | `false` | | `openWorldHint` | The tool interacts with an open, external world of entities | `true` | There is also a `title` for display. Everything here describes *behaviour*, not permissions. ## Read the defaults as a sentence The defaults are the single most asked detail, and they are chosen so that the absence of information is the least convenient assumption, never the most convenient one. Take an annotation-free tool and read the defaults out: it is **not** read-only, it **may** be destructive, it is **not** idempotent, and it **does** touch the open world. That is precisely the profile that should trigger a confirmation prompt. This matters because most servers in the wild annotate nothing. A host that treated "absent" as "safe" would auto-run every unannotated `delete_branch` it ever met. Fail-safe defaults mean the host's conservative path is also its default path. ## What each one buys a host **`readOnlyHint: true`** is the strongest signal and the one hosts actually act on. A read-only tool is a candidate for running without an individual confirmation, for running in parallel with others, and for retrying freely after a transport hiccup. Setting it on a tool that writes anything is the worst annotation mistake you can make. **`destructiveHint`** distinguishes additive change from irreversible change. `append_comment` is a non-destructive write; `delete_repository` and `force_push` are destructive. Hosts use it to decide how loud the confirmation is — a checkbox versus a typed confirmation — and it is meaningless on a tool that already claims `readOnlyHint: true`. **`idempotentHint`** governs retry safety. If a response stream breaks mid-call, the client has no way to know whether the server finished the work, and MCP 2026-07-28 has no resumption: the request must be re-issued as a brand-new request with a new JSON-RPC id. Whether that re-issue is safe is exactly what `idempotentHint` answers. `set_status("done")` is idempotent; `transfer_funds(100)` emphatically is not. This is the annotation most often set wrongly by optimism. **`openWorldHint`** describes the blast radius of the entity space. A tool that searches the public web, or calls an arbitrary URL supplied in its arguments, is open-world. A tool that reads rows from one known database is closed-world (`false`). Hosts use it to reason about what data could come back and how much scrutiny the result deserves. ## Hints, not enforcement All four are named `*Hint` on purpose. MCP does not verify any of them; nothing stops a tool annotated `readOnlyHint: true` from dropping a table. They exist to let a well-behaved server communicate intent so a host can build a sane consent and execution policy on top. The protocol itself enforces nothing here — the host is the enforcement boundary. ## Annotating your own server well - Annotate every tool. Leaving them off is not neutral; it is the pessimistic reading, and it means every call of your harmless `list_projects` gets a confirmation dialog. - Be honest, and specifically be pessimistic when unsure. The cost of an over-cautious annotation is an extra click; the cost of an over-optimistic one is a silently auto-executed destructive call. - Keep them stable. Annotations are part of the tool description that clients cache with `tools/list`, and flipping them changes host behaviour. - Do not encode authorization in them. "Only admins may call this" is not an annotation; it is an authorization decision made when the request is served. ## Revision note `ToolAnnotations` and its four defaults are unchanged in MCP revision 2026-07-28. What changed around them is the packaging: `tools/list` now returns a cacheable result with required `ttlMs` and `cacheScope`, so annotations, like the rest of the tool description, may sit in a client cache for the advertised lifetime rather than being re-fetched per connection.
- A server ships tools with no annotations at all. How should a host behave?As if every tool were the most dangerous kind: not read-only, potentially destructive, not idempotent, open-world. In practice that means prompting for confirmation on each call and not retrying automatically after an interrupted call. The defaults are deliberately pessimistic so "no information" and "assume the worst" are the same policy.
- Why does idempotentHint matter more in MCP 2026-07-28 than it might seem?Because this revision removed stream resumability. If a response stream breaks, there is no `Last-Event-ID` replay — the client must re-issue the call as a new request with a new JSON-RPC id, without knowing whether the first attempt completed. `idempotentHint` is the only signal telling the client whether that re-issue is safe or could double-charge a customer.
- Is destructiveHint meaningful on a tool that sets readOnlyHint to true?No. `destructiveHint` describes what kind of modification a writing tool performs, so it is only meaningful when `readOnlyHint` is false. A tool claiming to modify nothing has nothing to destroy; setting both is contradictory noise, and a host reading `readOnlyHint: true` will not consult it.
They read like the warning labels on a power tool: absent a label, the safe assumption is that it cuts.
saying these in an interview costs you the question
- Assuming an unannotated tool is read-only and safe
- Saying destructiveHint defaults to false
- Treating annotations as protocol-enforced guarantees
- Encoding who may call a tool as an annotation
- Marking a tool idempotent because retrying usually works