Build1 publisher3 min readPublished
Leaving readOnlyHint unset is what makes a missing destructiveHint default to true
The MCP schema defaults destructiveHint to true, but only where readOnlyHint is false. A ChatGPT app directory scanner asked for the field anyway on four read-only tools, and the tools came out of it behaving exactly as before.
The Engineer · Build desk

What happened
- A scanner in Eugeniya Ivanova's ChatGPT app directory review went through every tool on her MCP server and required explicit readOnlyHint, openWorldHint and destructiveHint values on each one.
- Four of her read-only tools omitted destructiveHint, an omission the specification permits; she added destructiveHint: false and wrote justifications for forty-odd annotation values, and the tools behaved the same afterwards.
- The ToolAnnotations interface is declared at line 1912 of schema/2026-07-28/schema.ts in the specification repository at commit 271ecc9, fetched on 2026-09-07.
- A client implementing the documented defaults must treat a tool that ships no annotations as not read-only, destructive, non-idempotent and open-world.
- A file-by-file count of seven source trees, probed on 2026-09-07, found four that returned zero for all four annotation field names.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint A reviewer demanding destructiveHint on every tool is asking for a value the schema calls meaningless on read-only tools, so authors clear the gate by adding annotations the spec would have them leave out.
- exposure A server author who annotates nothing cannot predict handling: one conforming client gates the call as destructive and another passes it through, both citing the same file.
- decision Declaring readOnlyHint: true or declaring nothing is now the choice that sets what every other default does, because readOnlyHint is the field that switches destructiveHint on.
- cost Whoever ships an MCP server into an app directory pays for optional fields in review-cycle time, writing per-value justifications while the tools keep behaving exactly as they did.
Line 1929 of the same file decides how an omission gets read. It says destructiveHint "is meaningful only when readOnlyHint == false" [11]. On a tool that declares readOnlyHint: true and stops there, leaving destructiveHint out is exactly right, and a client that reads true into it has made an error [12]. On a tool that declares nothing at all, readOnlyHint falls to its own default of false, that makes destructiveHint meaningful, and its default of true applies [13]. The same omission is correct in the first server and destructive in the second, because of a second field the tool also left unset.
Directly above the interface, at lines 1903 to 1908, the schema says all properties in ToolAnnotations "are hints" and "are not guaranteed to provide a faithful description of tool behavior". Clients, it adds, "should never make tool use decisions based on ToolAnnotations received from untrusted servers" [9]. Take a memory server whose save_memory tool ships no annotations. A client implementing the defaults gates that call as destructive, and a client reading the absent object as "no annotation gate applies" routes it straight through. Both of those clients are behaving correctly [25]. The dev.to walkthrough describes this as a schema that supplies defaults for a field it also instructs clients not to decide on, and says both halves are in force [26].
Himanshu Kumar audited a deployed server on 2026-08-30 and found six tools, all six of them annotation-free [5]. "The server did not lie. It said nothing," he wrote [6].
The scanner in Ivanova's review was stricter than the schema on exactly this point. Four of the tools it flagged were read-only, so line 1929 makes destructiveHint meaningless on them, and the value she added to clear the review is one the spec leaves optional [3][12]. Her account covers one review, published on dev.to on 2026-09-07 [1].
How common the silent tool is depends on a count whose method needs checking. The dev.to author downloaded each project's branch archive at a named commit, walked every regular file, and counted case-insensitive byte occurrences of the four field names. The walk was not restricted to paths containing "mcp", because that filter would have missed the assertions in mem0's tests/test_memory_core.py [14]. Case matters too: the Go SDK capitalizes the same fields, so a case-sensitive grep returns a false zero on any Go server [15]. In the four trees that returned zero, the same pass counted the word "annotations" 115 times in zep, 30 in graphiti, 210 in cognee and 36 in Memori [17].
Three of those four ship an MCP server in the same repository. zep registers 13 tools through mcp.AddTool under mcp/zep-mcp-server [18], graphiti registers 13 through @mcp.tool() decorators in mcp_server/src/graphiti_mcp_server.py [19], and cognee's sits under cognee-mcp/src [20]. Between them, zep and graphiti register 26 tools in trees where the count returned zero for all four field names [24]. Memori is a different case: the 6 registerTool calls under integrations/openclaw/src/tools belong to an OpenClaw plugin, and its MCP server is in a separate repository, MemoriLabs/memori-mcp [21].
All a zero in that count says is that those four strings are absent from the published source of that tree. For it to mean the deployed tools are unannotated, the registration code has to sit in the tree that was walked, and Memori shows that condition failing. If your own server registers tools through a wrapper that never touches these four fields, a client implementing the defaults applies all four defaults to every tool you expose [10]. A client following the NOTE applies none of them [9]. The dev.to author left his own published package out of the count. His reason was that measuring his own tree by a method he chose is not a comparison a reader should have to trust [23].
What to watch
- Whether the ChatGPT app directory publishes the annotation rule its scanner enforced, which is so far known only from Ivanova's walkthrough.
- Whether zep or graphiti add any of the four annotation fields at their tool registration sites in a commit later than the 2026-09-07 probe.
- Whether a future schema revision narrows the destructiveHint default now that a review scanner treats the permitted omission as a defect.