cmux mcp: agents_<verb> tools with receipts
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_answeronly denies a permission (modeis"deny"only). Any other mode, such as allow, always or bypassPermissions, is refused asnot_runwith "approve permissions in cmux, not over MCP", and nothing is sent. Questions can still be answered withselections. The tool is markeddestructiveHint: true. The CLI keeps every mode, because a person typed it. - Outcome states.
Backend::apptakes amutationflag. A feed reply whose transport drops after it was sent reportsin_progresswithretryable: false(outcome unknown), instead ofnot_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
stateis replaced, with{truncated: true, hint: "call agents_snapshot"}, soaction,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 withnot_run, and a workspace name goes through as--name=<value>. Every change except the dialog tools takesidempotency_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.mdlists 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.focusplus the app'sgoToWorkspaceand returns a receipt; - the other verbs send the CLI's requests, and only the purpose-focus tools touch focus;
- invalid arguments come back
not_runwith 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 withunknown 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
mutationparameter wired through with no behavior change). The deny-only, in_progress, oversized-receipt and mcp-origin tests failed. lint also failed, becausetests/cli.rsgrew 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 libagents_tests and the CLI receipt testagents_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.rswent past 1000 lines; its unit tests now live inagents/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
rendertool. Run 38578027533: test (linux) passed all 19 libagents_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