Embedding
Run a Cua Driver daemon as a direct child of your host app and connect an MCP proxy to it.
Embedding runs a dedicated cua-driver serve daemon as a direct child of your host app instead of launching the standalone CuaDriver.app. On macOS, the daemon inherits the host app's Accessibility and Screen Recording grants, so users only approve your app. A second cua-driver mcp child proxies stdio MCP traffic to that daemon; it never executes tools itself.
A complete macOS reference host and demo live in the repo at libs/cua-driver/rust/examples/embedded-host-macos.
Launch embedded#
Choose a private socket and start the embedded daemon first:
CUA_DRIVER_EMBEDDED=1 \
CUA_DRIVER_HOST_BUNDLE_ID=com.yourco.yourapp \
cua-driver serve --socket /tmp/yourapp-cua.sockThen start the MCP proxy against that socket:
cua-driver mcp --embedded --socket /tmp/yourapp-cua.sock \
--host-bundle-id com.yourco.yourappYou can pass --embedded --host-bundle-id com.yourco.yourapp to serve instead of the environment variables. Only the exact value CUA_DRIVER_EMBEDDED=1 enables environment-based embedded mode. The host bundle id is an advisory label echoed in check_permissions; trust still comes from macOS's responsibility chain.
Host requirements#
- Spawn
cua-driver serve --embeddeddirectly from your app, for example withProcess/NSTask,posix_spawn, orfork/exec. - Wait for its private socket to become ready, then spawn
cua-driver mcp --embedded --socket <path>and speak MCP over the proxy's stdin/stdout. - On macOS, do not launch the driver with
open(1)orNSWorkspace.open; LaunchServices makes the launched app its own responsible process and breaks permission inheritance. - On macOS, request Accessibility and Screen Recording from the host app with
AXIsProcessTrustedWithOptionsandCGRequestScreenCaptureAccess.
If macOS grants are added after the daemon has started, restart the daemon so TCC is re-queried with a fresh per-process cache.
App + gateway architectures#
--embedded does not transfer a GUI app's grants to the driver; it only keeps the daemon inside its spawner's macOS responsibility chain. If a separate gateway or Node process spawns the daemon, the daemon inherits the gateway's identity, not the app's. Spawn cua-driver serve --embedded from the app process.
Wrong (inherits the gateway's identity): Right:
gateway / node daemon YourApp.app
└─ cua-driver serve --embedded ├─ cua-driver serve --embedded
└─ cua-driver mcp --socket <private>What changes#
| Behavior | Standalone | Embedded |
|---|---|---|
| Process model | Standalone daemon + proxy | Host-spawned daemon + proxy |
| Daemon launch | May auto-launch CuaDriver.app | Host starts private daemon |
| macOS TCC identity | com.trycua.driver or caller | Host app |
| macOS permission prompts | Driver may prompt | Driver never prompts |
| macOS Settings entries | CuaDriver | Host app only |
check_permissions attribution | driver-daemon or caller | host on macOS embedded runs |
Driver tools, screenshots, AX tree reads, background input, and the agent cursor overlay otherwise behave the same.
macOS permission check#
Call the check_permissions MCP tool after the proxy connects to the embedded daemon. On macOS, embedded mode ignores prompt requests and should return source.attribution: "host":
{
"accessibility": true,
"screen_recording": true,
"screen_recording_capturable": true,
"source": {
"attribution": "host",
"host_bundle_id": "com.yourco.yourapp",
"embedded": true
}
}If source.attribution is not host on macOS, embedded mode is not active in the daemon handling your MCP calls. Check that CUA_DRIVER_EMBEDDED=1 is passed to the serve child, that the daemon was spawned directly, and that the proxy uses the intended private socket.
source.attribution: "host" means the daemon is running in embedded mode; it does not prove that your GUI app is the responsible process. If a gateway or Node process spawned the daemon, the reported grant state still belongs to that spawner.