ax-check rule

AXC-F006: Claimed MCP tool is not served

The file names an MCP tool that the live server's tools/list does not return.

ax-check is a checker being prepared for release. This page documents the rule ahead of that release; see all 50 rules.

Severityerror
KindObserved. Depends on the program and environment at run time.
Modeax-check drift
Applies toInstruction files and manifests
Pattern tagsdrift, discovery, server-cards
Fix in one lineRename the claim to the served tool name, or restore the tool on the server.

A file names an MCP tool, but the live server’s tool list does not include it. An agent that reads the file will try to call a tool that is not there, or will hunt for it and waste turns.

What it checks

When you pass --mcp <url>, ax-check connects to the server over Streamable HTTP. It sends initialize and then tools/list. Both are read-only. Passing --mcp is your explicit request for that connection, so it works without --online. If the server needs a header, add it with --header "Authorization: Bearer ...". ax-check compares the tool names in the live answer with the tool names claimed in your files. The rule fires for every claimed name that the server does not return. When a served tool has a name within a small edit distance of the claim, the message adds “Did you mean …”, which usually catches a typo or a small rename.

Why it matters

Tool names are exact. The MCP specification says tool names are case-sensitive, so create_invoice and Create_Invoice are different tools (MCP specification, Tools). Instruction files, skills and manifests are written once and often outlive a rename. When a tool is renamed from create_invoice to invoice_create, every file that still uses the old name tells agents to call something that fails. Guidance on catalogues of tools says the same thing: what the files promise and what the server serves should be one list (Agent tool catalogs).

How to fix

  • Rename the claim in the file to the name the server actually serves.
  • Or restore the tool on the server, if it was removed by mistake.
  • If the tool was removed on purpose, delete the sentence that tells agents to use it, and say what to use instead.

Example

Find it:

ax-check drift --mcp https://mcp.example.com/mcp AGENTS.md

Before

# AGENTS.md

## Invoice tools

Use the `create_invoice` tool to draft an invoice.
Use `list_invoices` to find existing ones.
Use `void_invoice` to cancel one.

The live server returns invoice_create, list_invoices and invoice_void.

After

# AGENTS.md

## Invoice tools

Use the `invoice_create` tool to draft an invoice.
Use `list_invoices` to find existing ones.
Use `invoice_void` to cancel one.

How ax-check detects it

The extraction and the rule logic are deterministic, but the result depends on what the live server returns from tools/list at the time of the run, so the same file can give different findings on another day. JSON and SARIF reports record when and where the check ran. In Markdown, ax-check reads backticked identifiers in snake_case (lower case, with at least one underscore, such as create_invoice or create_invoice()) when the line, or the heading above it, contains the word “tool” or “tools”. It ignores fenced code blocks for this purpose. In JSON files it reads the strings in a tools array and the name of each object in a tools list.

It deliberately does not claim a tool for every backticked word. A single word such as search, a kebab-case name, or a camelCase name is not read as a tool claim, to avoid false alarms.

A snake_case name is often a parameter, not a tool, so two filters keep parameters out of the claims.

  1. Syntax in the file. A name is read as a parameter, and not as a tool claim, when:
    • it is an argument in call syntax: in get_invoice(invoice_id) the tool is get_invoice and invoice_id is a parameter;
    • it is written as tool.param, such as get_invoice.invoice_id;
    • it follows or precedes a word such as “argument”, “parameter”, “field”, “option” or “key” (for example “the argument invoice_id”, “invoice_id is a required parameter”, or a list such as “parameters: a_b, c_d and e_f”), or follows --;
    • it is a key of an inline JSON object, such as {"invoice_id": "x"}, or a key anywhere in a fenced json block in the same file. Keys under tools, servers or mcpServers are not skipped, because those are named things.
  2. The live schema. With --mcp, every property name in any served tool’s inputSchema (including nested objects, array items and $defs) is known to be a parameter. A claimed name that is not a served tool but does match one of these property names is treated as a parameter and is not reported. This catches a parameter that the prose mentions with no helpful words around it, such as “the tool also reads cli_help”.

A name that is both a served tool and a property is still a served tool.

Known limits:

  • A snake_case word that is not a tool and is not caught by the two filters (a config key such as max_retries in a “tools” section, when no served tool has a property of that name) is treated as a claim. Move it out of that section, or silence the rule.
  • The schema filter can hide a genuinely missing tool whose name equals a property name of another tool. This is rare, and the cost is a missed finding, not a false alarm.
  • A tool whose name has no underscore is not checked.
  • A server that lists different tools to different callers (for example after sign-in) may not serve a tool to this unauthenticated check.

If you do not pass --mcp, this rule does not run. To see what would be compared without connecting, add --dry-run. To silence it, use --disable AXC-F006.

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.

  • EV-0013: FAQ blocks and structured data showed no citation effect within a domain (Preprint). An observational study of about 2 million AI-engine citations (preprint) found that FAQ blocks, structured data and Core Web Vitals had positive effects on citation in pooled data that reversed or fell to zero once domain fixed effects were applied.
  • EV-0019: Skill selection precision collapses as the skill pool grows (Preprint). A preprint reports that as the pool of available skills grew from 5 to 100, the precision with which agents actually used the right skill fell from 29.6% to 3.3%.
  • EV-0023: Hiding tools is not enforcing permissions (Preprint). Across 2,160 attempts with four frontier models (preprint), a server with only in-body permission checks exposed forbidden tools in 152 of 720 trials and permission-aware visibility cut that to 0 of 720, yet models named a hidden tool in up to 94% of settings when it was inferable from the prompt.
  • EV-0027: A capable agent skipped the index and guessed the page (Preprint). A preregistered ablation on a 709-page Markdown wiki (preprint) found that a capable tool-using agent never loaded the compact catalogue index, inferring page paths from the question instead, while retrieval-based access kept answer quality non-inferior and cut cost by about a third to over half.
  • EV-0039: Launch post and documentation disagree on agent output format (Independent measurement). Cloudflare's cf launch post says JSON output is 'condensed for agents', but the cf documentation says JSON output is indented whether or not output is a terminal, and a public issue reports byte-identical output with an agent detected.
  • EV-0040: Agent skills recommended a package that does not exist (Vendor measurement). Merged pull requests in Vercel's agent plugin repository corrected skill instructions that recommended an npm package that is not published, and plugin guidance that advertised deployment cards the production MCP server does not expose.