MCP tool annotations: readOnlyHint and how clients use them

MCP tool annotations are optional hints such as readOnlyHint. What each one means, its default, and how ChatGPT, VS Code, and Copilot use them.

6 min readSume
All posts

MCP tool annotations are optional hints a server attaches to each tool to describe how it behaves: readOnlyHint, destructiveHint, idempotentHint, openWorldHint, and a display title. Clients read them to decide things such as which calls need a person's approval, but they are hints, not guarantees: the spec says to treat them as untrusted unless they come from a trusted server.

The definitions come from the MCP specification's schema reference and Tools page, version 2025-11-25. Client behavior comes from each client's own docs, and the worked example is Sume's hosted MCP server, from its code and MCP tools and gates. All were read on 2026-09-27.

What does each annotation mean?

All four hints are booleans, and each has a default that applies when the server leaves it out:

  • Read by those defaults, a tool that sends no annotations is a write that may be destructive and may reach external entities.
  • title is a human-readable name. For display, the spec's order is the tool's title, then annotations.title, then name.
From the MCP schema reference, version 2025-11-25, read 2026-09-27.
AnnotationWhen trueDefault
readOnlyHintThe tool does not modify its environmentfalse
destructiveHintThe tool may perform destructive updates; false means only additive updates. Meaningful only when readOnlyHint is falsetrue
idempotentHintRepeated calls with the same arguments have no additional effect. Meaningful only when readOnlyHint is falsefalse
openWorldHintThe tool may interact with an open world of external entities, such as web search; a memory tool's world is closedtrue

Where do annotations go in a tool definition?

In the annotations object of each tool the server returns from tools/list, next to name, description, and inputSchema. Below is the spec's weather example, trimmed, with annotations added: a lookup that changes nothing and calls an outside service.

{
  "name": "get_weather",
  "title": "Weather Information Provider",
  "description": "Get current weather information for a location",
  "inputSchema": {
    "type": "object",
    "properties": {
      "location": { "type": "string", "description": "City name or zip code" }
    },
    "required": ["location"]
  },
  "annotations": {
    "readOnlyHint": true,
    "openWorldHint": true
  }
}

Are MCP tool annotations enforced?

No. The spec calls every property in ToolAnnotations a hint that is not guaranteed to describe the tool faithfully. Clients should never make tool use decisions based on annotations from untrusted servers, and clients must consider annotations untrusted unless they come from trusted servers. Mastra's docs put the risk plainly: a malicious or buggy server can claim a tool is read-only when it isn't.

How do MCP clients use readOnlyHint?

Differently. Some ask before any call not marked read-only, one drops such tools, and some leave the policy to your code:

From ChatGPT Developer mode, the VS Code MCP guide, GitHub Docs, AI SDK, and Mastra, read 2026-09-27.
ClientWhat its docs say
ChatGPT developer modeWrite actions need confirmation by default; it respects readOnlyHint, and tools without it are treated as write actions
VS CodeShows a confirmation dialog for all tools not marked with readOnlyHint; read-only tools run without one
GitHub Copilot code reviewUses a tool only if annotations.readOnlyHint is true; excludes it when missing or false
AI SDKExposes annotations on tool metadata but does not turn them into an approval policy automatically
MastraPasses annotations to a requireToolApproval function; relax approval with them only for servers you trust

What does Sume's MCP server set?

In current code, every tool on Sume's hosted server at https://mcp.sume.com/mcp carries all four hints. Sume's own gates sit on top of them: write and paid tools stay hidden until an OAuth session has mcp:write, and every write or paid call needs an idempotency_key. The values the code sets today:

  • Read tools such as tools_list, jobs_status, and jobs_wait: readOnlyHint: true, idempotentHint: true, destructiveHint: false.
  • Write and paid tools such as generate_video and video_trim: readOnlyHint: false and idempotentHint: false.
  • jobs_cancel is the only tool with destructiveHint: true. Every tool sets openWorldHint: false.
  • tools_list also returns a Sume safety block per tool, with fields such as read_only, paid_generation, and requires_idempotency_key, separate from the protocol annotations; Sume MCP tools list covers it.
  • So ChatGPT asks before a paid Sume tool but treats a status read as read-only; How to add an MCP server to ChatGPT shows that flow.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume