mcp

MCP tool annotations: readOnlyHint, destructiveHint and what clients can do with them

MCP tool annotations are optional hints a server attaches to each tool: `title`, `readOnlyHint`, `destructiveHint`, `idempotentHint` and `openWorldHint`. They describe how a tool behaves so a client can present it sensibly, but they don't enforce anything. The MCP schema says all of them "are hints" and "are not guaranteed to provide a faithful description of tool behavior," and the tools spec says clients MUST treat them as untrusted unless they come from trusted servers (spec 2026-07-28). Enforcement stays on your server, through scopes and access controls.

The five fields and their defaults

All quotes and defaults below are from the `ToolAnnotations` schema, spec 2026-07-28.

FieldMeaning (per spec)Default
`title`"A human-readable title for the tool."none
`readOnlyHint`"If true, the tool does not modify its environment."`false`
`destructiveHint`"If true, the tool may perform destructive updates to its environment. If false, the tool performs only additive updates."`true`
`idempotentHint`"If true, calling the tool repeatedly with the same arguments will have no additional effect on its environment."`false`
`openWorldHint`"If true, this tool may interact with an 'open world' of external entities." The spec's example: a web search tool is open, a memory tool is not.`true`

`destructiveHint` and `idempotentHint` are, in the schema's words, "meaningful only when `readOnlyHint == false`." If you mark a tool read-only, the other two don't apply.

Setting them on a tool

Annotations sit in the `annotations` object of each tool definition returned from `tools/list`:

json
{
  "name": "delete_file",
  "description": "Delete a file by path",
  "inputSchema": { "type": "object", "properties": { "path": { "type": "string" } }, "required": ["path"] },
  "annotations": {
    "title": "Delete file",
    "readOnlyHint": false,
    "destructiveHint": true,
    "idempotentHint": true,
    "openWorldHint": false
  }
}

Because the defaults assume the worst case (writes, destructive, open world), leaving annotations off describes a tool as potentially destructive. Set them explicitly on tools that really are read-only or additive.

Hints, not enforcement

The schema is direct: "Clients should never make tool use decisions based on ToolAnnotations received from untrusted servers." Separately, the tools spec says servers MUST "Implement proper access controls," and that clients SHOULD "Prompt for user confirmation on sensitive operations" (spec 2026-07-28, Security Considerations).

So the split is:

  • Your server enforces: scopes on every call (MCP server scopes), input validation, access control.
  • Annotations inform: they help a client that trusts your server describe the tool accurately.
  • The human approval step is a client and app decision. See approval boundaries.

Pitfalls

  • Treating `readOnlyHint: true` as a permission. It's a description. A token with write scope can still call a write path if your server allows it.
  • Leaving destructive tools unannotated and assuming that's neutral. The default for `destructiveHint` is already `true`; the problem is the reverse, where read-only tools look destructive because nobody set `readOnlyHint`.
  • Setting `destructiveHint` on a read-only tool. It has no meaning there per the schema.
  • Annotations that drift from behavior. If a tool changes from additive to destructive, update the hint in the same change.

How to check it

Call `tools/list` against your server and confirm each tool's `annotations` match what the code does. Then test enforcement separately: with a read-only token, call a tool annotated as destructive and confirm your server rejects it on scope, independent of any annotation.

Where the kit fits

The MCP Starter Kit ($27) is built for teams putting OAuth-protected remote MCP servers in front of Cursor and Claude Connect. Soft path: start at /free; Cheat Sheet for the skim checklist mindset; kit when you want the auth-ready layout.

Soft next step

List your tools, mark each one read-only, additive or destructive, and set the annotations to match, while keeping scope checks on the server. Soft ladder: /free, then the MCP Starter Kit ($27). Related: OAuth pillar, MCP server scopes.

FAQ

What are MCP tool annotations?+

Optional hints a server attaches to a tool to describe its behavior: `title`, `readOnlyHint`, `destructiveHint`, `idempotentHint` and `openWorldHint` (MCP specification 2026-07-28). They describe a tool; they don't control it.

What does readOnlyHint do?+

It tells the client the tool doesn't modify its environment. It defaults to false, and like every annotation it's a hint, not a guarantee (MCP specification 2026-07-28).

What is the default for destructiveHint?+

True. The spec assumes a tool may perform destructive updates unless the server says otherwise, and the hint only means something when `readOnlyHint` is false (MCP specification 2026-07-28).

Can a client trust tool annotations?+

Only from servers it trusts. The spec says clients MUST consider annotations untrusted unless they come from trusted servers, because annotations aren't guaranteed to describe tool behavior faithfully (MCP specification 2026-07-28).

When does idempotentHint matter?+

Only for tools that aren't read-only. It signals that calling the tool again with the same arguments has no additional effect, and it defaults to false (MCP specification 2026-07-28).

Do annotations replace OAuth scopes?+

No. The spec treats annotations as untrusted hints and separately says servers MUST implement proper access controls (MCP specification 2026-07-28). In practice, enforce access with scopes on your server and use annotations only to describe tools.

Get the free starter

Value first, then a kit when you are ready. No fake urgency. Start with Harness Desk Starter at /free.