MCP tool definitions carry an optional annotations object — a small set of hints like readOnlyHint and destructiveHint that a server can attach to a tool alongside its name, description, and schema. Almost no server sets them. That’s a missed opportunity, because annotations are the one part of a tool definition designed specifically to answer a question a description in prose answers only implicitly: is this safe to call without thinking twice? Here’s what the annotations actually are, why the spec insists they’re hints and not guarantees, and how gate uses them anyway.
The four annotations
The spec defines a handful of boolean and string hints a server can put on a tool. The two that matter most for safety are:
readOnlyHint— the tool doesn’t modify its environment.truemeans a lookup, a search, a list;falseor absent means assume it can write.destructiveHint— the tool may perform destructive updates: deleting data, spending money, sending something to a third party, or another hard-to-reverse change. Only meaningful whenreadOnlyHintisfalse.
Two more exist, and are used less consistently in practice:
idempotentHint— calling the tool again with the same arguments has no additional effect. Useful context on top ofreadOnlyHint: false, and closely related to the retry problem covered in MCP tool idempotency, though the spec treats them as separate signals rather than one implying the other.openWorldHint— the tool interacts with entities outside a closed, fully known set — a web search or an API call to an external service, as opposed to a fixed local dataset. It speaks to unpredictability more than danger.
There’s also a plain-language title annotation, meant as a display-friendly alternative to the tool’s programmatic name — unrelated to risk, worth setting anyway since it’s the cheapest annotation to write.
Why the spec calls them “hints,” not facts
Every annotation is optional, and the spec is explicit that a client must not treat any of them as a security guarantee. Nothing stops a server from marking a tool readOnlyHint: true while the tool itself writes to a database — through a bug, through neglect, or, in the worst case, on purpose to get past a client that trusts the hint at face value. Annotations describe intended behavior as reported by whoever wrote the server, which puts them in the same trust category as the tool description itself: useful signal from a cooperative server, and exactly the kind of claim a hostile one would lie about. If you’re vetting a server before connecting it, an annotation is a data point, not a verdict — see how to vet an MCP server for the rest of the checklist.
Why they’re still worth setting
Given that a client can’t fully trust them, it’s fair to ask why a server builder should bother. Two reasons hold up even with an untrustworthy-by-default annotation:
- They’re free structure a model doesn’t have to infer from prose. A description can say “permanently deletes a record” and a careful model will often act accordingly, but that’s the model parsing language under token pressure. A boolean field is unambiguous by construction — there’s no reading of
destructiveHint: truethat means something else. - They give a gateway or client something structured to gate on before any model judgment enters the picture. A policy engine can check
destructiveHintand require approval, or downgrade a tool’s default from allow to ask, without waiting on an LLM call to reach the same conclusion.
For a server you’re building yourself, setting readOnlyHint and destructiveHint honestly costs a few minutes per tool and gives every client downstream — not just the ones you tested with — a head start on treating your write and destructive tools with appropriate care.
How gate actually uses them
gate’s risk classifier runs once per server connect (and again on any tool-list change): it reads readOnlyHint and destructiveHint off each tool if present, and separately sends the full tool list — names, descriptions, schemas — to an LLM classification pass that assigns the same three-level risk independently, with no knowledge of what the annotations said. Whichever of the two calls it riskier wins: a tool a server marks readOnlyHint: true but whose description and schema look like a write still gets classified as a write, because the LLM pass isn’t deferring to a hint it can’t verify. That’s the same reasoning the spec gives for calling them hints in the first place, applied directly: useful when accurate, never load-bearing on their own.
If you’re building a server
Set readOnlyHint and destructiveHint on every tool you write, and set them conservatively — when a tool’s effect is ambiguous, the spec’s own guidance is to lean toward the annotation that assumes more risk, not less. If you’re generating tools from an existing API rather than writing them by hand — the process covered in turning an API into an MCP server — check whether your generator sets annotations at all; many auto-generated tool lists skip them entirely and leave every tool looking equally low-stakes to a client that only reads annotations. Pairing that with a plain-language description of any side effect, as covered in how to write MCP tool descriptions, covers the gap for clients that don’t weigh annotations as heavily as gate does.
The bottom line
Annotations are the closest thing MCP has to a structured risk label on a tool, and the spec is honest that they’re only as trustworthy as the server that set them. That doesn’t make them useless — it makes them one signal among several, best combined with an independent check rather than trusted on their own. Set them honestly if you’re building a server. If you’re connecting to someone else’s, don’t assume a missing or generous annotation means a tool is safe; verify it the way a gateway or a scan does, from the description and schema, not just the label.