Skip to content

fix(cua-driver-rs): proxy MCP through daemon on Windows/Linux when one is up - #1580

Merged
f-trycua merged 1 commit into
mainfrom
fix/cua-driver-rs-mcp-proxy-windows
May 19, 2026
Merged

fix(cua-driver-rs): proxy MCP through daemon on Windows/Linux when one is up#1580
f-trycua merged 1 commit into
mainfrom
fix/cua-driver-rs-mcp-proxy-windows

Conversation

@f-trycua

@f-trycua f-trycua commented May 19, 2026

Copy link
Copy Markdown
Collaborator

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

  • `crates/cua-driver/src/cli.rs` `should_use_daemon_proxy` non-macOS branch: returns `true` when `is_daemon_listening` finds a daemon on the default socket. 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.
  • `crates/cua-driver/src/cli.rs` `run_mcp_via_daemon_proxy`: drop the `#[cfg(target_os = "macos")]` gate. The macOS-only auto-launch step (`launch_daemon_and_wait` → `open -n -g -a CuaDriver`) 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.
  • `crates/cua-driver/src/main.rs` non-macOS Mcp arm: now calls the proxy router (mirroring the macOS arm).

Test plan

  • macOS `cargo check -p cua-driver` — clean
  • Windows PR pre-release-binaries CI builds the artifact (this PR is the test)
  • Install the resulting binary on the Windows VM, daemon already in Session 2 via `autostart kick`, run `claude --print "List running apps via cua-driver"` from SSH — should now see real GUI apps
  • `cua-driver mcp --no-daemon-relaunch` opt-out still runs in-process

Related

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Improved MCP daemon-proxy initialization with platform-specific handling
    • macOS now automatically launches the daemon service when required
    • Non-macOS platforms display detailed error messages with instructions when daemon is unavailable
    • Daemon proxy behavior can be controlled via environment variables and command-line flags

Review Change Stack

…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>
@vercel

vercel Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Ignored Ignored May 19, 2026 3:54pm

Request Review

@coderabbitai

coderabbitai Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5b5f728f-ab4a-4f8b-ac14-89f4a363bab6

📥 Commits

Reviewing files that changed from the base of the PR and between 0f834c6 and da059cd.

📒 Files selected for processing (2)
  • libs/cua-driver-rs/crates/cua-driver/src/cli.rs
  • libs/cua-driver-rs/crates/cua-driver/src/main.rs

📝 Walkthrough

Walkthrough

This PR modifies the MCP command handling on non-macOS platforms to intelligently route requests through an optional daemon proxy. should_use_daemon_proxy replaces a hardcoded false with environment-variable-driven and socket-availability-based logic. run_mcp_via_daemon_proxy gains platform-specific fallback behavior when no daemon listens. The MCP dispatcher conditionally invokes the proxy path on non-macOS based on the decision function.

Changes

MCP daemon proxy routing on non-macOS

Layer / File(s) Summary
Daemon proxy decision logic
libs/cua-driver-rs/crates/cua-driver/src/cli.rs
should_use_daemon_proxy replaces non-macOS hardcoded false with conditional logic: respects opt-out via --no-daemon-relaunch and CUA_DRIVER_RS_MCP_NO_RELAUNCH, forces proxy when CUA_DRIVER_RS_MCP_FORCE_PROXY=1, and otherwise probes daemon availability on the default socket.
Daemon proxy startup with platform-specific fallback
libs/cua-driver-rs/crates/cua-driver/src/cli.rs
run_mcp_via_daemon_proxy gains explicit handling when no daemon is listening: fails immediately with setup instructions under forced proxy, auto-launches daemon with user message on macOS, and bails with actionable guidance on non-macOS.
MCP command routing through proxy
libs/cua-driver-rs/crates/cua-driver/src/main.rs
Non-macOS MCP dispatcher now calls should_use_daemon_proxy, conditionally executes run_mcp_via_daemon_proxy with error logging and exit(1) on failure, and falls through to in-process server if proxy is not used.

Sequence Diagram

flowchart 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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • trycua/cua#1479: Implements auto-delegation of the mcp command to the serve daemon over Unix socket with matching opt-out behavior (--no-daemon-relaunch, CUA_DRIVER_RS_MCP_NO_RELAUNCH), daemon probing, and proxy fallback logic.

Poem

🐰 A proxy path blooms on non-macOS ground,
Where daemons listen on sockets profound,
Smart routing and fallback, with flags set just right,
Auto-launch on Mac makes everything bright! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: enabling MCP proxy through daemon on Windows/Linux when a daemon is listening, matching the core objective to route requests through the daemon on non-macOS platforms.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/cua-driver-rs-mcp-proxy-windows

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@f-trycua
f-trycua merged commit 2192f37 into main May 19, 2026
5 checks passed
@f-trycua
f-trycua deleted the fix/cua-driver-rs-mcp-proxy-windows branch May 19, 2026 16:03
f-trycua added a commit that referenced this pull request May 19, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant