deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

MCP's annotation defaults make unannotated tools implicitly destructive

A dev.to analysis shows the MCP spec's ToolAnnotations defaults treat every unannotated tool as destructive and open-world, and most popular memory servers ship no annotations at all.

MCP's annotation defaults make unannotated tools implicitly destructive

The Model Context Protocol specification gives its four tool annotations documented defaults, and the one for destructiveHint is true. A dev.to analysis published on 12 September 2026 works through what that means in practice: an MCP tool that ships with no annotations has not stayed neutral about its behaviour. Under the schema's own defaults, it has effectively declared itself non-read-only, destructive, non-idempotent and open-world.

What the spec says

According to the dev.to piece, the ToolAnnotations interface in the schema dated 2026-07-28 (commit 271ecc9) defines four booleans with defaults. When readOnlyHint is absent, a client must assume the tool modifies its environment. When destructiveHint is absent, the tool may perform destructive updates. When idempotentHint is absent, repeating the call has further effect. When openWorldHint is absent, the tool interacts with external entities.

Directly above that interface, however, sits a note stating that all annotation properties are merely hints, not a guaranteed description of tool behaviour, and that clients should never make tool-use decisions based on annotations from untrusted servers. The schema therefore supplies defaults for fields it simultaneously tells clients not to trust, and the gap between those two halves is the heart of the problem.

One omission, two readings

This leaves two lawful interpretations of the same silence. A client that applies the documented defaults will gate an unannotated tool as destructive. A client that reads the missing object as a lack of information will route the same call straight through. Neither client is misbehaving.

There is a second-order rule as well: destructiveHint only carries meaning when readOnlyHint is false. A tool that declares readOnlyHint true and omits destructiveHint is doing the correct thing, while a tool that declares nothing at all falls through to readOnlyHint's default of false, which activates destructiveHint's default of true. The same omission is clean in one case and loaded in the other, and what separates them is a second field the tool also left blank.

What the census found

The dev.to analysis anchors this in two field reports it cites. Eugeniya Ivanova's 7 September walkthrough of passing the ChatGPT app directory review describes a scanner that demanded explicit annotation values even though the spec marks them optional; she ended up adding destructiveHint false to four read-only tools and writing justifications for roughly forty values, without changing the tools themselves. On 30 August, Himanshu Kumar audited a deployed server with six tools, none declaring any annotation — his verdict being that the server was not lying, merely silent.

The author then counted case-insensitive occurrences of the four field names across every file in seven source trees, probed on 7 September 2026. The reference modelcontextprotocol/servers repository contains dozens of mentions (61, 51, 51 and 60 across the four fields). supermemoryai/supermemory shows six of each, mem0ai/mem0 almost none, and four trees — getzep/zep, getzep/graphiti, topoteretes/cognee and MemoriLabs/Memori — contain zero occurrences of any field name, despite shipping MCP servers: zep registers 13 tools, graphiti exposes 13 bare @mcp.tool() decorators, and Memori's server lives in a separate repository that also reads zero.

The census comes with honest caveats. Supermemory's count of six reflects four preset constants imported by fifteen tool files, so literal counting undercounts its annotated tools by roughly two and a half times. Mem0's missing destructiveHint is actually correct, because its single tool declares readOnlyHint true and the field is meaningless in that case. Graphiti is the starkest example: thirteen unannotated tools covering both clearly destructive operations (clear_graph, delete_entity_edge, delete_episode) and clearly safe ones (search_nodes, get_episodes, get_status). A client honouring the defaults treats all thirteen identically — accurate for the first group, wrong for the second.

The practical takeaway is that source code is not the wire: the piece recommends probing a running server's tools/list response, which three JSON-RPC lines on stdin will reveal, to see which fields each tool actually omits.

Why it matters

For agent developers, the counter-intuitive part is that shipping no annotations is not the safe, minimal choice — under the documented defaults it is the loudest possible claim, asserting destructiveness, non-idempotence and open-world reach. As clients and review scanners begin honouring the defaults, every unannotated read-only tool risks being gated or surfaced as dangerous. Conversely, because the spec tells clients not to trust annotations from untrusted servers, developers cannot rely on the hints being honoured at all. The cheap fix is to declare all four fields explicitly on every tool, which removes the ambiguity regardless of which reading a client implements.

  • #mcp
  • #model-context-protocol
  • #ai-agents
  • #developer-tools
  • #api-design

Related posts