forked from manaflow-ai/cmux
-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add cmux MCP server for terminal control #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| # cmux MCP Server Recommendation | ||
|
|
||
| Last updated: March 7, 2026 | ||
|
|
||
| ## Recommendation | ||
|
|
||
| Build a local MCP server for cmux, but keep it as a thin adapter over the existing v2 Unix socket API instead of adding a second control plane inside the app. | ||
|
|
||
| This is justified because cmux already exposes most of the primitives an MCP host would need: | ||
|
|
||
| 1. `TerminalController` already implements a JSON v2 socket protocol with stable methods for `window.*`, `workspace.*`, `pane.*`, `surface.*`, and `browser.*` in [TerminalController.swift](../Sources/TerminalController.swift). | ||
| 2. `system.tree` and `system.identify` already provide the discovery and self-location data an agent needs to reason about windows, workspaces, panes, and surfaces. | ||
| 3. `TabManager`, `Workspace`, and `AppDelegate` already own the real mutations for selection, creation, moving, focusing, and cross-window routing, so an MCP server does not need to replicate that logic. | ||
|
|
||
| ## Codebase Findings | ||
|
|
||
| ### Existing control plane | ||
|
|
||
| 1. `TerminalController` is already the automation boundary: | ||
| - capability advertisement via `v2Capabilities()` | ||
| - hierarchy discovery via `v2SystemTree()` | ||
| - window/workspace/surface/pane/browser dispatch in the `window.*`, `workspace.*`, `surface.*`, `pane.*`, and `browser.*` handlers | ||
| 2. `AppDelegate` already exposes window-level lookup and mutation: | ||
| - `listMainWindowSummaries()` | ||
| - `tabManagerFor(windowId:)` | ||
| - `focusMainWindow(windowId:)` | ||
| - `createMainWindow()` | ||
| - `closeMainWindow(windowId:)` | ||
| 3. `TabManager` already owns workspace lifecycle and selection: | ||
| - `addWorkspace()` | ||
| - `selectWorkspace(_:)` | ||
| - `reorderWorkspace(...)` | ||
| - workspace title/color/pin operations | ||
| 4. `Workspace` already owns pane and surface topology: | ||
| - bonsplit pane state | ||
| - panel registry | ||
| - focused surface tracking | ||
| - terminal/browser surface metadata | ||
|
|
||
| ### Design implication | ||
|
|
||
| The app already has a useful "cmux control kernel". The missing piece is a host-friendly MCP facade, not more app-side orchestration code. | ||
|
|
||
| ## Proposed Architecture | ||
|
|
||
| Run the MCP server as an external local stdio process: | ||
|
|
||
| 1. MCP host starts `scripts/cmux_mcp_server.py`. | ||
| 2. The server connects to the configured cmux socket (`CMUX_SOCKET_PATH` or `--socket`). | ||
| 3. MCP tools translate directly to v2 socket calls. | ||
| 4. `TerminalController` remains the source of truth for capability routing and object identity. | ||
|
|
||
| Why external is better than embedding inside cmux: | ||
|
|
||
| 1. MCP is a transport/protocol concern; cmux already has the stateful control API. | ||
| 2. External stdio avoids mixing MCP lifecycle with AppKit lifecycle. | ||
| 3. The same adapter can target different cmux sockets without changing the app. | ||
| 4. The adapter can stay intentionally small and experimental while the socket API evolves. | ||
|
|
||
| ## Initial Tool Set | ||
|
|
||
| Recommend exposing a narrow first pass instead of mirroring every socket method one-for-one. | ||
|
|
||
| ### Discovery | ||
|
|
||
| 1. `cmux_socket_discover` | ||
| - Find candidate local cmux sockets under `/tmp`. | ||
| 2. `cmux_system_tree` | ||
| - Return windows, workspaces, panes, surfaces, and the active path. | ||
|
|
||
| ### Workspace steering | ||
|
|
||
| 1. `cmux_list_workspaces` | ||
| 2. `cmux_create_workspace` | ||
| 3. `cmux_select_workspace` | ||
|
|
||
| ### Surface steering | ||
|
|
||
| 1. `cmux_list_surfaces` | ||
| 2. `cmux_send_text` | ||
| 3. `cmux_read_text` | ||
|
|
||
| ### Escape hatch | ||
|
|
||
| 1. `cmux_socket_call` | ||
| - Raw pass-through to an existing v2 socket method. | ||
| - Useful while validating which abstractions should become first-class MCP tools. | ||
|
|
||
| ## What Not To Build Yet | ||
|
|
||
| 1. Do not duplicate the entire `window.*` / `workspace.*` / `surface.*` / `browser.*` matrix as separate MCP tools yet. | ||
| 2. Do not add a second IPC path inside cmux just for MCP. | ||
| 3. Do not invent new object IDs; reuse cmux UUID/ref handles from the socket API. | ||
| 4. Do not let the MCP layer bypass `TerminalController` focus rules or socket auth/access mode. | ||
|
|
||
| ## Risks and Guardrails | ||
|
|
||
| 1. Focus-stealing remains an app concern. The MCP adapter should respect the focus policies already enforced by `TerminalController`. | ||
| 2. Socket auth mode matters. If the socket is password-protected or disabled, MCP should fail clearly instead of trying to bypass it. | ||
| 3. Tool count can explode quickly. Keep MCP tools task-shaped and leave uncommon operations behind `cmux_socket_call` until repeated usage proves they deserve dedicated schemas. | ||
|
|
||
| ## Recommendation Summary | ||
|
|
||
| Yes, build the MCP server. | ||
|
|
||
| But build it as: | ||
|
|
||
| 1. an external stdio adapter | ||
| 2. backed by the existing v2 socket API | ||
| 3. with a small, high-value tool surface first | ||
| 4. and a raw-call escape hatch while the tool taxonomy settles |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR description mentions the Python server (
python scripts/cmux_mcp_server.py) as a runnable implementation, but the README (lines 110–161) only documents the JavaScript MCP server. The Python server has a different tool set (8 tools using the v2 socket API directly) compared to the JavaScript server (6 tools using the cmux CLI). Neither the README nor the docs explain when to use which server, or that both exist. This creates confusion for users. The README should either document the Python server as an alternative, or explain the relationship/difference between the two implementations.