Skip to content

CLI and MCP argument sets have drifted: seven gaps, and nothing guards against the next one #58

Description

@vlsi

Comparing a live tools/list against --help on each subcommand, seven arguments exist on the CLI and not on the MCP tool that mirrors it. Every one of them is a lever an agent cannot reach — and the MCP schema is the only surface an agent has, with no --help to fall back on.

Tool On the CLI Over MCP
digest --detail, --no-private / -fields / -docs / -attrs / -lines, --preset still the pre-#37 pair include_private / include_fields
map --preset, --include-private, --include-fields absent
callers --tests, --exclude-tests absent
reverse-deps --tests, --exclude-tests absent
search --rebuild absent
context --rebuild absent
impact --rebuild absent
show <TARGET>..., several targets path is a single string

digest is the widest gap: #37 made map and digest one command with three orthogonal axes, and the MCP schema kept the two-flag form the change replaced. An agent asking for digest over MCP cannot request --detail signatures, which is the middle ground between a bare-name digest and a full map.

show may be a decision rather than an omission — a single path still accepts a glob, so the multi-target grammar is reachable in spirit. Worth confirming either way, since #49 established that a schema which under-promises costs an agent a capability it has no way to discover.

Drift runs the other way too, and #49 already fixed one instance: rebuild was declared on callers / callees / trace but absent from their args structs, so serde dropped the key and the tools advertised a lever they did not have.

Why nothing catches this

The two surfaces are declared in different languages in different files: clap derives in src/lib.rs, a hand-written JSON literal in src/mcp/tools.rs (plus src/impact.rs, src/context.rs, and src/search/mcp.rs, which declare their own args). #49's guards check that an argument which exists describes itself. Neither one asks whether it exists on both sides.

#57 closes the value half — defaults now come from src/defaults.rs, so a retuned number cannot land on one surface alone, and three guards fail the build on a default declared anywhere else. Argument existence is untouched by that.

Proposed guard

A cli_mcp_parity test comparing the argument names of Cli::command() against tools::list(), with an explicit allowlist of intended differences:

  • the six maintenance subcommands (install, uninstall, status, hook, prompt, mcp), which have no MCP tool by design;
  • --compact, which shapes text output that MCP does not render;
  • --help;
  • whatever the show target grammar turns out to be.

The allowlist is the deliverable as much as the test is: it turns "these two happen to differ" into "these two differ on purpose, and here is the list", which is what a reviewer needs to judge the next change.

Related: #56 records the convention in AGENTS.md, #57 shares the defaults.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions