cmux mcp: agents_<verb> tools with receipts

Merged
TE
23d ago
+1334-379

Summary

An MCP client can now drive cmux NEXT the way cmux agents does, without screenshots. cmux mcp lists eight new tools: agents_workspace_select, agents_workspace_create, agents_tab_select, agents_terminal_focus, agents_terminal_split, agents_palette_open, agents_dialog_list and agents_dialog_answer. Each answers the CLI's receipt {action, result, idempotency_key, state}, where state is a fresh composed snapshot (same shape as agents_snapshot).

There is one code path. A tool call turns its JSON arguments into the words cmux agents <verb> takes, then runs the CLI's own normalize_qualified_targets, command() parser and a new agents::execute over the MCP Backend trait. The CLI now runs execute too, through mcp::cli_backend(global). The app-focus follow-up for selects now goes through Backend::app. It behaves the same way as before: a missing app or not_found leaves the daemon's answer as it is.

  • Dialogs. These tools reach other agents' pending tool inputs, and their descriptions say so. Over MCP, agents_dialog_answer only denies a permission (mode is "deny" only). Any other mode, such as allow, always or bypassPermissions, is refused as not_run with "approve permissions in cmux, not over MCP", and nothing is sent. Questions can still be answered with selections. The tool is marked destructiveHint: true. The CLI keeps every mode, because a person typed it.
  • Outcome states. Backend::app takes a mutation flag. A feed reply whose transport drops after it was sent reports in_progress with retryable: false (outcome unknown), instead of not_run.
  • Receipts. A receipt names the idempotency key the change carried: the caller's key, or the one generated for it. When the snapshot pushes a receipt past the 256 KiB result limit, only state is replaced, with {truncated: true, hint: "call agents_snapshot"}, so action, result (with the new ids) and the key survive.
  • Origin. Palette open runs with origin: "mcp" over MCP and "agents" from the CLI.
  • Schemas and keys. Schemas are strict (additionalProperties: false). Targets take a public id, a unique prefix or <session>:<id>. Values that the CLI grammar would read as flags are refused with not_run, and a workspace name goes through as --name=<value>. Every change except the dialog tools takes idempotency_key.
  • Focus. Select, focus and palette open move focus because that is their purpose. Create and split leave the user's window alone.
  • Docs. plans/cmux-next/mcp.md lists the tools and these rules.

Proof

Boundary tests in cli/mcp/tests.rs, run through mcp::Server with the Fake backend:

  • tools are listed with strict schemas;
  • select sends workspace.focus plus the app's goToWorkspace and returns a receipt;
  • the other verbs send the CLI's requests, and only the purpose-focus tools touch focus;
  • invalid arguments come back not_run with nothing sent;
  • a failing owner maps through failure_result;
  • allow, always, bypass and other modes are refused with nothing sent, and deny is sent;
  • a reply lost after sending reports in_progress;
  • an oversized receipt keeps its action, result and key;
  • palette open carries origin: "mcp".

tests/cli/agents_cli_receipt.rs pins the CLI against a real daemon: agents workspace create exits 0 and prints the receipt with its key and the new ws_ id, a usage error exits 2, and an owner's refusal exits 3 with details.state: rejected.

  • First red: bash scripts/verify-cmux-tui-hosted.sh --filter agents_ on f58509f4da3b5. Run 38554199991: all 5 new tests failed with unknown tool: agents_*.
  • First green: run 38562167210 on 478180db548c6. 15 agents_ tests passed on Linux.
  • Review fixes, red: run 38570256393 on 977a21538873d (tests only, plus the mutation parameter wired through with no behavior change). The deny-only, in_progress, oversized-receipt and mcp-origin tests failed. lint also failed, because tests/cli.rs grew past its god-file baseline; the fix commit moves that test into its own module. The CLI receipt test did not run, because the lib tests failed first.
  • Review fixes, green: --filter agents_ on 30ba565416cf8. Run 38575568842: test (linux) passed all 19 lib agents_ tests and the CLI receipt test agents_cli_actions_print_a_receipt_and_owner_exit_codes_through_a_real_daemon. lint (linux), including the god-file check, and Rust MSRV passed too. The macOS jobs were still running when I wrote this. Run 38573532548, on 205cff84bc904 before the last commit, failed lint (agents.rs went past 1000 lines; its unit tests now live in agents/tests.rs) and failed one test, because the answer description did not say "other agents".
  • Merged head: feat-cmux-next 887f7e6b53ef merged in as ef32da1394799, keeping both the agents tools and the new render tool. Run 38578027533: test (linux) passed all 19 lib agents_ tests and the CLI receipt test; lint (linux) and Rust MSRV passed. The macOS jobs were still running when I wrote this.

Changelog

Added: cmux mcp offers the cmux agents actions (workspace, tab, terminal, palette, dialogs) as tools that return a receipt with a fresh snapshot; over MCP, permission dialogs can only be denied.

🤖 Generated with Claude Code