fix(cua-driver-rs): proxy MCP through daemon on Windows/Linux when one is up - #1580
Conversation
…e is up
`cua-driver mcp` is the entrypoint Claude Code (and every other MCP
client) spawns. On macOS it can optionally proxy through a
LaunchServices-attributed daemon so TCC bundle-id grants apply. On
Windows and Linux the proxy was a no-op — `should_use_daemon_proxy`
hard-coded `false` — which meant the in-process server always ran in
the calling session.
Concretely on Windows: an `cua-driver mcp` spawned by Claude Code
over SSH lands in Session 0 (the sshd service session). Session 0
has no desktop attached, so the window/UIA/screen tools return
empty regardless of whether a daemon is up in the user's Session
1+. The `cua-driver call` CLI did not have this problem because
`run_call` already has its own `is_daemon_listening` shortcut.
This change cross-platform-ifies the existing proxy plumbing:
* `should_use_daemon_proxy` on non-macOS now returns `true` when
`is_daemon_listening` reports a daemon on the default socket
(`\\.\pipe\cua-driver` on Windows, UDS on Linux). Same opt-out
knobs as macOS: `--no-daemon-relaunch`,
`CUA_DRIVER_RS_MCP_NO_RELAUNCH`. Plus
`CUA_DRIVER_RS_MCP_FORCE_PROXY` for test harnesses.
* `run_mcp_via_daemon_proxy` is no longer macOS-gated. The
auto-launch step (`launch_daemon_and_wait`, the `open -n -g -a
CuaDriver` trick) stays macOS-only — on Linux/Windows, when no
daemon is up, we bail with an actionable error pointing at
`cua-driver autostart enable && kick` rather than silently
falling through to a Session-0-attributed in-process server.
* The non-macOS Mcp arm in `main.rs` now calls the proxy router
instead of always falling through to in-process.
Verified live on the Windows VM today: an interactive-session
daemon (`autostart kick` lands it in Session 2 via Task Scheduler's
`/ru interactive`), `cua-driver call list_apps` from SSH returned 5
real running GUI apps via the existing `run_call` proxy — but
`claude --print` invoking `cua-driver mcp` reported "0 running"
because the MCP path wasn't proxying. This fix routes MCP through
the same daemon and lets Claude Code see what the user actually
sees.
The opt-out path remains — `cua-driver mcp --no-daemon-relaunch`
or `CUA_DRIVER_RS_MCP_NO_RELAUNCH=1` keeps the in-process server
when callers explicitly want it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR modifies the MCP command handling on non-macOS platforms to intelligently route requests through an optional daemon proxy. ChangesMCP daemon proxy routing on non-macOS
Sequence Diagramflowchart TD
Start["User runs: cua-driver mcp"] --> Decision{"should_use_daemon_proxy<br/>checks: opt-out flags,<br/>force proxy, daemon availability"}
Decision -->|"no_daemon_relaunch=true<br/>or env var set"| InProcess["Run in-process<br/>MCP server"]
Decision -->|"CUA_DRIVER_RS_MCP_FORCE_PROXY=1"| ProxyForced["Attempt daemon proxy"]
Decision -->|"daemon socket listening"| ProxyNormal["Attempt daemon proxy"]
Decision -->|"no opt-out, no force,<br/>no daemon listening"| ProxyFail
ProxyForced --> DaemonCheck{"Daemon listening?"}
ProxyNormal --> DaemonCheck
DaemonCheck -->|"yes"| ProxyExec["Execute proxy request"]
DaemonCheck -->|"no + forced"| ErrorFast["Fail: instruct to run<br/>cua-driver serve"]
DaemonCheck -->|"no + non-macOS"| ErrorUser["Fail: instruct to start<br/>daemon in interactive session"]
DaemonCheck -->|"no + macOS"| AutoLaunch["Auto-launch daemon<br/>via launch_daemon_and_wait"]
AutoLaunch --> ProxyExec
ProxyExec --> Success["Return Ok<br/>and exit"]
ErrorFast --> Exit1["stderr log error<br/>exit status 1"]
ErrorUser --> Exit1
InProcess --> Success
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Adds two sibling pages under guide/getting-started/ to close gaps in the cua-driver fumadocs that are heavily macOS-focused today: - windows-ssh.mdx: the "daemon in user's interactive Session 1+ → SSH or remote shell → cua-driver mcp/call proxies through the daemon → real GUI apps respond" workflow that just shipped in cua-driver-rs v0.2.7 (PR #1580). Covers the Session 0 problem, the canonical `autostart enable && kick` recipe, prerequisite (active interactive session — Active or Disc), opt-outs, and the v0.2.7 reason `mcp` now proxies the same way `call` always did. - autostart.mdx: concept page for the `cua-driver autostart` {enable,disable,kick,status} verb family. Documents what each verb does per platform (Windows = Scheduled Task with LogonType:Interactive, macOS/Linux still manual via install-local.sh --autostart), the daemon-survives-RDP-disconnect property, and the worked example. Also adds cross-link callouts in installation.mdx so the existing "Auto-start at logon (Windows)" and "TCC auto-delegation" sections point at the new pages, plus a sibling daemon-proxy callout for Windows/Linux. Docs-only; no code touched. Behaviour shipped in cua-driver-rs v0.2.7. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Summary
`cua-driver mcp` is the entrypoint Claude Code (and every other MCP client) spawns. On macOS it can optionally proxy through a LaunchServices-attributed daemon so TCC bundle-id grants apply. On Windows and Linux the proxy was a no-op — `should_use_daemon_proxy` hard-coded `false` — which meant the in-process MCP server always ran in the calling session.
Concretely on Windows: an `cua-driver mcp` spawned by Claude Code over SSH lands in Session 0 (the sshd service session). Session 0 has no desktop attached, so the window/UIA/screen tools return empty regardless of whether a daemon is up in the user's Session 1+. The `cua-driver call` CLI did not have this problem because `run_call` already had its own `is_daemon_listening` shortcut, but `mcp` (the path Claude Code actually uses) didn't.
Repro live on the Windows VM today
```
Daemon running in Session 2 (RDP user's interactive session)
$ ssh winvm 'cua-driver call list_apps' | head
{ "apps": [ {"pid": 3040, "name": "cua-driver.exe", "running": true},
{"pid": 11552, "name": "WindowsTerminal.exe", "running": true},
{"pid": 9956, "name": "Windows PowerShell ISE (x86)", "running": true},
{"pid": 10516, "name": "Microsoft Edge", "running": true},
{"pid": 2748, "name": "explorer.exe", "running": true} ] }
Claude Code, via the SAME stdio MCP transport:
$ ssh winvm 'claude --print "List running apps via cua-driver"'
"No apps have running:true in the listing."
```
Before this fix: claude's spawned `cua-driver mcp` ran in-process in Session 0 → saw no desktop apps. After: it proxies through the Session-2 daemon → sees what `call` already sees.
What changed
Test plan
Related
🤖 Generated with Claude Code
Summary by CodeRabbit