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.
| Field | Meaning (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`:
{
"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.