Logan Kelly
In MCP, whether a tool is safe to call twice is a hint the client must distrust. Waxell MCP Gateway decides it per call, before dispatch.

The current Model Context Protocol specification, revision 2026-07-28, gives a client exactly four booleans with which to reason about what a tool will do to the world. One of them, idempotentHint, answers the question a retry loop depends on: is it safe to call this again with the same arguments? The same specification attaches a normative instruction to all four: "For trust & safety and security, clients MUST consider tool annotations to be untrusted unless they come from trusted servers."
That is not a gap in the protocol. It is the protocol stating its own scope correctly. The architectural consequence is the part most agent stacks have not absorbed: the field that says a call is safe to repeat is supplied by the party on the other end of the call, and the specification tells you not to believe it.
> Tool annotations are optional, server-declared metadata on an MCP tool definition that describe how the tool behaves — whether it is read-only, whether it is destructive, whether it is idempotent, and whether it reaches an open world of external entities. The MCP schema is explicit that they are hints: "all properties in ToolAnnotations are hints. They are not guaranteed to provide a faithful description of tool behavior." They are a risk vocabulary for a client to reason with, not a guarantee a client can rely on.
That distinction matters because repeating a call is now a first-class part of the stack. Durable-execution runtimes retry failed steps automatically, and the 2026-07-28 MCP revision specifies a retry flow of its own.
What do MCP's four tool hints actually say?
Read from the schema reference of the current revision, with the defaults, all four:
readOnlyHint — "If true, the tool does not modify its environment." Default: false.
destructiveHint — "If true, the tool may perform destructive updates to its environment. If false, the tool performs only additive updates." Meaningful only when readOnlyHint is false. Default: true.
idempotentHint — "If true, calling the tool repeatedly with the same arguments will have no additional effect on its environment." Meaningful only when readOnlyHint is false. Default: false.
openWorldHint — "If true, this tool may interact with an 'open world' of external entities. If false, the tool's domain of interaction is closed." Default: true.
Those defaults are a deliberate posture. In a March 2026 post on the protocol's own blog, co-authored by an MCP maintainer with authors from GitHub and AWS, the design is summarised plainly: "a tool with no annotations is assumed to be non-read-only, potentially destructive, non-idempotent, and open-world. The spec assumes the worst until told otherwise."
So an unannotated tool is already, by specification, presumed unsafe to retry. The interesting case is the annotated one — where a server has told you it is safe, and you have been instructed not to take its word for it.
Why "safe to retry" is the hint that moves
Retries are not an edge case in agentic architecture. They are the mechanism by which durability is delivered.
Temporal's documentation states the contract in its own words: "If an Activity attempt fails, it is automatically retried using its Retry Policy. Each attempt starts from the initial state, unless your code uses a Heartbeat detail payload for checkpointing." And then, on the same page, the obligation that falls out of it: "We recommend that it be idempotent, so retries can be processed without duplicate side effects."
That recommendation is addressed to the author of the activity — someone who controls the code being retried. An agent calling a third-party MCP tool is in a different position entirely. It does not own the tool and did not write it, and MCP's machine-readable statement about whether the tool can absorb a second call is a boolean the tool's publisher set.
The protocol itself now carries a retry shape. The 2026-07-28 revision describes a multi-round-trip flow in which a call that needs more input is re-sent with the earlier context attached, and it is specific about the mechanics: "the JSON-RPC id MUST be different between the initial request and the retry." Retrying a tool call is a normal, specified part of speaking the protocol.
Stack the two together and the shape of the problem is clear. The layer that decides to retry is the agent framework. The layer that knows whether retrying is safe is the upstream tool. The only thing connecting them is a self-reported hint the specification classifies as untrusted. Two layers, one boolean, no contract.
Risk is a property of the session. MCP is stateless by design.
The second half of the architecture problem is scope, and both halves of it are stated in the protocol's own material.
The maintainers' post makes the risk argument directly. Discussing Simon Willison's "lethal trifecta" — private data access, exposure to untrusted content, and the ability to communicate externally — it describes a demonstrated attack chain assembled from a malicious calendar event description, an MCP calendar server and a local code-execution tool, then draws the conclusion that matters structurally: "users routinely combine tools from several servers in one session, so the risk profile is a property of the session, not of any single server." It is blunt about the limit this puts on per-tool metadata: "search_emails isn't safe or dangerous on its own; it depends on what other tools the agent has. Annotations on one tool can't tell you that."
Now put that next to the protocol's own security guidance, which states the design directly: "MCP is stateless and has no protocol-level sessions. Servers that need state spanning multiple requests mint an explicit handle, such as a shopping cart ID or a workflow ID, and receive it back as an ordinary tool argument on each request."
Those two sentences are the whole architectural argument. Risk accrues at session scope. The protocol holds no session state. Therefore the thing that evaluates risk has to be a component that is neither the agent nor the server — one that watches a sequence of calls, holds counters and history across them, and reaches a decision on the call in front of it. That is an architecture requirement rather than a product preference, and it is the same requirement that produced API gateways, web application firewalls and identity-aware proxies in earlier waves of integration growth.
Where the contract belongs
The most useful sentence in that post is the one that says where the enforcement belongs, and it comes from the people who own the protocol:
> "Is it a hint or a contract? Hints inform decisions; contracts enforce them. If a proposal's value depends on the annotation being true, it's asking for a contract, and the right place for that is the authorization layer, the transport, or the runtime rather than ToolAnnotations."
The accompanying advice to client authors is the same ruling in operational form: "treat annotations from untrusted servers as informational and lean on them for UX, but keep your actual safety guarantees in deterministic controls." The post also sketches the hybrid: "Annotations can be one input among several into a policy engine enforcing rules like 'no destructive tools without approval' or 'open-world tools are blocked in sessions that have accessed private data.' The hints don't need to be perfectly trustworthy if the engine cross-references other signals."
That is the design. Keep the hints — they are cheap and frequently right. Stop treating them as the decision. The decision belongs to something that evaluates the call against rules you wrote, with state the protocol does not carry, at a point where refusing is still possible. The post describes adoption of the lighter-weight version as "largely unexploited today," and names GitHub's read-only mode as the closest production analog, enabled by about 17% of users.
Specification Enhancement Proposals are open against the annotation set as of that post, and a Tool Annotations Interest Group — participants from Microsoft, OpenAI, AWS, Cloudflare and Anthropic among others — is forming to work through them. Richer vocabulary is coming. It will still be vocabulary.
How Waxell handles this: Waxell MCP Gateway is one MCP endpoint per tenant that agents point at in place of their upstream server list, which puts a decision point at the layer the argument above points to. It requires no changes to existing agent code: the edit is swapping the upstream entries in a client config for the gateway URL. Per the gateway's policy rules documentation, every tools/call is evaluated before dispatch against the tenant's rules, and results are evaluated again on the way back. Rules scope by upstream, tool glob, user, role, team and the agent profile that made the call — so the same person gets different rules depending on which agent is acting for them. The starter rule set in those docs is the direct answer to a self-declared destructiveHint: deny on *delete* and *drop* across all upstreams, which is the tenant asserting what counts as destructive rather than asking the publisher. require_approval parks a call for a human reviewer with the exact arguments visible, under a per-rule timeout with a configured decision on expiry, and precedence is fixed at deny > require_approval > redact > allow. For the retry problem specifically, rate_limit caps calls per window per matching scope as a runaway-loop backstop, and its counters are shared across gateway replicas — cross-call state the protocol does not hold. deny_drift blocks tools whose fingerprint changed since they were last approved, and a newly discovered tool cannot be called until an admin approves it. Every decision — allow, deny, redaction, approval, and the rule that fired — is recorded in the audit log. These sit among the 50+ policy categories Waxell ships with, which live in the governance plane, are applied by reference rather than copied into agent code, and are evaluated deterministically before execution proceeds.
FAQ
What are MCP tool annotations?
They are optional metadata a server attaches to a tool definition to describe its behaviour. The current schema defines four: readOnlyHint, destructiveHint, idempotentHint and openWorldHint. The schema reference states that all of them are hints and "are not guaranteed to provide a faithful description of tool behavior," and the specification instructs clients to consider annotations untrusted unless they come from trusted servers.
Does idempotentHint mean a tool is safe to retry?
It means the server says so. The schema defines it as "if true, calling the tool repeatedly with the same arguments will have no additional effect on its environment," and the maintainers' post maps it to the client behaviour "safe to retry on failure." Whether that holds is a property of the upstream implementation rather than of the annotation, which is why the default is false.
If annotations can't be trusted, why use them at all?
Because they are useful as one input among several. The maintainers' framing is that hints inform decisions while contracts enforce them, and that a client should "treat annotations from untrusted servers as informational and lean on them for UX, but keep your actual safety guarantees in deterministic controls."
Where should the enforcement point sit in an agentic architecture?
At a layer that sees the call and holds state across calls. The protocol's security guidance states that "MCP is stateless and has no protocol-level sessions," while the maintainers' post argues that risk "is a property of the session, not of any single server." That leaves the authorization layer, the transport or the runtime — the three places the maintainers nominate.
What does this change about how agents should retry tool calls?
Treat the retry decision as a governed action rather than a client-side convenience. The specification requires a retry to carry a different JSON-RPC id, so it reaches the upstream as a new request; a bound on repeats therefore has to be enforced somewhere that counts them — a rate limit over a window, scoped to the agent or the user.
Is an unannotated tool treated as safe?
No, and that is deliberate. The maintainers describe the defaults as assuming the worst until told otherwise: an unannotated tool is taken to be non-read-only, potentially destructive, non-idempotent and open-world. The practical difficulty is uneven coverage — the post notes that many servers ship without annotations and that clients vary in how strictly they honour the pessimistic defaults.
Sources
Model Context Protocol, "Tools" (specification revision 2026-07-28) — https://modelcontextprotocol.io/specification/2026-07-28/server/tools
Model Context Protocol, "Schema Reference" (specification revision 2026-07-28) — https://modelcontextprotocol.io/specification/2026-07-28/schema
Model Context Protocol, "Security Best Practices" (2026-07-28) — https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/security_best_practices
Hungerford, O., Morrow, S. & Chang, L., "Tool Annotations as Risk Vocabulary: What Hints Can and Can't Do" (Model Context Protocol Blog, 16 March 2026) — https://blog.modelcontextprotocol.io/posts/2026-03-16-tool-annotations/
Temporal, "What is a Temporal Activity?" — https://docs.temporal.io/activities
Waxell, "Policies & Approvals" — https://waxell.ai/docs/mcp-gateway/policies
Waxell, "AI Agent Policy Enforcement" — https://waxell.ai/capabilities/policies
Bring the tool list your agents are actually connected to and the retry behaviour of the framework calling them: book a 30-minute demo and see which calls the Waxell MCP Gateway would have stopped before dispatch.
Agentic Governance, Explained





