DEV Community

Jeff
Jeff

Posted on Originally published at powerduck.com

Marking MCP Tools Read-Only or Destructive with Annotations So the Agent Asks First

Quick answer: MCP tool annotations let a server declare whether a tool is read-only, destructive, idempotent, or open-world, so an agent can decide whether to call it freely or ask for confirmation. They are hints, not enforcement; the server still has to authorize and validate every write. If you expose a tool that deletes or modifies data without marking it, the agent has no signal that it should pause, and a casual prompt can trigger a destructive call.

Where annotations fit in the model

An MCP tool already has a name, description, and input schema. Annotations add metadata about the tool's behavior without changing how it is invoked. The model reads them alongside the description and uses them to judge risk and plan the interaction. The key distinction is that annotations guide the agent's decisions, while your server-side checks are the real security boundary.

Annotation Type What it tells the agent
readOnlyHint boolean The tool does not modify any state
destructiveHint boolean The tool may destroy an irreplaceable resource
idempotentHint boolean Repeating the call has the same effect as one
openWorldHint boolean The tool interacts with external, unbounded entities

Declaring the annotations on a tool

Here is a server-side example showing two tools with different risk profiles. The delete tool is marked destructive and non-idempotent, while the read tool is safe to call freely.

A read-only counterpart flips the flags. A tool that lists customers sets readOnlyHint to true and leaves destructiveHint false, which tells the agent it can call it during planning without asking.

How the agent uses the hints at runtime

When a user says "clean up the test accounts," the agent may plan to call delete_customer several times. With a destructive hint, a well-behaved client surfaces the plan and asks for confirmation before the first deletion rather than executing immediately. For read-only tools, the agent can gather information without an interruption. The hints also support better retries: an idempotent tool is safe to retry after a timeout, while a non-idempotent write needs the agent to check whether the first call landed before sending it again.

Why hints are not authorization

Nothing in the protocol forces a client to obey annotations, and a malicious or buggy client can ignore them. Treat them as user experience and planning signals, never as a control. The server must still authenticate the caller, scope the operation to what that identity is allowed to do, and validate the input. A common mistake is to mark a tool destructive and then assume the client will always confirm, skipping the permission check on the server. The correct design layers both: the annotation produces a confirmation prompt, and the server rejects a deletion the caller is not entitled to make.

Choosing the hints honestly

Overstating safety is more dangerous than overstating risk. If a tool writes to an external system, do not mark it read-only just because it looks like a lookup; a tool that sends an email or triggers a webhook is a write even if it does not change your database. The openWorldHint exists for exactly these cases where the tool reaches entities outside the closed set the server controls. When a tool's behavior depends on its input, prefer the conservative hint and document the condition in the description rather than claiming the tool is always safe.

Generating annotations from a contract

When an MCP server is generated from an OpenAPI description, the HTTP method gives a strong starting signal. GET and HEAD map naturally to read-only, DELETE maps to destructive, and PUT tends to be idempotent while POST is not. A generator can prefill the annotations from the method and let you override the exceptions, which is more reliable than hand-tagging every tool and forgetting the risky one. Review the generated output, because a POST that performs a search is effectively read-only and a GET that triggers a server-side action is not.

Confirming destructive calls in the user interface

A good client does more than ask a generic yes-or-no question. It should show the resource the call will affect, the count of items when a tool operates in bulk, and any irreversible consequences, so the user can confirm the real plan rather than a vague prompt. When you review a client's behavior, check that the confirmation carries the rendered input and the affected identifier rather than only the tool name, because a confirmation without context is trivially accepted and adds little protection.

Frequently asked questions

Are tool annotations enforced by the protocol?

No. They are hints that influence how a client presents and plans tool calls. Enforcement belongs on the server through authentication, authorization, and input validation, which apply regardless of what the client does.

What happens if I omit the annotations?

The client has no explicit risk signal and may treat every tool the same, potentially calling a destructive operation without a confirmation step. Omitting hints does not break calls, but it removes the information that supports safe, well-planned behavior.

Can a tool be both read-only and destructive?

Generally no. A read-only tool does not modify state, so it cannot destroy anything. If a tool looks like a read but has side effects, that is a sign the operation is mislabeled and should be marked as a write.

How does idempotentHint help beyond confirmations?

It drives retry logic. After a timeout or ambiguous response, an idempotent tool can be safely re-invoked, while a non-idempotent one requires the agent to verify the first outcome. Accurate hints prevent duplicate writes during flaky network conditions.

This is part of the The MCP Server Guide — start there for the full set of design decisions behind safe, reliable tool exposure.

Top comments (0)