Standalone MCP server exposing the
computer_use tool verbatim — the same tool, same schema, same verdict
ladder as in Hermes Agent's
tools.computer_use. Backed by cua-driver.
The only difference from the original: transport. The vendor ships it as an in-process tool; we ship it as an MCP stdio tool so any MCP-speaking client (Claude Desktop, Cursor, Cline, your custom CLI, etc.) can drive a real desktop through it. The tool keeps the vendor's own name —
computer_use— which is safe: host agents like Hermes prefix MCP tools with the server name (mcp__mcp_computer_use__computer_use), so it never collides with their in-processcomputer_usetool. Override viaMCP_COMPUTER_USE_TOOL_NAME(any[a-z][a-z0-9_]*identifier) if a specific deployment wants a different name.
A single MCP tool named computer_use with 14 actions:
| Action | Description |
|---|---|
capture |
Screenshot + accessibility tree (modes: som / vision / ax) |
click · double_click · right_click · middle_click |
Pointer input (element index or coordinate) |
drag |
Pointer drag (from_* → to_*) |
scroll |
Scroll direction + amount |
type |
Type text (layout-safe, with input-method hooks) |
key |
Key / shortcut combos (e.g. cmd+shift+t) |
set_value |
Select value on a control (popup / slider) without native menu |
focus_app |
Focus an app (optionally raise_window) |
list_apps · list_windows |
Inventory queries (real driver calls) |
wait |
Poll (capped at 30s) |
Every input action returns a structured verdict:
{
"effect": "confirmed" | "unverifiable" | "suspected_noop",
"verified": true|false,
"escalation": { "recommended": "px"|"foreground"|"stop",
"reason": "..." },
"code": "background_unavailable" | "foreground_unsupported" | null
}The standard escalation ladder (element+background → re-capture → coordinate+background → typed page → foreground → stop) is unchanged from the vendor.
Input is background-first — no cursor steal, no focus steal, no
Space-switch. Foreground requires delivery_mode=foreground and
bring_to_front=true.
git clone https://github.com/phimage/mcp-computer-use
cd mcp-computer-use
python3.11 -m venv .venv
. .venv/bin/activate
pip install -e .
# sanity
python -m mcp_computer_use.serverRequires Python ≥ 3.11 and cua-driver on PATH:
which cua-driver. macOS needs Accessibility + Screen Recording
granted to the MCP client terminal (the vendor driver handles the rest).
See examples/claude_desktop.json for a
Claude-Desktop-style config. The canonical shape is:
{
"mcpServers": {
"computer-use": {
"command": "python",
"args": ["-m", "mcp_computer_use.server"],
"env": {
"MCP_COMPUTER_USE_APPROVAL_MODE": "deny",
"MCP_COMPUTER_USE_SESSION_KEY": "my-client"
}
}
}
}Because this tool drives the host desktop (real AX APIs, real cua-driver
process), the server has to run on the host machine, not inside the
container. The container (where Hermes lives) then reaches it over the
network. Two transports are offered:
| Transport | How to run | When |
|---|---|---|
stdio (default) |
python -m mcp_computer_use.server |
Same machine, client spawns it as a subprocess (Claude Desktop, Cursor, Hermes on the host). |
http (Streamable-HTTP) |
python -m mcp_computer_use.server --transport http |
Remote client — e.g. Hermes in a Docker container, or any client over the network. |
# On the host Mac, in a terminal:
MCP_COMPUTER_USE_APPROVAL_MODE=auto \
MCP_COMPUTER_USE_SESSION_KEY=hermes-docker \
MCP_COMPUTER_USE_HOST=0.0.0.0 \
MCP_COMPUTER_USE_PORT=8723 \
.venv/bin/python -m mcp_computer_use.server --transport httpBy default it binds 0.0.0.0:8723 with a stateless HTTP session — each
request is self-contained (no session id to keep), which is the right
posture for a shared host service. The endpoint is http://<host>:8723/mcp;
/health is a trivial liveness probe.
MCP_COMPUTER_USE_STATELESS=1(default) — stateless; any client can hit it.MCP_COMPUTER_USE_ALLOWED_HOSTS/MCP_COMPUTER_USE_ALLOWED_ORIGINS— optional DNS-rebinding / origin allow-lists (comma-separated). Empty = protection disabled, same as the MCP SDK default.MCP_COMPUTER_USE_APPROVAL_MODE—deny/auto/callbackas before; the gate still applies in HTTP mode (the decision is made in the server process on the host, not in the container).
From inside the container, register the host endpoint as a remote MCP
server (Hermes supports url-based MCP servers natively):
# Replace `host.docker.internal` with whatever address the container uses to
# reach the host (Docker Desktop on Mac gives you host.docker.internal
# out of the box; on Linux you'll typically use the host's LAN/bridge IP).
hermes mcp add mcp-computer-use \
--url "http://host.docker.internal:8723/mcp"If Hermes prompts "does this server require authentication?", answer No for the simple setup (our server has no auth layer by default; add a reverse-proxy / API-key in front if you want a token).
Then the tool is exposed to the agent as
mcp__mcp_computer_use__computer_use — the same 23-parameter schema,
same 14 actions, same verdict ladder as the stdio transport. The only
difference is the path across the wire: HTTP → /mcp → manager → Server → vendored handle_computer_use → cua-driver on the host.
- One desktop, one driver. All clients (Hermes-in Docker, Claude
Desktop on the host, a browser-based MCP client) share the same physical
desktop via the host
cua-driver. Concurrent input will still interleave exactly like concurrent terminals; if you need true isolation, run one server per user/session (bind different ports, differentSESSION_KEYs). - Approval stays local to the server. The
deny/auto/callbackpolicy runs on the host process, not the container — so the container never needs to be able to call back, and approval is enforced even across remote hops. - No secrets in the container. The container only holds the host's network address.
- The server must run on a host that actually has a desktop (macOS /
Windows / Linux with cua-driver working) — a container without X11/Wayland
is not a valid
cua-drivertarget. - HTTP mode defaults to
0.0.0.0. If your host is on an untrusted network, setMCP_COMPUTER_USE_ALLOWED_HOSTS=<container-egress-host>:*to enforce theHostheader, and consider TLS via a reverse-proxy (Caddy /caddy_reverse_proxy) for anything beyond a private LAN. cua-driverreads Accessibility / Screen Recording grants tied to the terminal on the host that started the server — grant those to that terminal.
The MCP spec has no first-class "ask the user" primitive, so the vendor's
approval machinery (set_approval_callback + approve_once /
approve_session / always_approve / deny / timeout) is surfaced
through an env-var policy:
| Mode | Effect |
|---|---|
deny (default) |
All destructive actions refused until an integrator installs a callback or sets auto. |
auto |
All destructive actions auto-approved for the current session. Convenience for headless automation. |
callback |
The integrator must register a callback (see below); every destructive call goes through it. |
Set it via env: MCP_COMPUTER_USE_APPROVAL_MODE=deny|auto|callback.
The legacy MCP_COMPUTER_USE_AUTO_APPROVE=1 is a shortcut for auto.
Interactive approvals (callback mode) — install your own callback before constructing the server. Signature:
from mcp_computer_use.approval import interactive_callback
def my_gate(action: str, args: dict, summary: str) -> str:
# Return: "approve_once" | "approve_session" | "always_approve"
# | "deny" | "timeout"
return "approve_session"
interactive_callback(my_gate)See examples/interactive_approval.py
for a full working example.
Safe actions — capture, wait, list_apps, list_windows never
hit the gate. They are read-only and always permitted.
Hard blocks — a handful of dangerous patterns are refused
regardless of approval mode (e.g. rm -rf / typed via type,
cmd+shift+backspace empty trash, cmd+option+shift+q force-logout).
These come straight from the vendor's
_BLOCKED_KEY_COMBOS table.
pip install -e ".[dev]"
pytest -v36 tests cover:
- schema identity (the 23-param dict, the 14-action enum)
- renderer (OpenAI-style → MCP
ContentBlock, including the multimodalcaptureshape) - dispatch through the vendored handler (
_SAFE_ACTIONS,_DESTRUCTIVE_ACTIONS, callback verdicts, blocked patterns) - E2E over stdio using the official MCP Python client (
initialize,tools/listwith the default name,MCP_COMPUTER_USE_TOOL_NAMEoverride + invalid-name rejection,tools/call, unknown-tool error) - E2E over Streamable-HTTP using the official MCP Python client — the exact
path a remote client (Hermes in Docker) uses: server launched as a
subprocess on a real port, then
initialize,tools/list, and a fulltools/callthat reaches the vendored handler across the wire
.venv/bin/python examples/live_e2e.pySpawns the real server as a subprocess, talks to it through the
official MCP client, and drives the real cua-driver: list_apps,
focus_app Finder, capture Finder (saves the screenshot to
~/Library/Caches/mcp-computer-use/cache/images/).
src/mcp_computer_use/
├── __init__.py # public API + bootstrap invocation
├── _bootstrap.py # sys.modules alias map
├── _shims/ # one module per vendor-internal import path
│ ├── async_utils.py # verbatim from hermes
│ ├── aux_client.py
│ ├── hermes_constants.py
│ ├── image_routing.py
│ ├── lazy_deps.py
│ ├── load_config.py
│ ├── model_tools.py
│ ├── models_dev.py
│ ├── subprocess_compat.py
│ ├── subprocess_env.py
│ ├── tool_approval.py
│ ├── tools_config.py
│ └── vision_tools.py
├── backend.py # [verbatim] ComputerUseBackend ABC
├── cua_backend.py # [verbatim] CuaDriverBackend + MCP-over-stdio client
├── schema.py # [verbatim] COMPUTER_USE_SCHEMA
├── tool.py # [verbatim] handle_computer_use + dispatch
├── vision_routing.py # [verbatim]
├── approval.py # [new] env-var policy adapter
├── server.py # [new] mcp.server.Server + tool registration
└── __main__.py # [new] `python -m mcp_computer_use.server`
tests/
├── conftest.py # clean-env + no-backend fixtures
├── test_approval.py # approval policy
├── test_dispatch.py # vendor dispatch (safe / destructive / callback)
├── test_e2e_stdio.py # full protocol via the mcp client
├── test_render.py # renderer unit tests
└── test_schema.py # schema identity
examples/
├── claude_desktop.json
├── interactive_approval.py
└── live_e2e.py
- mcp-computer-use — MIT, © 2026 phimage
- Vendored source —
tools/computer_use/*from NousResearch/hermes-agent, MIT © 2025 Nous Research. Unmodified (see[verbatim]tags above). - Driver — trycua/cua, MIT © 2025 Cua AI, Inc.