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.

Severityerror
KindHeuristic. A pattern match: a prompt to look, not a verdict.
Modeax-check lint
Applies toMCP tool lists
Pattern tagsapproval, confirmation, description
Fix in one lineMake 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

  1. 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”).
  2. 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_status reads as “status” and email_lookup as “lookup”.
  3. 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.
  4. If readOnlyHint is true and a write verb counts (create, update, send, delete, archive, publish, charge and many more), the rule fires. So get_report described as “Deletes the report after reading it” is reported, while email_lookup described as “Gets the email addresses on file” is not.
  5. Otherwise, if destructiveHint is false, readOnlyHint is 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

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.