MCPmatic

MCP tool annotations explained

Published · Updated · Written against MCP spec 2026-07-28

Tool annotations are optional hints a server attaches to a tool to tell clients how it behaves. Clients use them to decide things like whether to ask the user before running a call.

The four hints and their defaults#

HintMeaning when trueDefault if missing
readOnlyHintThe tool does not modify its environment.false
destructiveHintThe tool may perform destructive updates. When false, it only makes additive updates. Only meaningful when readOnlyHint is false.true
idempotentHintCalling it again with the same arguments has no further effect. Only meaningful when readOnlyHint is false.false
openWorldHintThe tool interacts with an open world of external entities, like web search. False means a closed domain, like a local memory store.true

There's also an optional title annotation for display.

The defaults are cautious, so be explicit#

A tool with no annotations is treated as possibly destructive, not idempotent and open-world. That's safe, but it means a harmless read-only tool may trigger the same confirmation prompts as a delete. Mark read-only tools readOnlyHint: true, and give write tools an honest destructiveHint.

Hints are not a security control#

The schema reference is blunt about this: annotations are hints, not guaranteed to be accurate, and clients should never make tool-use decisions based on annotations from untrusted servers. The tools spec says clients MUST treat annotations as untrusted unless they come from trusted servers. So annotations help well-behaved clients give a better experience. They don't replace access control on your server.

Examples#

{
  "name": "search_orders",
  "description": "Search the shop's orders by customer email or order number. Returns up to 25 matches.",
  "inputSchema": {"type": "object", "properties": {"query": {"type": "string", "description": "Email or order number"}}, "required": ["query"]},
  "annotations": {"readOnlyHint": true, "openWorldHint": false}
}
{
  "name": "refund_order",
  "description": "Refund an order in full to the original payment method. Cannot be undone.",
  "inputSchema": {"type": "object", "properties": {"order_id": {"type": "string", "description": "Order number"}}, "required": ["order_id"]},
  "annotations": {"readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": true}
}

The refund is idempotent if your API refuses a second refund of the same order. If it doesn't, set idempotentHint: false.

Converting from OpenAPI? The converter suggests hints from each HTTP method, and the linter flags tools whose names don't match their hints. For the other fields of a tool, see the tool schema reference.

Sources#