ax-check rule
AXC-D009: Side-effect annotation contradicts the description
readOnlyHint is true, or destructiveHint is false, while the opening verb of the description (or of the name) says it deletes, sends, creates or changes something.
ax-check is a checker being prepared for release. This page documents the rule ahead of that release; see all 50 rules.
| Severity | error |
| Kind | Heuristic. A pattern match: a prompt to look, not a verdict. |
| Mode | ax-check lint |
| Applies to | MCP tool lists |
| Pattern tags | approval, confirmation, description |
| Fix in one line | Make the annotations match what the tool does, and enforce the boundary in the server, not only in hints. |
The tool’s annotations say it is safe, but its name or description says otherwise. Either readOnlyHint is true while the tool creates, sends or changes something, or destructiveHint is false while the tool deletes, cancels or overwrites something. A client that trusts the hints may skip a confirmation the user should have seen.
What it checks
ax-check finds the action verb of each MCP tool, from its name and from the first sentence of its description, and compares it with the tool’s readOnlyHint and destructiveHint.
Why it matters
The MCP schema defines readOnlyHint as “If true, the tool does not modify its environment” and destructiveHint as “If true, the tool may perform destructive updates … If false, the tool performs only additive updates.” Clients use these hints to decide how much friction to put in front of a call, for example whether to ask the user first.
The specification is also clear that hints are not a guarantee: “All properties in ToolAnnotations are hints. They are not guaranteed to provide a faithful description of tool behavior.” It says clients “MUST consider tool annotations to be untrusted unless they come from trusted servers.” So a wrong hint does harm in exactly the setting where it is trusted: your own server, inside your own client. A contradiction between hint and description is almost always a copy-and-paste mistake, which is why this rule is an error. See Human approval workflows for AI agents.
How to fix
Make the annotations match what the tool does. Then enforce the boundary in the server as well, for example by giving a read-only tool a read-only credential. The tool descriptions guide recommends writing the boundary in the description and enforcing it in the server, because hints alone are untrusted.
Example
Before
{
"name": "archive_invoice",
"description": "Archives an invoice and removes it from the customer's open balance.",
"annotations": { "readOnlyHint": true }
}
After
{
"name": "archive_invoice",
"description": "Archives an invoice and removes it from the customer's open balance. It can be undone with restore_invoice.",
"annotations": {
"readOnlyHint": false,
"destructiveHint": false,
"idempotentHint": true,
"openWorldHint": false
}
}
How ax-check detects it
- The description verb is the first word of the first sentence, after common lead-ins such as “This tool will”, “Use this to”, “Lets you” and “Allows you to” are removed, and after light stemming (“Deletes” becomes “delete”).
- The name verb comes from the words of the tool name (split at underscores, hyphens, dots and camelCase) that are in ax-check’s lists of read verbs and write verbs. Normally the first such word is used. Some write verbs are just as often nouns in names: order, message, email, comment, tag, label, book, share, import, schedule, close, lock, block, post, patch, push, commit, merge, transfer, charge, refund, invite, archive, update, install and set. When the first verb-like word is one of these and the name has another, the later word is used, so
order_statusreads as “status” andemail_lookupas “lookup”. - The description verb is the stronger statement. A write or destructive verb counts when the description opens with it. A write or destructive verb from the name counts only when the description does not open with a read verb such as get, list or search.
- If
readOnlyHintis true and a write verb counts (create, update, send, delete, archive, publish, charge and many more), the rule fires. Soget_reportdescribed as “Deletes the report after reading it” is reported, whileemail_lookupdescribed as “Gets the email addresses on file” is not. - Otherwise, if
destructiveHintis false,readOnlyHintis not true, and a destructive verb counts (delete, remove, destroy, drop, purge, erase, wipe, truncate, overwrite, revoke, reset, cancel, kill, terminate, uninstall, ban, unshare), the rule fires.
Known false positives: a description that opens with a noun which is also a write verb, such as “Email addresses on file for a customer”, reads as “email” and is reported when readOnlyHint is true. Start the description with its real verb (“Returns the email addresses …”), or silence the rule with --disable AXC-D009.
Known false negatives: only the opening verb is read, so a side effect mentioned later, as in “Returns the report and then deletes it”, is missed. A tool named delete_report whose description opens with “Gets” is also not reported, because a read verb at the start of the description outranks the name.
Sources
- Specification: Model Context Protocol schema reference, ToolAnnotations, revision 2026-07-28. https://modelcontextprotocol.io/specification/2026-07-28/schema . Defines
readOnlyHint(default false) anddestructiveHint(default true), and says all annotations are hints, not a faithful description of behaviour. - Specification: Tools, Model Context Protocol specification, revision 2026-07-28. https://modelcontextprotocol.io/specification/2026-07-28/server/tools . Says clients “MUST consider tool annotations to be untrusted unless they come from trusted servers.”
- Site guide: Write tool descriptions an agent can act on, agentexperience.tech. https://agentexperience.tech/insights/tool-descriptions/ . Recommends writing the boundary in the description and enforcing it in the server.
Related evidence
Records in the AX evidence register that share a pattern tag with this rule. A shared tag means the record is about the same pattern, not that it tests this rule. Read the evidence class before the number.
- EV-0014: Allow/ask/never policies blocked less overreach than per-action approval (Preprint). In a study of 113 people without software backgrounds (preprint), user-authored allow/ask/never policies blocked 20.1 percentage points less agent overreach than per-action approval, partly because participants chose 'ask' for 114 of 140 rules and then approved most overreach at runtime.
- EV-0016: Approval records omit the effects a command goes on to trigger (Preprint). A preprint reports that coding-agent approval records name the approved command but omit effects its workflow exercises: across 111 approval and trace pairs, unrecorded residual effects fell from 40 with explicit fields to 17 with command semantics and 13 with decision-time metadata.
- EV-0034: Vendor claim: users approved about 93% of permission prompts (Vendor claim). Anthropic states that its telemetry showed users approved roughly 93% of Claude Code permission prompts, and that an operating-system sandbox reduced permission prompts by 84%.
- EV-0001: An enum in the schema ends silent failures from example-only vocabularies (Preprint). SilentProbe (preprint) reports that a vocabulary a parameter description only exemplified ("e.g.") was missed on 88 of 88 attempts across twelve models, and that promoting it into the schema cut the failure to 0 of 89.
- EV-0003: Error text that names the next tool lifts recovery (Preprint). A preprint testing five OpenAI models reports that an expired-credential error naming a terminal command left 45% of tasks recovered, naming the server's login tool instead raised recovery to 84%, and on rate limits naming the call to repeat raised recovery from 6% to 88%.
- EV-0015: Approvals that outlive their task raise attack success (Preprint). A preprint reports that approvals persisted beyond the context that justified them raised prompt-injection attack success by up to 35.1 percentage points on 508 AgentDojo cases, and by 24.9 points on average in live tests on three production coding agents.
