← All posts
August 24, 2026·6 min read

MCP tool annotations: how your AI agent's host knows which calls need your OK

By Alejandro Rioja

MCP tool annotations are a small metadata block — readOnlyHint, destructiveHint, idempotentHint, openWorldHint — that a server attaches to every tool it exposes, so the host on the other end (Claude Desktop, Cursor, a custom MCP client) can tell a harmless lookup from a change to your data before it ever calls it. Done sets these across all 56 of its tools, derived straight from the same list that already enforces read-only token scoping, so the advertised safety story and the enforced one can't drift apart.

What do readOnlyHint, destructiveHint, idempotentHint, and openWorldHint actually mean?

  • readOnlyHint — true if the call can't change anything. Done's 18 read tools (list_my_tasks, get_task, get_digest, fleet_status, and the rest) carry this; the other 38, which create, update, or delete something, don't.
  • destructiveHint — true if the call removes or overwrites something already there, rather than only adding to it. delete_task and delete_project are the obvious cases; update_task counts too, since overwriting a field is destructive to whatever was in it before, even though Done's deletes are soft and recoverable from Trash.
  • idempotentHint — true if calling the same write twice with the same arguments leaves things in the same state as calling it once. block_task or set_step_status can be repeated safely; anything that appends a new row — comment_on_task, start_run, decompose_task — can't, so it's left off.
  • openWorldHint — whether the tool reaches outside a closed, known set of resources. Every Done tool touches only your own account's rows, calendar links included, so this is false everywhere.

Why does a host need this instead of just asking the model?

Because the model isn't the one deciding whether to prompt you — the host is, and it has to decide before the call runs, not after. A host that wants to let read-only calls through without interrupting you, but pause on anything that deletes or overwrites data, needs a place to read that distinction that doesn't depend on the model correctly guessing intent from a tool's name or description on every single call.

What happens if a server doesn't declare annotations at all?

It doesn't read as "probably fine." A host that can't classify a tool has to assume the worst about it — an unannotated call gets treated the same as an unverified, destructive write against an open world, reads included. That's the gap Done closed: every one of its 56 tools shipped for a while with no annotations block, so a host gating on "not explicitly read-only" had to gate on all 56 — get_digest and list_my_tasks along with delete_task — because there was nothing on the wire telling it otherwise.

How does Done keep the annotations honest?

By deriving them from the same source of truth the scope guard already uses, instead of hand-writing a second copy that could drift. WRITE_TOOLS is the set that decides whether a read-only-scoped token is allowed to call something at all; annotationsFor() reads that same set to decide readOnlyHint. A destructive or idempotent write is checked against two further sets — DESTRUCTIVE_TOOLS and CONVERGENT_TOOLS — built by the same logic (does it overwrite or remove; does it converge on repeat), so there's exactly one place that says what a tool does, not one enforcement path and one advertised description that quietly disagree.

Do annotations replace the approval gate or token scoping?

No — they're a different layer, upstream of both. Token scoping controls what an agent's credentials let it call at all; Done's approval gate controls whether a non-auto-OK task can close without a human signing off; MCP annotations are what a host's own UI can act on before either of those is even reached — for example, prompting you to confirm a destructive call inline, rather than after the fact. None of the three substitutes for the others, and annotations are hints for the host's judgment, not a security boundary Done relies on by themselves — the scope guard and the approval gate still enforce the real limits server-side, regardless of what a host does with the metadata.

Frequently asked questions

What are MCP tool annotations?

A metadata block — readOnlyHint, destructiveHint, idempotentHint, openWorldHint — that an MCP server attaches to each tool it exposes, so a host can tell whether a call is a safe read, a repeatable write, or something that overwrites or removes data, before it calls the tool.

What does readOnlyHint mean in MCP?

readOnlyHint: true means the tool can't change anything — a lookup like get_task or list_my_tasks. Done sets it on all 18 of its read-only tools, and false on the 38 that create, update, or delete something.

Does declaring no annotations make a tool safer?

No — the opposite. A host that can't classify a tool has to treat it as the worst case: an unverified, destructive write against an open world. Declaring accurate annotations is what lets a host treat a read like get_digest differently from a write like delete_task.

Try it yourself

Capture a task, assign it to an AI agent, and stay the one who signs off.