Nous et des tiers sélectionnés utilisons des cookies ou des technologies similaires à des fins techniques et, avec votre consentement, pour analyser l’utilisation du site.

Vous pouvez librement donner, refuser ou retirer votre consentement à tout moment.

MCP Tool Annotations: What readOnlyHint and destructiveHint Mean

MCP tool annotations explained: readOnlyHint, destructiveHint, idempotentHint, openWorldHint, their defaults, SDK syntax and why clients treat them as hints.

Cet article est en anglais.

TL;DR. MCP tool annotations are optional hints a server attaches to each tool in its tools/list response: readOnlyHint, destructiveHint, idempotentHint, openWorldHint and a display title. They tell a client whether a tool only reads, may delete, can be repeated safely, or reaches outside systems. The 2026-07-28 specification says clients must treat them as untrusted unless the server is trusted, so they shape approval prompts and display, not security. Leave them out and the defaults say "may be destructive" and "open world". mcp.tc shows the two main hints on every listing it can read and records on the History tab when they flip.

What are MCP tool annotations?

MCP tool annotations are an optional annotations object on each tool definition that a server returns from tools/list, carrying boolean hints about how the tool behaves (read-only, destructive, idempotent, open world) plus an optional title. The 2026-07-28 tools specification lists annotations as "Optional properties describing tool behavior", next to name, description, inputSchema and outputSchema.

On the wire a tool with annotations looks like this:

json
{
  "name": "delete_record",
  "title": "Delete a record",
  "description": "Permanently delete one record by id",
  "inputSchema": {
    "type": "object",
    "properties": { "id": { "type": "string" } },
    "required": ["id"]
  },
  "annotations": {
    "readOnlyHint": false,
    "destructiveHint": true,
    "idempotentHint": true,
    "openWorldHint": false
  }
}

The ToolAnnotations comment in the specification repository says that "all properties in ToolAnnotations are hints. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like title)." Nothing in the protocol checks them. A server can mark a tool read-only and then write; the hint is a claim the server makes about itself.

Content blocks in tool results and resources also have an annotations field, with audience, priority and lastModified. Same word, different object. This article is about the tool-level hints.

What do readOnlyHint, destructiveHint, idempotentHint and openWorldHint mean?

Each MCP tool annotation is a boolean with a default, and the defaults are easy to overlook: a tool with no annotations counts as possibly destructive and open world. The table uses the wording of the ToolAnnotations type in the 2026-07-28 schema.

HintMeaning when trueDefaultMeaningful when
readOnlyHint"the tool does not modify its environment"falsealways
destructiveHint"the tool may perform destructive updates to its environment"; false means "only additive updates"truereadOnlyHint is false
idempotentHint"calling the tool repeatedly with the same arguments will have no additional effect on its environment"falsereadOnlyHint is false
openWorldHintthe tool "may interact with an 'open world' of external entities"; false means its "domain of interaction is closed"truealways

The schema gives one example for the last hint: "the world of a web search tool is open, whereas that of a memory tool is not."

There is also annotations.title, "A human-readable title for the tool". The Tool type documents the display precedence as "title, annotations.title, then name". The top-level title wins, so a new server can set that and skip annotations.title.

Two hints are conditional. destructiveHint and idempotentHint both carry the note "(This property is meaningful only when readOnlyHint == false)". A read-only tool does not need them, and a tool that says readOnlyHint: true and destructiveHint: true at the same time hands the client a contradiction. Avoid writing that combination.

Why the defaults matter when you set nothing

A tool without an annotations object is, by the schema's defaults, not read-only, possibly destructive, not idempotent and open world, so a client that prompts on destructive tools will prompt before every call to it. That is the correct reading of the defaults, and it is why unannotated tools collect confirmation dialogs they do not need.

Take a small server with three tools:

  • search_items reads a catalog. Unannotated, it defaults to readOnlyHint: false and destructiveHint: true, so a client that confirms destructive calls will confirm every search.
  • add_item appends a row. The honest annotation is readOnlyHint: false, destructiveHint: false, idempotentHint: false: it writes, it does not delete, and calling it twice adds two rows.
  • delete_item removes a row by id. readOnlyHint: false, destructiveHint: true, idempotentHint: true: deleting the same id twice has no further effect.

Set readOnlyHint: true on the search and the other two hints stop applying to it. openWorldHint is per tool, not per server: a catalog search over your own database is closed world, a web search is not.

Public servers set these hints about half the time. Of the 1,567 tools in 113 listings mcp.tc could read on 4 October 2026, 775 (49%) carried at least one annotation, 629 (40%) were marked read-only and 77 (5%) destructive, according to the state of public MCP servers in 2026. The other 51% said nothing and so inherited the defaults above.

How do I set annotations in the TypeScript and Python SDKs?

In both official SDKs annotations are a field on the tool definition, and both SDKs' docs say the same thing about them: they change what the client shows, never what the server runs.

TypeScript SDK v2

The TypeScript SDK's tools page defines tools with registerTool(name, config, handler) and shows a destructive tool like this:

ts
import { McpServer } from '@modelcontextprotocol/server';
import * as z from 'zod/v4';

server.registerTool(
    'clear-catalog',
    {
        title: 'Clear the catalog',
        description: 'Remove every product from the catalog',
        annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true }
    },
    async () => {
        catalog.length = 0;
        return { content: [{ type: 'text', text: 'Catalog cleared' }] };
    }
);

The same page says annotations "never change how the SDK runs the tool" and that clients use them "to decide what to put in front of the end user: a host can auto-approve a read-only tool and require confirmation before a destructive one."

Python SDK

The Python SDK's tools page takes a ToolAnnotations object with snake_case field names (read_only_hint, destructive_hint, idempotent_hint, open_world_hint); on the wire the keys stay the camelCase ones from the specification.

python
from mcp.types import ToolAnnotations

@mcp.tool(
    title="Search the catalog",
    annotations=ToolAnnotations(read_only_hint=True, open_world_hint=False),
)
def search_books(query: str) -> str:
    """Search the catalog by title or author."""
    return f"Found 3 books matching {query!r}."

The page's own warning is short: "They are hints, not security. Never rely on a client honouring them."

What do clients do with tool annotations?

The specification tells clients what annotations are for without prescribing a user interface: keep a person able to deny calls, confirm sensitive operations, and never let an untrusted server's hints make the decision alone. The 2026-07-28 tools page says there "SHOULD always be a human in the loop with the ability to deny tool invocations", that applications SHOULD "Present confirmation prompts to the user for operations", and, under security considerations, that clients SHOULD "Prompt for user confirmation on sensitive operations" and "Show tool inputs to the user before calling the server".

The MCP server concepts page lists the controls an application can offer: showing which tools are available, "Approval dialogs for individual tool executions", "Permission settings for pre-approving certain safe operations" and activity logs. Annotations feed the middle two. A read-only hint is what makes a tool a candidate for pre-approval; a destructive hint is what makes it a candidate for a dialog on every call.

How a given client maps hint values to prompts is that client's choice and lives in its own settings. What a user ends up seeing depends on the hint values the server sent, the client's policy for each value (approve automatically, ask once, ask every time), and whether the client treats that server as trusted at all.

One rule applies to every client. The specification says "clients MUST consider tool annotations to be untrusted unless they come from trusted servers." A hint can lower the friction for a tool you already trust; it cannot create trust.

Can I trust a tool's annotations?

You can trust a tool's annotations exactly as far as you trust the server that sent them, because an annotation is a self-description with no enforcement behind it. The schema comment says "Clients should never make tool use decisions based on ToolAnnotations received from untrusted servers", and the Python SDK docs call them "hints, not security".

What "trusted" means is up to you and your client. Useful signals are who runs the server, whether its code is public, and whether its tool list stays stable. The verified checkmark on mcp.tc answers the first: it means either an official server of a well-known vendor checked by the admin, or that the owner of the server's domain added a DNS TXT record. It is not a security review, and it says nothing about whether a readOnlyHint is accurate. The fuller checklist is in Is this MCP server safe?.

Two quick checks when you first read a tool list:

  1. Compare each hint with the description. A tool described as deleting, sending or paying that claims readOnlyHint: true is a contradiction. A tool named get_ or list_ with no annotations is usually an omission, and it will prompt more than it needs to.
  2. Compare the hints with the sign-in scope. A server that asks for a write scope and marks every tool read-only deserves a second look.

How mcp.tc shows annotations and when they change

mcp.tc reads readOnlyHint and destructiveHint with each tool it can see, shows them on the listing page, and records on the History tab when they flip. The server owner docs list what the checker reads, "tools/list, with each tool's readOnlyHint and destructiveHint", and explain that these become "the Read-only, Writes and Can delete hints on the page". Without them, "the page can't say which tools only read".

Every 3 hours mcp.tc reads the tool list of each listed server whose tools it can read without signing in, and a change is recorded only after two reads agree. The change history docs count "hints that flipped, such as readOnlyHint or destructiveHint" as a change, alongside added and removed tools, changed descriptions, titles, schemas and server instructions. On 21 October 2026, 250 of the 944 listings on mcp.tc have a tool list that mcp.tc compares this way, and the history began on 6 October 2026. The newest changes across the directory are on the recent changes page, with an Atom feed at https://mcp.tc/changes.xml.

A flipped hint is worth noticing because your client's policy was set against the old value. A tool that was read-only when you approved it and is marked destructive now may prompt differently, or the client may keep an earlier decision. The 2026-07-28 changelog also says servers SHOULD return tools from tools/list "in a deterministic order" to help client-side caching, and list results now carry ttlMs and cacheScope; stable ordering also makes two reads easier to compare.

The limits are plain. The History shows what mcp.tc's own reads, made without signing in, saw at each check. Servers that ask for sign-in before they list their tools, and local servers without a bundle manifest, have no watched tool list, so their History holds only the versions mcp.tc can read and the edits their owner saves. It cannot see tools shown only after sign-in or only to some clients, and a change undone between two checks can be missed. It is not a security review. readOnlyHint and destructiveHint are labels a server gives its own tools: the History reports when they change, never what a tool can actually do.

If you run a server, npx mcp-tc check https://mcp.example.com/mcp reads it the way mcp.tc does and names the tools that carry no readOnlyHint or destructiveHint, so you can add the missing hints before you publish. The mcp-tc command-line tool needs Node.js 20 or later and is MIT licensed.

What you can do next on mcp.tc

The directory is the quickest place to see annotations in use. Open any listing with a readable tool list and the Read-only, Writes and Can delete hints sit next to each tool, so you know before adding the server which calls a careful client will confirm.

To hear when a hint flips, sign in with GitHub or an email code and press Follow on the listing. mcp.tc emails you when its checks find a change in a followed listing's tools or version, as changes happen (at most one email an hour), as a daily summary, or not at all, and every email has a link that stops them. You can follow up to 200 listings, and who follows what is not public.

If you maintain a server, annotate your tools, run npx mcp-tc check against the deployed URL, and then suggest it if it is not listed yet. Owners who prove their domain can edit the listing, and those edits appear on the History tab as "Server owner".

FAQ

Does readOnlyHint stop a tool from writing?

No. readOnlyHint is a claim the server makes about its own tool, and the MCP specification calls every annotation a hint that is "not guaranteed to provide a faithful description of tool behavior". The SDK does not enforce it and the client cannot verify it. A client uses the hint to decide whether to ask before calling; what the tool actually does is decided by the server's code. Treat the hint as trustworthy only when you trust the server.

What happens if I set no annotations on a tool?

The schema's defaults apply: readOnlyHint false, destructiveHint true, idempotentHint false and openWorldHint true. A client that follows those defaults treats the tool as one that may delete data and reach outside systems, and prompts accordingly. For a tool that only reads, setting readOnlyHint: true is the single most useful change, because the two write-related hints then no longer apply.

Should I use title or annotations.title?

Use the top-level title. The 2026-07-28 schema documents the display precedence as title, then annotations.title, then name, so a top-level title always wins. annotations.title remains in the schema and a client that only reads the older field will still show it, but a new server does not need both. The display name is also a hint and is not guaranteed to be accurate.

Does mcp.tc check whether a server's annotations are accurate?

No. mcp.tc reads each tool's readOnlyHint and destructiveHint and shows them as Read-only, Writes and Can delete on the listing, and its History tab records when a hint flips between two checks. It does not run the tools or verify that a read-only tool really only reads. The verified checkmark says who runs a server, not that its hints are correct, and the History is not a security review.

Do destructiveHint and idempotentHint matter on a read-only tool?

No. The schema marks both as "meaningful only when readOnlyHint == false". A tool that does not modify its environment has nothing to destroy and nothing to repeat, so clients can ignore the other two hints on it. Set readOnlyHint: true and leave the rest out. Setting readOnlyHint: true together with destructiveHint: true gives the client a contradiction and is best avoided.

Where can I see when a server changed a tool's annotations?

On mcp.tc, open the listing and its History tab (https://mcp.tc/i/{slug}#history), which lists dated changes found by mcp.tc's checks every 3 hours, including hints that flipped, added and removed tools and changed descriptions. The recent changes page collects the newest entries across the directory, each listing has an Atom feed at /history.xml, and following a listing sends the same changes by email. Only tool lists readable without sign-in are watched.

Sources

  1. modelcontextprotocol.io/specification/2026-07-28/server/tools
  2. modelcontextprotocol.io/specification/2026-07-28/schema
  3. github.com/modelcontextprotocol/modelcontextprotocol
  4. modelcontextprotocol.io/specification/2026-07-28/changelog
  5. modelcontextprotocol.io/docs/learn/server-concepts
  6. ts.sdk.modelcontextprotocol.io/v2/servers/tools.html
  7. py.sdk.modelcontextprotocol.io/servers/tools/
  8. github.com/mcptc/mcp-tc

Serveurs à essayer

Tous les serveurs
  • GitHub, vérifié

    Outils de développement

    Compte requis

    Travailler avec les dépôts, issues, pull requests, workflows Actions et la recherche de code GitHub depuis votre assistant IA.

    mcp.tc/i/github
  • Compte facultatif

    Récupérer la documentation de bibliothèques à jour et propre à chaque version, avec des exemples de code, directement dans le contexte de votre assistant.

    mcp.tc/i/context7
  • Sans compte

    Poser des questions sur n’importe quel dépôt GitHub public et lire sa documentation DeepWiki générée par IA.

    mcp.tc/i/deepwiki
Tous les articles