Every MCP server exposes tools. A tool might read a file, query a database, send an email, or delete a deployment. The problem: the client has no way to tell these apart just from the tool’s name and description. A model calling send_invoice needs a different level of caution than one calling list_invoices.

Tool annotations solve this. They are optional metadata fields on any MCP tool definition that signal what kind of side effects a tool has. Clients use them to decide whether to auto-approve a call, prompt the user, or block it entirely.

The four annotations

The Model Context Protocol specification defines four boolean annotation fields. Each one answers a specific question about a tool’s behavior.

readOnlyHint

Does this tool only read data without modifying anything?

When set to true, the tool promises it will not change state. It only observes. A client that sees readOnlyHint: true can run the tool without asking for confirmation, because nothing will change.

Defaults to false if omitted. That means an unannotated tool is assumed to write by default, which is the safe assumption.

{
  "name": "get_user_profile",
  "description": "Fetch a user's profile by ID",
  "annotations": {
    "readOnlyHint": true
  }
}

destructiveHint

Can this tool destroy or permanently alter data?

When set to true, the tool may delete records, drop tables, remove files, or perform other irreversible operations. Clients should treat this as a signal to require explicit user confirmation before executing. Some clients may refuse to auto-approve destructive tools entirely.

Defaults to true if omitted. An unannotated tool is treated as potentially destructive. This is intentional. The spec favors caution over convenience.

{
  "name": "delete_repository",
  "description": "Permanently delete a GitHub repository",
  "annotations": {
    "destructiveHint": true,
    "readOnlyHint": false
  }
}

idempotentHint

Can the tool be called multiple times with the same input and produce the same result?

An idempotent tool is safe to retry. If a network timeout cuts off the response, the client can re-run the call without worrying about duplicate side effects. Setting a user’s email address is idempotent. Sending an email is not.

Defaults to false if omitted. A tool that both writes and is not marked idempotent gets the most cautious handling.

{
  "name": "set_user_email",
  "description": "Update the email address for a user account",
  "annotations": {
    "idempotentHint": true,
    "readOnlyHint": false,
    "destructiveHint": false
  }
}

openWorldHint

Does this tool interact with systems outside the local environment?

A tool that calls an external API, sends a webhook, posts to Slack, or pushes to a remote repository has open-world effects. These calls cross trust boundaries. A client might allow local file reads without prompting but require confirmation for anything that touches the network.

Defaults to true if omitted. The spec assumes tools reach outside by default.

{
  "name": "post_slack_message",
  "description": "Send a message to a Slack channel",
  "annotations": {
    "openWorldHint": true,
    "destructiveHint": false,
    "readOnlyHint": false
  }
}

How clients use annotations

Annotations are hints, not permissions. The spec calls them “hints” deliberately. A client is free to ignore them. But well-built clients use them to create tiered approval flows.

A common pattern looks like this:

  • readOnlyHint: true — auto-approve, no user prompt needed
  • readOnlyHint: false + destructiveHint: false + idempotentHint: true — auto-approve or light confirmation
  • destructiveHint: true or openWorldHint: true — require explicit user confirmation
  • destructiveHint: true + openWorldHint: true — block unless the user has pre-authorized this tool

Claude Code uses exactly this kind of tiered system. Tools marked as read-only run silently. Tools that write to the file system ask once. Tools that hit external services always prompt.

Why defaults matter

The default values are the most important design decision in the annotation system. Every default leans toward safety:

AnnotationDefaultWhy
readOnlyHintfalseAssume writes unless told otherwise
destructiveHinttrueAssume destructive unless told otherwise
idempotentHintfalseAssume not safe to retry
openWorldHinttrueAssume external effects

An unannotated tool gets the most restrictive treatment possible. This is a deliberate choice. Server authors who skip annotations do not accidentally create tools that clients auto-approve.

Adding annotations to your server

If you are building an MCP server, adding annotations takes one extra field in your tool definition. The annotations object sits alongside name, description, and inputSchema.

server.tool(
  "list_files",
  "List files in a directory",
  {
    path: z.string().describe("Directory path to list")
  },
  {
    annotations: {
      readOnlyHint: true,
      destructiveHint: false,
      openWorldHint: false
    }
  },
  async ({ path }) => {
    // implementation
  }
);

For Python-based servers, the pattern is similar. Pass the annotations as metadata when registering the tool handler.

The key rule: be honest. A tool that sometimes writes should not be marked read-only. A tool that can fail with duplicate side effects should not be marked idempotent. Incorrect annotations are worse than no annotations, because they cause clients to skip safety checks they should be running.

Annotations and agent safety

As AI agents gain more autonomy, the gap between “tool that reads” and “tool that deletes production data” matters more. Human-in-the-loop confirmation works when agents make ten tool calls per session. It breaks down when agents make hundreds.

Annotations give client developers a structured way to automate the easy decisions (let read-only tools run) and surface the hard ones (flag destructive or open-world calls for review). They are not a complete safety system on their own. But they are the foundation that more sophisticated permission models will build on.

For a look at how different MCP servers handle auth and access control, see our guide on how MCP auth works.

FAQ

Q: Are annotations required in the MCP spec? A: No. Annotations are optional. But the defaults are strict enough that skipping them means your tools get the most restrictive treatment from well-built clients. Adding accurate annotations makes your server easier to work with.

Q: Can a client override annotations? A: Yes. Annotations are hints, not guarantees. A security-focused client might require confirmation for every tool call regardless of annotations. A developer testing locally might auto-approve everything. The server declares intent; the client decides policy.

Q: What happens if I set conflicting annotations? A: The spec does not enforce consistency between fields. If you mark a tool as both readOnlyHint: true and destructiveHint: true, most clients will follow the more cautious signal. But the real fix is to set accurate values. A read-only tool is by definition not destructive.