MCP: an unknown tool name gets "did you mean <nearest tool>?"

TE
6d ago
+262-9

Fixes an internal report: MCP servers answer an unknown tool with no nearest name.

What

An agent that mistypes a tool name (team_sned, workspace_lst) got a bare unknown tool: NAME from every MCP server we ship, and had to list the tools again to find the right one. Now each server answers unknown tool: NAME; did you mean TOOL? when one of its own tools is close, and keeps the bare text when nothing is.

  • New acpmux::mcp_hint (unknown_tool, nearest, names): one shared nearest-name helper. A name is close when, ignoring case, it is within an edit distance (a swap of two neighbouring letters is one edit) of 1 for up to 3 letters or max(2, len / 3) for longer, or when it is a prefix (3+ letters) of a tool name. The closest wins; a tie goes to the first in list order. A name is never its own hint.
  • acpmux's MCP servers (acpmux_mcp, forum_mcp, offer_mcp, secret_mcp, subsession_mcp, and the bg tools' fallback, which the server's own routing makes unreachable) pass the names from their own tools() / TOOLS, so the hint cannot drift from what they list.
  • cmux mcp (cli/mcp.rs) passes the names it lists to this chat (allowed tools only; plus cmux_docs/cmux_exec in code mode). list() is split into a refresh and listed(), so the hint uses the actions and proxied tools as last read and asks the app nothing again. list() itself behaves as before.

No other behaviour changes: the error codes, the isError results and every known-tool path are unchanged.

Overlap: #26625 adds a similar hint inside cmux_exec scripts only (code_mode::nearest_names). If both land, a script call to a mistyped tool would carry two hints; whichever lands second should drop one.

Test

Red commit 58cd1ab66c3 adds the cases; fix commit follows.

  • cd cmux-tui && cargo test -p acpmux --lib acpmux_mcp mcp_hint: an_unknown_session_or_a_missing_argument_is_an_error_the_agent_reads now expects unknown tool: team_sned; did you mean team_send? (and still unknown tool: nope), plus the helper's unit tests.
  • cd cmux-tui && cargo test -p cmux-tui --lib json_rpc_lifecycle_and_errors: workspace_lst gets unknown tool: workspace_lst; did you mean workspace_list?; no_such_tool keeps the bare text.

Red (the red commit's cmux-tui/ tree, on the fleet Linux runner):

  • cargo test -p acpmux --lib -- acpmux_mcp: 7 passed, 1 failed: an_unknown_session_or_a_missing_argument_is_an_error_the_agent_reads, left "unknown tool: team_sned", right "unknown tool: team_sned; did you mean team_send?".
  • cargo test -p cmux-tui --lib -- cli::mcp::tests::json_rpc_lifecycle_and_errors: failed at tests.rs:817, left "unknown tool: workspace_lst", right "unknown tool: workspace_lst; did you mean workspace_list?".

Green (head 80d2b70b97a5):

  • cargo test -p acpmux --lib -- acpmux_mcp mcp_hint: 14 passed (8 server tests, 6 helper tests).
  • cargo test -p cmux-tui --lib -- cli::mcp::tests::json_rpc_lifecycle_and_errors cli::mcp::tests::code_mode: 4 passed.
  • cargo check -p cmux-tui --all-targets: clean.

Observed / Not run

  • Observed: the red and green runs above, on the fleet at the PR head; prepush-gate.py reports no new violations; rustfmt clean on the changed files.
  • Not run: the full acpmux and cmux-tui suites, and clippy. The forum, offer, secret and subsession servers have no daemon-backed unknown-tool test; their change is the same one-line call to the helper with their own tools(), read but not run end to end.

Changelog

  • Changed: MCP servers answer a mistyped tool name with the nearest tool name ("did you mean ...?").

risk: low (error text only).

Agent session: agent session

🤖 Generated with Claude Code