Skip to main content
Glama

Introduction

Computer Use is an open-source MCP server — a computer-use agent (CUA) backend — that lets any coding agent use your computer the way a person does. It reads the screen through accessibility trees, clicks and types in the background so your mouse stays yours, shows an agent pointer where it is working, zooms in on small text, and drives tabs in your signed-in Chrome — on macOS, Windows and Linux.

Works with Claude Code, Codex, Cursor and MT Code, or any other MCP client, with any model — no vision model is required for interaction.

Built by Munim Technologies as the Computer Use engine of MT Code, and published here on its own.

Related MCP server: openowl

Table of contents

Quick start

  1. Download the latest binary for your platform from Releases (munim-computer-use-macos-universal.zip, munim-computer-use-windows-x64.zip) or build from source.

  2. Put it somewhere on your PATH (/usr/local/bin/munim-computer-use, or %LOCALAPPDATA%\Programs\munim-computer-use\munim-computer-use.exe).

  3. macOS only: run munim-computer-use request-permissions once to be prompted for Accessibility and Screen Recording.

  4. Register it with your agent:

# Claude Code — fastest: the npm launcher fetches the signed binary on first run.
# Add `--scope user` to register it for every project instead of just this one.
claude mcp add munim-computer-use -- npx -y munim-computer-use
# or point at a downloaded binary
claude mcp add munim-computer-use -- /usr/local/bin/munim-computer-use
# Codex — writes the entry below into ~/.codex/config.toml for you (needs a Codex CLI
# with `codex mcp`; check with `codex mcp --help`)
codex mcp add munim-computer-use -- npx -y munim-computer-use
# Codex — ~/.codex/config.toml, if you would rather edit it yourself
[mcp_servers.munim-computer-use]
command = "npx"
args = ["-y", "munim-computer-use"]
# Cursor — no MCP subcommand in its CLI, so write the config. ~/.cursor/mcp.json applies
# to every project; .cursor/mcp.json in a repo applies to that one.
mkdir -p ~/.cursor && [ -s ~/.cursor/mcp.json ] || echo '{}' > ~/.cursor/mcp.json
jq '.mcpServers["munim-computer-use"] = {"command":"npx","args":["-y","munim-computer-use"]}' \
  ~/.cursor/mcp.json > ~/.cursor/mcp.json.tmp && mv ~/.cursor/mcp.json.tmp ~/.cursor/mcp.json
// Cursor — the entry that produces, in .cursor/mcp.json
{ "mcpServers": { "munim-computer-use": { "command": "npx", "args": ["-y", "munim-computer-use"] } } }

Then ask: "Open Safari, find the cheapest flight to Denver on Tuesday and put it in a note." The agent reads the UI with get_app_state, acts by element id, and verifies with screenshot.

Capability matrix

Munim Computer Use is the highlighted first column; the others are the computer-use servers people reach for. each cell comes from that project's own README or docs in September and October 2026 (sources under Credits and license). ✅ present · ❌ absent or not documented · ⚠️ partial.

Capability

Munim Computer Use

Cua Driver

Codex Computer Use

OpenAI Agents API

Anthropic reference demo

Windows-MCP

MacOS-MCP

open-computer-use

computer-use-mcp (zavora)

Notes

macOS

✅

✅

✅

n/a

❌

❌

✅

✅

✅

The Anthropic demo drives a Linux desktop inside Docker, not your machine. The OpenAI Agents API (September 2026) runs a browser OpenAI hosts, so it never drives your machine either.

Windows

✅

✅

✅

n/a

❌

✅

❌

✅

✅

Windows-MCP is Windows only; MacOS-MCP is macOS only.

Linux

✅

✅

❌

n/a

✅ (sandbox)

❌

❌

✅

✅

Munim Computer Use uses AT-SPI + X11; native Wayland apps get element actions but not coordinate clicks.

Accessibility tree with element ids

✅

✅

⚠️

❌

❌

✅

✅

✅

✅

Codex reads the accessibility tree first on macOS and works from screenshots on Windows; the Agents API and the Anthropic demo are screenshot-driven. Ids let the agent press the button instead of a pixel.

Background input (your mouse never moves)

✅

✅

⚠️

n/a

n/a

❌

❌

❌

❌

Munim Computer Use addresses events to the target window: always on macOS (SkyLight and per-process events), for UI Automation patterns and classic Win32 controls on Windows; other Windows UI and all Linux pointer input (XTEST) still move the real pointer. Codex works in the background on macOS with cursors of its own, and takes over the pointer on Windows. Cua Driver delivers in the background by default and falls back to the foreground when that does not land. The other servers in this table drive the real cursor.

Agent pointer overlay

✅

✅

✅

❌

❌

⚠️

❌

❌

❌

Windows-MCP flashes a border around captures; Codex and Cua Driver draw their own cursor on your screen.

Zoom into a region at full resolution

✅

✅

❌

❌

✅

❌

❌

❌

❌

Anthropic's toolset has zoom; here it is a tool on every platform.

Screenshots carry screen-coordinate mapping

✅

⚠️

n/a

n/a

n/a

❌

❌

❌

❌

Origin and pixels-per-point in every capture, so clicks from Retina or downscaled images land.

Hover, wait, label query

✅

⚠️

⚠️

❌

⚠️

✅

⚠️

❌

⚠️

Windows-MCP has Wait/WaitFor; MacOS-MCP has Wait; Anthropic has wait/mouse_move.

Your signed-in Chrome, own tab group or yours

✅

⚠️

⚠️

❌

❌

⚠️

❌

❌

❌

Codex uses its in-app browser; Cua Driver attaches to a browser over CDP; Windows-MCP reads the DOM of open browsers. Munim Computer Use opens its own labelled tab group in your real Chrome, and can also take over a tab you already have open when you ask it to.

Act and observe in one call

✅

✅

n/a

n/a

✅

❌

❌

❌

❌

return_state on any action appends the fresh accessibility tree or page snapshot, so one call acts and checks. Cua Driver's run_steps runs several actions and reports what changed. The Anthropic demo returns a screenshot after each action; Codex and the Agents API run the loop themselves.

Several actions in one call

✅

✅

n/a

n/a

❌

⚠️

❌

❌

✅

Cua Driver and computer-use-mcp (zavora) run a list of steps in one call with run_steps; Windows-MCP's MultiSelect and MultiEdit act on several elements at once. Munim Computer Use's run_steps does the same, checks the whole list before running any of it, and stops at the first failure.

Read a page's text in a signed-in tab

✅

⚠️

❌

❌

❌

⚠️

❌

❌

❌

browser_read returns the page as text with headings, in chunks, with a query filter, from a background tab. Windows-MCP scrapes pages.

Sign-in without the model seeing the password

✅

❌

❌

✅

❌

❌

❌

❌

❌

browser_request_credentials opens a Chrome window naming the site's real origin; what the user types goes into the page and never into the conversation. The Agents API hands sign-in to the developer's app the same way. Desktop password fields refuse typing by default.

Per-app and per-site allow / ask / block rules

✅

❌

⚠️

⚠️

❌

❌

❌

❌

❌

A policy file the user controls; ask shows an approval prompt once per task. Codex asks before each new app and keeps an "Always allow" list; the Agents API asks the developer's app before each new website.

Works with any MCP client

✅

✅

❌

❌

❌

✅

✅

✅

✅

Codex Computer Use is Codex only; the Anthropic demo is Claude only.

Identical tool surface on every platform

✅

⚠️

n/a

n/a

n/a

n/a

n/a

⚠️

✅

38 tools with byte-identical schemas across the Swift and Rust servers. Cua Driver keeps shared parameters in step with a CI check, with a few tools on one platform only.

Prebuilt signed binaries + npm launcher

✅

✅ (script)

✅

n/a

❌

❌

❌

✅ (npm)

✅ (npm)

macOS universal (Developer ID signed) and Windows x64 on Releases.

Open source

✅ Apache-2.0

✅ MIT

❌

❌

✅

✅ MIT

✅ MIT

✅ MIT

✅ MIT

Also looked at: mediar-ai/mcp-server-macos-use (macOS, accessibility, real input), deploymenttheory/windows-mcp-server (Windows, UIA Invoke patterns, WaitFor), nuphus-mcp (OCR + bring-your-own vision model, CDP Chrome), computer-control-mcp (PyAutoGUI + OCR), and microsoft/playwright-mcp (browser only). Corrections welcome — open an issue with a link.

The chart counts the table above (n/a cells are left out), so it shares the table's bias: we picked the rows.

Benchmarks

Reading a window is the step an agent repeats most, so every server we could run reads the same file manager window on each system as the folder grows from 100 to 20,000 files: File Explorer on Windows, Finder on macOS and Thunar on Linux. Each one ran its latest release on 4 November 2026. munim-computer-use is the fastest at every folder size on all three, and returns the fewest tokens on all three.

Windows 11 · File Explorer

Server

Read tool

100 files

1,000

5,000

20,000

Tokens per read (≈)

Files listed

munim-computer-use 0.10.0

get_app_state

0.12 s

0.12 s

0.13 s

0.12 s

2.0K

16

munim-computer-use 0.9.3

get_app_state

0.12 s

0.12 s

0.13 s

0.13 s

2.0K

16

Cua Driver 0.38.0

get_window_state, include_screenshot: false

0.15 s

0.16 s

0.15 s

0.16 s

3.9K

17

Windows-MCP 0.8.8

Snapshot

15.37 s

15.71 s

15.24 s

15.59 s

1.2–3.9K

0

computer-use-mcp 7.8.0 (zavora)

get_ui_tree (COMPUTER_USE_PROFILE=full)

0.44 s

0.40 s

0.43 s

0.40 s

9.3–9.4K

17

open-computer-use 1.1.3

get_app_state, include_screenshot: false

3.09 s

2.96 s

3.23 s

3.22 s

4.9K

17

Windows-MCP 0.8.8 takes about 15 s per Snapshot on this PC, where 0.8.5 took 0.7 s on the same windows the same day (and in our 27 October run), so the slowdown is in that release. At 1,000 files its last read left the File Explorer window out (1.2K tokens, no files). munim lists 16 rows and the others 17: munim's on-screen check leaves out the row at the bottom edge of the list.

macOS 27 · Finder

Server

Read tool

100 files

1,000

5,000

20,000

Tokens per read (≈)

Files listed

munim-computer-use 0.10.0

get_app_state, window: 0

0.04 s

0.05 s

0.06 s

0.10 s

1.0K

18

munim-computer-use 0.9.3

get_app_state, window: 0

0.04 s

0.05 s

0.06 s

0.10 s

2.3–2.4K

18

Cua Driver 0.38.0

get_window_state, include_screenshot: false

0.29 s

0.29 s

0.30 s

0.35 s

2.4K

13

computer-use-mcp 7.8.0 (zavora)

get_ui_tree (COMPUTER_USE_PROFILE=full)

1.16 s

1.18 s

1.16 s

1.17 s

22.4K

38

open-computer-use 1.1.3

get_app_state, include_screenshot: false

0.13 s

0.30 s

0.98 s

3.59 s

1.1K

20

MacOS-MCP 0.4.7

Snapshot, use_vision: false

0.36 s

2.26 s

8.63 s

73.91 s

2.6–17.3K

18

A MacBook Air (M1) on macOS 27.2. munim reads one window here (window: 0), as the others do; without it, get_app_state lists every Finder window. Cua Driver ran with mcp --direct and open-computer-use with OPEN_COMPUTER_USE_DISABLE_APP_AGENT_PROXY=1, their documented switches for running inside the process that holds the permissions, since their own apps had none on this Mac. Cua Driver's read stops at 99 elements, which leaves out part of the file list; zavora lists rows scrolled out of view too. MacOS-MCP's reads swing between about 0.3 s and 17 s at 5,000 files and take over a minute at 20,000.

Linux · Thunar

Server

Read tool

100 files

1,000

5,000

20,000

Tokens per read (≈)

Files listed

munim-computer-use 0.10.0

get_app_state

0.19 s

0.20 s

0.24 s

0.23 s

1.2K

33

munim-computer-use 0.9.3

get_app_state

0.51 s

0.52 s

0.55 s

0.20 s

0.4–1.9K

33

Cua Driver 0.38.0

get_window_state, include_screenshot: false

0.66 s

0.30 s

0.29 s

0.29 s

2.3–5.7K

0

computer-use-mcp 7.8.0 (zavora)

get_ui_tree (COMPUTER_USE_PROFILE=full)

0.85 s

0.91 s

0.88 s

0.86 s

28.6K

0

open-computer-use 1.1.3

get_app_state, include_screenshot: false

1.58 s

2.89 s

4.93 s

12.90 s

26.0–42.7K

175

Ubuntu in WSL 2 on the same laptop, X11 (Xvfb, 1920×1080) with bspwm and AT-SPI. Thunar's default icon view tells assistive technology nothing about its files, so every server read its details view. Only munim lists exactly the 33 rows in view: Cua Driver's read listed files only at 100 files, zavora's tree lists none of them, and open-computer-use lists every row it has, in or out of view, and timed out at 20,000 files on some calls. munim 0.9.3 read nothing of the 20,000-file folder, because AT-SPI refuses to hand over more than 65,536 children at once; 0.10.0 walks such a list by index.

How it was measured

For each folder size a fresh file manager window was opened in front and every server was started once, called over stdio with its own read tool (text only where the tool has a switch for it), one warm-up call dropped, then 5 timed calls; the whole run was done twice, so each time is the median of 10. Tokens are estimates at 4 characters per token. "Files listed" counts the folder's file names in the 1,000-file read. Terminator was left out: its get_window_tree picks the first explorer process, which is the taskbar, and it takes no pid. The scripts are in a scratch kit, not this repository; ask in an issue if you want them.

Acting

On the same Windows PC: the five clicks of 1 + 2 = on Calculator (clear, 1, +, 2, =), each a separate call by element id, after one read. Median of 3 runs.

Server

Five clicks

Per click

munim-computer-use 0.10.0

1.70 s

0.34 s

Cua Driver 0.38.0

2.31 s

0.46 s

Most of a click is the agent pointer flying to the target so you can see where the agent acts. run_steps sends all five in one call.

Every Windows release

The same window read by every Windows release, from the release binaries, in two rounds of all 17 with Windows-MCP in each round, plus 0.8.0 measured against 0.7.3 the same way, on 27 October 2026 (an earlier run than the ones above, so the times differ a little; Windows reads have not changed since 0.8.0, and 0.9.3 and 0.10.0 measured alike above). 0.7.0 is where get_app_state started reading only what is on screen and fetching each element's attributes in one request; 0.8.0 fetches the whole window in one request instead of one per element, with the same output.

Release

Read time (median)

Characters

Tokens (≈)

0.2.0

0.40 s

9,860

2.5K

0.3.0

0.43 s

9,860

2.5K

0.3.1

0.38 s

9,860

2.5K

0.4.0

0.38 s

9,860

2.5K

0.4.1

0.38 s

9,860

2.5K

0.4.2

0.39 s

9,860

2.5K

0.4.3

0.40 s

9,860

2.5K

0.4.4

0.38 s

9,860

2.5K

0.5.0

0.38 s

9,860

2.5K

0.5.1

0.37 s

9,860

2.5K

0.5.2

0.43 s

9,860

2.5K

0.6.0

0.37 s

9,860

2.5K

0.6.1

0.41 s

9,860

2.5K

0.7.0

0.23 s

7,499

1.9K

0.7.1

0.28 s

7,499

1.9K

0.7.2

0.25 s

7,520

1.9K

0.7.3

0.25 s

7,520

1.9K

0.8.0

0.16 s

7,515

1.9K

Windows-MCP 0.8.5 Snapshot

0.75 s

14,929

3.7K

node scripts/readme-charts.mjs redraws the charts; the coverage chart is counted from the capability matrix.

Why it works well

  • Accessibility first, pixels second. get_app_state returns the app's accessibility tree with stable element ids, so the agent presses the button instead of guessing at a coordinate. It costs a fraction of the tokens of a screenshot and it is what scores highest on OSWorld-style tasks. Screenshots are for verifying and for content the tree cannot describe.

  • What is on screen, fast. get_app_state lists what is in view: a list or table gives its visible rows, and anything scrolled away is skipped and counted (offscreen: true lists it too). Attributes are read in one request per element, and on Windows the whole window comes back in a single request. Rows of a list print one to a line. A Finder window reads in about 0.05 s, File Explorer in about 0.12 s and Thunar in about 0.2 s, faster than any other server we measured on each system (see Benchmarks).

  • Dialogs first. While a sheet, alert or popover blocks a window, only its controls are listed, with a note saying so, so the agent answers the dialog instead of clicking behind it.

  • Electron and Chromium apps (macOS). Slack, VS Code, Discord and other Chromium-based apps expose their tree only to a client that asks; get_app_state asks, and says so while a background app is still building it.

  • Text the tree cannot describe. When an app exposes no labelled controls, its window is read with on-device OCR (Apple Vision on macOS, Windows OCR on Windows) and the text is listed with ids that click, hover, right_click and drag accept. The image never leaves the machine. ocr: "always" or "never" overrides it.

  • Ids that fail instead of misfiring. Every id is rechecked before an action. If its element is gone, or its role or name changed (a dialog swapped its buttons, a list re-sorted), the action fails with a clear error instead of acting on whatever sits there now.

  • Waits as long as the app takes. return_state waits for the app's accessibility notifications to stop instead of a fixed pause: about 230 ms for a field edit (was 350 ms), and long enough for a sheet to finish sliding in.

  • Background control. Events are addressed to the target window (SkyLight on macOS, UI Automation patterns and posted window messages on Windows). On macOS the agent never takes your mouse or keyboard; see Works alongside you for the exact guarantee on each platform.

  • Pointer overlay, not your pointer. A soft lavender agent pointer shows where the agent is acting. Your cursor is untouched.

  • Coordinates that land. Every screenshot and zoom carries its screen origin and pixels-per-point. zoom captures any region at full physical resolution.

  • Your browser, your logins. The Chrome extension gives the agent its own labelled tab group in your signed-in Chrome, and leaves your tabs alone unless you point it at one.

  • Or the tab you already have open. browser_list_tabs all=true shows every tab in the browser and browser_use_tab takes one over in place — useful when the page is already signed in or mid-flow and re-opening the URL would throw that away. An adopted tab is not moved into the agent's group, not activated and not reloaded; cleanup releases it rather than closing it, and browser_release_tab hands it back early.

  • Tabs the agent's clicks open are the agent's too. A target=_blank link or popup opened by an agent click or key press within 10 seconds joins that task's tabs and group, and its cleanup closes it. A tab opened from an agent tab without a recent agent action stays yours.

  • Parallel tasks, one extension. Every MCP process gets its own tab group, and one process can run several tasks by passing a stable session_id on its browser calls. A task cannot drive or adopt another task's tabs, and cleanup (including a process exiting) closes only its own. Any number of MCP processes share the one extension: the first owns it and the rest go through it, and if the owner exits another takes over without closing anyone's tabs. Tasks share Chrome's cookies and logins, and desktop apps and the clipboard are not isolated.

  • Model-agnostic. No vision model is required for interaction; local models work too.

  • Look → act → verify. hover for mouse-over menus, wait for loads, query to find a control by label without reading a whole tree.

  • Act and look in one call. Pass return_state: true to any action (click, type_text, set_value, browser_click, browser_navigate, …) and the result carries the app's fresh accessibility tree, or the page's current snapshot. Browser actions report readiness separately: a loaded tab is not proof that delayed SPA UI has rendered. Use wait_for_selector for an expected visible element (see below). state_query narrows the desktop tree the same way query does.

  • Several steps, one call. run_steps takes a list like click the field, type, press return and runs it in order, each step with the same checks as a direct call. The whole list is checked before anything runs, the first failing step stops the rest, and the result says what ran and what did not. Put return_state on it to see the app after the last step.

  • Pages as text. browser_read returns a page's readable text with its headings, including what is scrolled out of view, from a tab in the background. It comes in chunks you can continue with offset, query keeps only the matching lines under their heading, and include_links lists the links.

  • Frames too. A support chat, payment form or sign-in box embedded in a page is its own document, often from another site. browser_snapshot lists its controls after the page's, marked (in frame "…"), and their indices work with browser_click, browser_fill and browser_upload_file; browser_read adds each frame's text under a [frame "…"] line, and wait_for_selector looks inside frames. Your site rules apply to a frame's own site, and browser_request_credentials never fills a field inside a frame.

  • See a tab without taking your window. browser_screenshot captures what one of the agent's tabs shows, frames included, while it stays in the background: the tab is not selected, its window is not raised and the screen is not captured, so you keep working in your own tab. browser_select_tab bring_to_front=true is the only browser call that takes your window, and it is a last resort.

  • JavaScript dialogs, answered on purpose. When a page opens an alert, confirm or prompt, browser_snapshot shows its type, message and id, and other commands on that tab say a dialog is open instead of timing out. browser_handle_dialog accepts or dismisses it, with text for a prompt, in a background tab without the keyboard. A dialog that replaced the one the agent read is refused, and a dialog never counts as an answer on its own.

  • Why a page is broken. browser_page_errors lists the uncaught exceptions and console errors a tab's page has logged since it loaded (as far as Chrome keeps them), and the requests that failed (network errors and HTTP 400 or above) since the agent attached, the latest 50, with URLs stripped of query strings and fragments. Exceptions and console errors are read from Chrome's own record during the call, so the DevTools Runtime domain, which bot checks on signed-in sites look for, is not left on in between. Headers, cookies and bodies are never read, and the tab is not reloaded or shown.

  • Passwords stay out of the conversation. browser_request_credentials asks the user to sign in in a small Chrome window that shows the site's real origin. What they type goes straight into the page's fields and is never returned to the model. browser_snapshot never shows a password field's value, and on the desktop, typing into password fields is refused by default.

  • Pick up what you are looking at. get_app_state and screenshot take app: "frontmost" for the app in front of the user, so a client can hand the agent "this window" in one step.

Works alongside you

The agent has its own pointer; yours stays yours.

macOS — guaranteed. Every action goes through accessibility (press, set value, select text, show menu, scroll bars) or through events addressed to the target app's process and window. The server never moves your pointer, never posts into the system-wide input stream, and never holds or blocks your input, so you can keep clicking and typing in other apps while the agent works — even in the same app, on another window. Events come from a private source, so a modifier you are holding does not leak into the agent's clicks. The target app may be brought forward when that is the point of the step (activate_app, or handing you a password field), but not on every action.

Command shortcuts sent to a background app run through its menu bar (macOS gives menu key equivalents only to the front app), so cmd+W, cmd+S and the like work without bringing it forward. A minimized window, or a hidden app, is moved for the moment it needs pixels or input (typing, a coordinate click, a screenshot, OCR) onto an invisible display, where it works while nobody sees it; it is minimized or hidden again after 20 idle seconds, when another app's window needs the display, or when the server exits. If you bring the app up yourself meanwhile, its windows come back on screen where they were. Accessibility actions on such a window (press, set value) need none of this. COMPUTER_USE_PARK_WINDOWS=0 turns it off, and those actions then return an error asking for the window to be restored.

The exceptions refuse instead of borrowing your pointer: a click, hover or scroll with no target app (coordinates over the desktop before any get_app_state), and drags that leave the source window (between apps, or onto the desktop). The error says what to pass instead.

Windows — best effort, reported. Element presses (Invoke), set_value, select_text and scrolling through UI Automation's ScrollPattern never touch your pointer, and classic Win32 controls also take clicks, hovers, wheel and drags as posted window messages. Other UI (Chromium, Electron, WPF, UWP) only reacts to real mouse input, so those clicks, hovers and drags move your pointer, and type_text/press_key go to the focused window. Whenever the real pointer was used, the result says via cursor. A coordinate action on an element that another app's window covers is refused rather than clicking that window, and pointer and keyboard actions on a minimized window return an error asking for it to be restored (accessibility actions still work on it).

Linux — pointer actions use it. XTEST input moves the real pointer and goes to the focused window, and results say via cursor. Element actions through AT-SPI (press, set value, insert text, select) do not move it.

Apps and sites the agent may use

You decide which apps and websites the agent may touch. Put a policy.json in the server's support directory (~/Library/Application Support/computer-use on macOS, %LOCALAPPDATA%\munim-computer-use on Windows, ~/.local/share/munim-computer-use on Linux; munim-computer-use identity prints it as supportDir), or point COMPUTER_USE_POLICY at a file anywhere. An app that embeds the server under its own profile reads the file from its own support directory:

{
  "apps": { "Keychain Access": "block", "com.apple.MobileSMS": "ask" },
  "sites": { "bank.example": "block", "mail.google.com": "ask", "*": "allow" },
  "foreground": "allow"
}
  • allow, ask or block per app or site. ask shows the user a prompt the first time the agent reaches for it (a system dialog for apps, a small Chrome window for sites), and an approval lasts until that agent task ends. An unanswered prompt counts as no after two minutes.

  • Apps match their name or bundle id, ignoring case. The rule applies when the agent reads, captures or activates the app. Actions then target elements from a snapshot that was allowed.

  • Sites match a host and all its subdomains, and the most specific pattern wins. They are checked on every browser action against the page the tab is showing at that moment, so following a link into a blocked site does not get around it.

  • * sets the default, which is otherwise allow, so {"apps": {"*": "block", "Notes": "allow"}} is an allow-list.

  • "foreground" is one rule for the whole screen, not per app. Set it to "block" to keep the agent in the background, so it never takes the screen from you. It covers:

    • activate_app;

    • browser_select_tab with bring_to_front;

    • on Windows, typing into a window that is not in front, and any action that would fall back to your real pointer;

    • on Linux, every pointer action (they all move the real pointer).

    Those calls fail with foreground_required before anything moves, and work that runs in the background goes ahead. "ask" prompts once per agent task. A tool argument cannot override the rule. Remote control from a viewer is exempt, because there you are the one driving.

  • Changes apply at once, with no restart. A file that is not valid blocks everything rather than being ignored, so a typo cannot quietly turn a block into an allow.

This is a guard rail for an agent that follows its instructions, not a sandbox. A coordinate click lands wherever it points, and a whole-display screenshot shows every window.

Tools (38)

Area

Tools

See

list_apps, get_app_state (with query), screenshot, zoom, list_displays

Act

click, right_click, hover, drag, scroll, type_text, set_value, select_text, press_key, activate_app, wait, run_steps

Clipboard

clipboard_read, clipboard_write (plain text)

Browser

browser_open_tab, browser_list_tabs, browser_use_tab, browser_release_tab, browser_select_tab, browser_navigate, browser_snapshot, browser_read, browser_screenshot, browser_page_errors, browser_click, browser_type, browser_fill, browser_upload_file, browser_request_credentials, browser_press_key, browser_handle_dialog, browser_close_tab, browser_close_all_tabs

Every action tool also takes return_state, which returns current state after the action in the same call. run_steps runs a list of them in one call and stops at the first that fails.

Editing a page in a background tab

The agent's tabs stay in the background: the browser tools never select them, raise Chrome's window, or send OS keystrokes. While the agent holds a tab, its DevTools session emulates focus, so Chrome keeps rendering the tab and real input reaches it:

  • browser_click presses with trusted mouse events, which frameworks that ignore synthetic clicks (Angular Material buttons, for example) respond to. Page events remain only as a fallback, and the reply says which route ran.

  • browser_press_key takes any key plus modifiers (cmd, ctrl, alt, shift): Delete, arrows, Home/End, PageUp/PageDown, F-keys, characters and shortcuts. Editing shortcuts such as select all (key: "a", modifiers: ["cmd"]) mean the same with cmd or ctrl on every platform. Browser shortcuts like close-tab never reach Chrome itself.

  • browser_fill replaces a field's whole value by its snapshot index: input, textarea or contenteditable, open shadow roots included. It selects everything, inserts the new text through Chrome's text input (trusted beforeinput/input events), and says whether the field now holds exactly that text.

  • browser_upload_file attaches local files to a file input by its snapshot index, or by the index of a label for one. Chrome sets the input's files (DOM.setFileInputFiles) and the page gets its usual input/change events. No file picker opens and no OS input is sent. Snapshots list file inputs as input[file], including ones a page hides behind a styled button, and show how many files each holds. Paths are on the computer running Chrome and the server, and the server checks each is a readable regular file before sending it. Several files need an input with multiple. The tool only stages the files: it never submits the form, and the reply says the site has not received them yet.

  • browser_snapshot also lists table and grid rows (row "Ana | Admin | Active"), cells and other elements that are only clickable by handler or pointer cursor, so list rows can be found and clicked. Checkboxes, radios and switches are named by their label, never their value ("on"), and show their state: input[checkbox] "Admin (all permissions)" (unchecked).

  • A click never lands on the wrong thing. An element out of view is scrolled into view, and the press goes only to a point where the element itself (or its own label) is on top. When a banner, dialog or another control covers it, nothing is pressed and the call fails naming what is in front. A checkbox click reports what the box is now.

The desktop press_key and type_text go to whichever Chrome window is in front, which may be the user's, so never use them on an agent tab.

Typing alongside the user

The person at the computer can keep typing in their own windows while the agent types elsewhere. Browser tools never touch the OS keyboard. On the desktop, delivering keys or a click to a background app lends that app keyboard focus for the length of the action. On macOS, keys the person presses during that moment are held in a short-lived session event tap and replayed, in order, to their own window once it has focus again (set MUNIM_COMPUTER_USE_HOLD_USER_KEYS=0 to turn this off). Windows holds and replays them the same way. A desktop click by element_id that has to use a position checks it with the app's own hit test first, and fails instead of clicking whatever else is there.

Browser readiness and safe re-observation

browser_click, browser_type, browser_fill, browser_upload_file, browser_press_key, and browser_navigate accept wait_for_selector together with return_state: true. browser_snapshot accepts the same wait without performing an action:

{"tab_id": 42, "index": 0, "return_state": true,
 "wait_for_selector": "[role=dialog] button", "wait_timeout_ms": 3000}

The extension polls for any matching visible element in the page, open shadow roots included (each root is matched on its own, so one selector cannot span a host and its shadow tree), independently of resource loading, including same-document rendering. It shares the snapshot's visibility rules: at least 2×2 CSS pixels; no hidden, aria-hidden, inert or fully transparent ancestor. Self-transparent native form controls remain available for styled upload/checkbox hit targets; transparent containers do not. This is not a guarantee of clickability or lack of occlusion. Frames are searched too (a match there names the frame); closed shadow roots are not, and a timeout says how many frames could not be searched. Choose a selector specific to the new expected UI; an element already present can satisfy it immediately. No arbitrary page script is accepted, and a selector may not test the value attribute: React mirrors a controlled input's value there, password fields included, so a wait could otherwise confirm a password one guessed character at a time.

  • wait_timeout_ms: defaults to 8000, accepts integers 0–10000; 0 checks once. Requires wait_for_selector. The budget covers polling, not browser/API latency or user site-approval prompts. Site rules are rechecked before every observation and before the final snapshot.

  • readiness: met (visible_selector, … ms): the specified condition was observed, not a guarantee that all network requests, animations, or application work ended.

  • readiness: timeout: the condition was not observed within the budget. The action's success remains separate, and the current snapshot is still returned.

  • readiness: error: the condition could not be evaluated (for example invalid CSS); the action can still have succeeded. Correct the wait and re-observe.

  • Without a selector, return_state retains the short pause and bounded navigation-load wait, but reports not_requested (document_load, … ms) rather than claiming SPA readiness. A navigation-load budget expiry reports timeout. A plain browser_snapshot without a wait remains an immediate observation.

If a returned snapshot looks unchanged, do not repeat a non-idempotent click just because expected UI is absent. Wait/re-observe first:

{"tab_id": 42, "wait_for_selector": "[role=dialog] button", "wait_timeout_ms": 3000}

Send this to browser_snapshot, inspect its readiness and fresh indices, then choose the next action. A timeout or tool error does not roll back a click, submission, typing, or navigation. Use current state to decide whether a retry is safe. Invalid wait types/bounds or an action wait without return_state: true are rejected before acting. null for an optional argument means the same as leaving it out. These options require an updated extension as well as the native server; when an older extension ignores them, the result says so and asks for a reload.

Regression checks: node --test chrome-extension/background.test.mjs covers the wait contract and site-policy guards. The real-browser CI harness scripts/e2e-browser.mjs tests a dialog delayed by 1100 ms without navigation, one click only, plus timeout and read-only recovery. The dependency-free CDP regressions cover both snapshot scoping/pagination and SPA readiness. Run against an isolated Chrome/Chromium profile; no Playwright module is required:

node scripts/snapshot-dom.test.mjs --chrome /path/to/chrome
# Extract the pre-fix source; each suite must fail independently:
git show deb4c7a:chrome-extension/background.js > original-background.js
node scripts/snapshot-dom.test.mjs --chrome /path/to/chrome --source original-background.js --suite snapshot
node scripts/snapshot-dom.test.mjs --chrome /path/to/chrome --source original-background.js --suite readiness

browser_snapshot automatically scopes to the topmost visible dialog when one is open, so covered background controls cannot consume the dialog's budget. A dialog's portalled popups stay in scope: elements its controls point at through aria-controls/aria-owns, open popovers, and controls painted on top of the dialog, such as a select's options rendered at the end of <body>. Hidden and inert subtrees are excluded; reachable offscreen controls remain available. Native modeless dialogs and explicit aria-modal="false" do not scope the page. Native modal dialogs and explicit aria-modal="true" are definitive candidates. Implicit ARIA dialogs are heuristically scoped only when they cover the viewport centre and are topmost there, rather than merely at their own centre. With no hit-test stack, centre coverage is the fallback. Corner chat widgets and bottom cookie banners therefore retain the page's indexed controls. It returns up to limit controls (default and maximum 250), reports scope, offset, limit, and total, and explicitly says when it is truncated and which offset to request next. Continue on the same tab_id and session_id. Indices are global within the selected scope and stay actionable across pages of an unchanged UI; after a UI change or navigation, take a fresh snapshot from offset=0. An action's return_state also starts at offset=0 and uses the same scope and truncation reporting.

Names, argument shapes and descriptions are identical on every platform, and CI enforces it (node scripts/check-tool-parity.mjs); a model that learned them on a Mac needs nothing new on Windows. To change a tool, edit both literals at once with scripts/tool-defs.mjs rather than by hand.

Repository layout

Directory

What

Build

macos/

Swift server on the Accessibility API and ScreenCaptureKit (macOS 14+)

swift build -c release → .build/release/munim-computer-use

windows-linux/

Rust server: UI Automation on Windows, AT-SPI + X11 on Linux

cargo build --release → target/release/munim-computer-use

chrome-extension/

Chrome extension + native messaging host for the browser_* tools

Load unpacked; sh install.sh / install.ps1 registers the host; node background.test.mjs

Dockerfile

Headless Linux build of the Rust server for registry introspection (Glama and similar); no desktop control inside a container

docker build -t munim-computer-use .

Build from source

On Fedora, install the native development libraries before building the Rust server (in addition to your Rust toolchain):

sudo dnf install gcc pkgconf-pkg-config clang-devel pipewire-devel \
  libdrm-devel mesa-libgbm-devel mesa-libEGL-devel wayland-devel libxcb-devel

These are needed by the screen-capture dependencies even on X11. In particular, a missing libpipewire-0.3.pc during the build means pipewire-devel is missing.

# macOS
cd macos && swift build -c release
# Windows / Linux
cd windows-linux && cargo build --release

Linux notes: element actions work everywhere; coordinate clicks need an X11 or XWayland client, since native Wayland apps do not expose absolute geometry.

Tests

cd windows-linux && cargo test                  # Rust server
node chrome-extension/background.test.mjs       # extension, against a fake Chrome
node scripts/check-tool-parity.mjs              # Swift and Rust tool lists match
# Snapshot and SPA readiness regressions (dependency-free CDP, isolated profile):
node scripts/snapshot-dom.test.mjs --chrome <Chrome for Testing binary>
# The browser tools end to end, in a throwaway Chrome for Testing profile that
# never touches your own Chrome (npx playwright install chromium to get one):
node scripts/e2e-browser.mjs --server <munim-computer-use binary> --chrome <Chrome for Testing binary>

Chrome extension (optional)

  1. chrome://extensions → Developer mode → Load unpacked → select chrome-extension/.

  2. Register the native messaging host: munim-computer-use install-native-host (for example npx -y munim-computer-use install-native-host), or from a checkout sh chrome-extension/install.sh (macOS/Linux) / powershell -File chrome-extension/install.ps1 (Windows), which find the build and run the same command. Point COMPUTER_USE_PATH at the binary if it is not in the default build location.

If the extension still gets Specified native messaging host not found., or the browser tools say the extension is not connected, run munim-computer-use check-native-host in an ordinary terminal. It looks the host up the way Chrome does (on Windows: registry views and policies; everywhere: the manifest's encoding, name, type and allowed origins), starts it with Chrome's command line, and names the step that would fail. Two Windows causes look fine from the inside. An installer run by an MSIX-packaged app (some agent desktop apps are packaged) writes to a private, virtualised copy of HKCU and %LOCALAPPDATA% that Chrome never reads. One run by a sandbox with its own Windows account writes to that account's HKCU. The check detects both; run install-native-host again from your own terminal. install-native-host reports the same findings under problems.

The standalone server's host is com.munimtech.computer_use.desktop; MT Code, which bundles this server, registers com.munim.mtcode.desktop. The extension connects to every host it knows at once and answers each on its own connection, so MT Code and a standalone server (npx, Claude Code, Cursor, a checkout) can both drive Chrome at the same time, each in its own tab groups.

Sandboxed agents (Windows)

Some agents run their commands in a sandbox: a restricted or low-integrity token, a separate Windows account, or another desktop. A server started there can still list windows and their titles, but other apps' UI Automation refuses it and captures come back black, without any error. When that happens the server says so: get_app_state explains an empty tree, screenshot and zoom flag a capture that is one flat colour, and activate_app checks that the window really came forward. Each names the cause it found (for example "runs at low integrity" or "runs as another account"). Retrying from the same context will not help; configure the agent to start the MCP server in your own, unsandboxed context.

Environment flags

Variable

Effect

COMPUTER_USE_BROWSER=0

Hide the browser_* tools

COMPUTER_USE_AGENT_CURSOR=0

Do not draw the agent pointer

COMPUTER_USE_AGENT_CURSOR_TASK_FADE_SECS

How long the pointer stays after the last tool call (default 8)

COMPUTER_USE_ALLOW_SECURE_FIELD_INPUT=1

Allow typing into password fields (refused by default)

COMPUTER_USE_REMOTE_CONTROL=1

Remote-desktop mode: input takes over the real pointer

COMPUTER_USE_PARK_WINDOWS=0

macOS: do not move minimized or hidden windows to an invisible display to act on them

COMPUTER_USE_POLICY=<file>

Where to read the app and site policy (default policy.json in the support directory)

Remote control

Normally this server never touches the pointer: coordinate clicks are routed to a specific window, keystrokes are posted to a specific process, and an action that cannot be targeted is refused rather than taking over the machine. That is what lets an agent work while the user keeps using their computer.

COMPUTER_USE_REMOTE_CONTROL=1 inverts that contract for one process, for the case where a person is watching this machine's screen from another one and is steering it themselves. Then click, right_click, drag, hover and scroll move the real cursor and type_text and press_key go to whatever is focused, the way Chrome Remote Desktop or Screen Sharing behave. scroll also accepts x/y so the wheel acts over the point the viewer scrolled at, and the agent-cursor overlay stays hidden — there is only one pointer now.

A host that runs both an agent and a viewer runs them as two processes, so turning this on for the viewer never takes the pointer away from the user on the agent's behalf.

Embedding in an app

An app can ship this binary inside its own bundle and run it under its own identity, so it never shares a browser bridge, agent-cursor app or native-messaging host with a standalone install on the same machine. MT Code does exactly this. Pass a profile, either as a JSON object or a path to a JSON file, with --profile <json|file> (any position) or COMPUTER_USE_PROFILE:

{
  "name": "example-desktop",
  "envPrefix": "EXAMPLE_DESKTOP_",
  "agentCursorName": "ExampleAgentCursor",
  "agentCursorBundleId": "com.example.agent-cursor",
  "nativeHostNames": ["com.example.desktop"],
  "extensionIds": ["abcdefghijklmnopabcdefghijklmnop"],
  "nativeHostDescription": "Example desktop control bridge"
}

Key

Default

Meaning

name

none (standalone paths)

Moves the support dir, bridge socket and Windows pipe under this name

supportDir

~/Library/Application Support/computer-use, $XDG_DATA_HOME/munim-computer-use, %LOCALAPPDATA%\munim-computer-use

Native-host wrapper, profile copy; on macOS also the bridge socket and a bare build's overlay app

bridgeSocket

<supportDir>/bridge.sock (macOS), $XDG_RUNTIME_DIR/<name>/bridge.sock (Linux), <name>-bridge-<user> pipe (Windows)

Where the MCP server and the Chrome relay meet

envPrefix

none

Tunables are read as <prefix>BROWSER, <prefix>AGENT_CURSOR, … before COMPUTER_USE_*

agentCursorName / agentCursorBundleId

MunimAgentCursor / com.munimtech.computer-use.agent-cursor

The pointer overlay's app, executable and window-class name, and its macOS bundle id

historyDir

none

Default --root for computer-history

nativeHostNames / extensionIds

com.munimtech.computer_use.desktop, com.munim.mtcode.desktop / kgdolgnijopbghhomnblabjkmjhnoage

What install-native-host registers, and for which extension. The first name is this identity's own; later ones are aliases, written only if no other installed app owns them

Each of supportDir, bridgeSocket, envPrefix, agentCursorName, agentCursorBundleId and historyDir can also be overridden by COMPUTER_USE_<SNAKE_CASE> (for example COMPUTER_USE_SUPPORT_DIR), which wins over the profile. munim-computer-use identity prints the resolved values.

  • Browser bridge. Run munim-computer-use install-native-host with the same profile. It writes a wrapper that relays Chrome into this identity's bridge (replaying the profile), and a host manifest for each name in every Chrome/Chromium profile directory (the registry on Windows). It rewrites nothing that is already current, so an app can call it on every launch.

  • Extension. Use the stock extension (it connects to com.munim.mtcode.desktop and com.munimtech.computer_use.desktop), or build a variant with its own host names, tab-group title and key: node scripts/build-extension.mjs --out <dir> --host com.example.desktop --group-title "Example" --key <base64>. It prints the variant's extension id for extensionIds.

  • macOS permissions. The MCP server is a bare executable, so Accessibility and Screen Recording are granted to the app that spawns it. Only the agent-cursor overlay has a bundle of its own; ship <agentCursorName>.app (a copy of the binary plus an LSUIElement Info.plist) beside the binary, or it is materialised under supportDir on first use.

Prompting your agent

Look → act → verify. get_app_state for ids, act by id with return_state: true to see the result in the same call, and screenshot when the tree cannot show it. Read pages with browser_read, and let the user type passwords through browser_request_credentials. Use zoom for small text, hover for menus that appear on mouse-over, wait after loads, keyboard shortcuts for stubborn widgets. The system-prompt text MT Code gives its agents lives in CodexDeveloperInstructions.ts and is a good starting point.

Contributing

The server is developed here. MT Code ships it by pinning a release of this repository (native/munim-computer-use.json), so changes reach MT Code with the next release. Issues and pull requests are welcome.

Credits and license

Designed and built by Munim Technologies (Munim, Inc.) for MT Code. On-screen pruning, dialog scoping, notification-based settling, OCR fallback, menu shortcuts and window parking follow arc-cua (MIT). Copyright 2026 Munim, Inc. Licensed under the Apache License 2.0; see LICENSE.

Comparison sources: Codex Computer Use, its docs, its Windows notes and MacStories on how it reads apps · Cua Driver and its tool reference · OpenAI Agents API computer use · Anthropic computer-use demo · CursorTouch/Windows-MCP · CursorTouch/MacOS-MCP · QwenLM/open-computer-use · zavora-ai/computer-use-mcp · mediar-ai/mcp-server-macos-use · deploymenttheory/windows-mcp-server · nuphus-mcp · computer-control-mcp · microsoft/playwright-mcp

Available Tools

37 tools
activate_appActivate appA
Idempotent

Bring an app's windows to the foreground and give it keyboard focus. type_text, press_key and element-id actions such as click and set_value do not need it; use it only when the app itself must be in front. Side effect: the window the user was working in loses focus.

Inputs: app (required), window_id.

browser_clickClick in browserA
Destructive

Click in one of the agent's tabs, either an element by its index from browser_snapshot (preferred) or a point given in page coordinates. Pass index or x and y, not both. Works on a background tab with real (trusted) mouse input, without selecting the tab or raising its window. An element out of view is scrolled into view first; when something else covers it (a banner, a dialog, another control) nothing is pressed and the call fails, naming what is in front. A checkbox, radio or switch reports what it is now. A tab the click opens (a target=_blank link or a popup) becomes one of the agent's tabs; find it with browser_list_tabs. Use click for native app windows. A click can submit forms or follow links, so snapshot first.

Inputs: tab_id (required), x, y, index, +4 more.

browser_close_all_tabsClose all agent tabsA
DestructiveIdempotent

Close every tab the agent opened, including tabs its clicks opened, and remove its tab group. Tabs taken over with browser_use_tab are released back to the user, not closed. Call this when finished with the browser so no empty group is left in the user's tab strip. The MCP process also runs this automatically when the Computer Use session ends. Unsaved state in the agent's tabs is lost.

Inputs: session_id.

browser_close_tabClose browser tabA
DestructiveIdempotent

Close one of the agent's tabs, discarding any unsaved page state. A tab taken over with browser_use_tab is released rather than closed — it belongs to the user. Use browser_close_all_tabs to clean up everything at the end of a task.

Inputs: index, tab_id, session_id.

browser_fillFill field in browserA
DestructiveIdempotent

Replace the whole value of a text field in one of the agent's tabs: focuses the element by its browser_snapshot index, selects everything in it and puts the text in through Chrome's own text input, so the page sees the same trusted input events as typing. Works on input, textarea and contenteditable fields, including inside web components (open shadow roots), in a background tab without selecting it or raising its window. Pass an empty string to clear the field. The reply says whether the field now holds exactly the text, and what it holds when the page changed or limited it (not for password fields). Use browser_type to add at the caret instead, and browser_request_credentials for passwords and codes. Never use the desktop press_key or type_text for a Chrome tab: those go to whichever Chrome window is in front, which can be the user's.

Inputs: text (required), index (required), tab_id (required), session_id, +3 more.

browser_handle_dialogAnswer JavaScript dialogA
Destructive

Answer the JavaScript dialog (alert, confirm, prompt or leave-page) open in one of the agent's tabs. While one is open the page is paused: browser_snapshot shows the dialog's type, message and dialog_id instead of the page, and other browser commands on the tab say a dialog is open. accept presses OK and dismiss presses Cancel. prompt_text is entered into a prompt before accepting it; without it, a prompt is accepted with its default text. Pass the dialog_id from the snapshot, so a dialog that replaced the one you read is refused instead of answered. Works on a background tab without showing it or using the keyboard. A dialog that opened before the agent attached to the tab cannot be seen or answered; the user answers that one in Chrome. Accepting a confirm can submit, delete or leave a page with unsaved changes, so read the message, and ask the user before confirming anything irreversible.

Inputs: action (required), tab_id (required), dialog_id, session_id, +1 more.

browser_list_tabsList browser tabsA
Read-onlyIdempotent

List the tabs in the agent's own Chrome tab group, marking the active one, with the tab_id each other browser tool needs. Pass all=true to see every tab open in the browser, including the user's, so you can pick one to drive with browser_use_tab. Read-only.

Inputs: all, session_id.

browser_navigateNavigate browser tabA
Destructive

Point one of the agent's tabs at a different URL, replacing the current page; unsaved page state is lost. Use browser_open_tab to keep the current page and open another. Follow with browser_snapshot, since element indices reset after navigation.

Inputs: url (required), tab_id (required), session_id, return_state, +2 more.

browser_open_tabOpen browser tabA

Open a URL in a new background tab inside the agent's own labelled tab group in the user's signed-in Chrome, and return its tab_id for browser_snapshot, browser_click, browser_type and browser_navigate. The tab opens in the background, so the user's browsing is not interrupted. Use browser_use_tab instead when the user already has the page open and signed in. Requires the Computer Use Chrome extension; a limited fallback mode applies without it.

Inputs: url, session_id.

browser_page_errorsList page errorsA
Read-onlyIdempotent

List the problems the page in one of the agent's tabs has reported: uncaught JavaScript exceptions and console errors the page has logged since it loaded (Chrome keeps a limited number), and requests that failed since the agent attached to the tab (network errors and HTTP status 400 or above), oldest first, with how long ago each happened. Use it when a page is empty, broken or ignores an action, before navigating again or assuming a sign-in problem. The reply says when watching for failed requests began; ones Chrome still held from before are marked as earlier. Exceptions and console errors are read from what Chrome keeps for the page during this call only, so the page is not watched for them in between. The latest 50 are listed. URLs are shown without query strings, fragments or credentials, and request headers, cookies and bodies are never read. Read-only: it does not reload, navigate or show the tab.

Inputs: tab_id (required), session_id.

browser_press_keyPress key in browserA
Destructive

Press a key in one of the agent's tabs, optionally with modifiers held: Enter to submit, Tab to move to the next field, Escape, Backspace, Delete, arrows, Home/End, PageUp/PageDown, F1-F12, a character, or a shortcut such as key='a' modifiers=['cmd'] to select all in the focused field. Works in a background tab: the key goes to that tab's page only, never to the user's windows, and browser shortcuts (close tab, quit) never reach Chrome itself. Editing shortcuts (select all, copy, cut, paste, undo) work with cmd or ctrl on every platform. The reply says where focus is and how many characters are selected. Tab fails rather than leave focus where it was, and Backspace checks the focused field changed. Use browser_type for text and browser_fill to replace a field's value. Never use the desktop press_key or type_text for a Chrome tab: those go to whichever Chrome window is in front, which can be the user's. Enter can submit forms and Backspace deletes, so check the page state with browser_snapshot first.

Inputs: key (required), tab_id (required), modifiers, session_id, +3 more.

browser_readRead page textA
Read-onlyIdempotent

Read the text of the page in one of the agent's tabs: headings marked with #, then paragraphs, lists and table text in reading order, including content scrolled out of view. Use it to read an article, results or documentation; use browser_snapshot to find something to click. Works on a background tab. Long pages come back in chunks: the result says where it stopped, and offset continues from there. Read-only.

Inputs: tab_id (required), query, offset, max_chars, +2 more.

browser_release_tabRelease adopted tabA
Idempotent

Hand a tab taken over with browser_use_tab back to the user: the agent stops driving it and the page is left exactly as it is. Call it as soon as you are done with an adopted tab. Use browser_close_tab for tabs the agent opened itself.

Inputs: tab_id (required), session_id.

browser_request_credentialsAsk the user to sign inA

Ask the user to sign in on the page in one of the agent's tabs without the values passing through you. Chrome shows the user a small window naming the site's real origin, with one field per input you list; what they type goes straight into those inputs and is never returned to you. Use it for passwords, verification codes and any secret instead of browser_type, and never ask the user to paste a secret into the conversation. Take the input indices from browser_snapshot (password inputs show as input[password]), then click the page's sign-in button yourself. Waits until the user answers, cancels, or the time runs out.

Inputs: fields (required), tab_id (required), reason, session_id, +1 more.

browser_select_tabSelect browser tabA
Idempotent

Make one of the agent's tabs the visible one in its window, for example before native keyboard input or a screenshot. browser_snapshot, browser_click, browser_type and browser_press_key work on background tabs, so most tasks never need this. The agent's tabs live in the user's Chrome window, so by default the tab is only shown when the user is already looking at one of the agent's tabs; otherwise it stays in the background, the user keeps their tab, and the reply says it was not brought to front. Pass bring_to_front=true to show it and raise its window regardless; that takes the window from the user, so use it sparingly. The user's own tabs are never selected.

Inputs: index, tab_id, session_id, bring_to_front.

browser_snapshotSnapshot page elementsA
Read-onlyIdempotent

List the interactive elements (links, buttons, inputs, tabs, checkboxes, table and grid rows listed as row with their cell text, and other clickable elements) on the page in one of the agent's tabs, with the index each one has for browser_click, plus the page title and URL. Inputs show their type, such as input[password], and text the page lets you edit in place shows as [editable], such as span[editable] (click it, or browser_fill it); checkboxes, radios and switches are named by their label and show their state, such as (checked) or (unchecked, disabled). Works on a background tab, so the user can be looking at something else. Use it before every browser_click, because indices change when the page changes; use browser_read for the page's text. Read-only. Automatically scopes to the topmost visible dialog when one is open, naming it on a dialog: line, excluding hidden/inert controls but retaining reachable offscreen controls. Results report scope, total and truncation. Continue with offset and limit on the same tab/session; indices stay global across pages of an unchanged UI. Snapshot again after UI changes. While the page has a JavaScript dialog open (alert, confirm, prompt), the snapshot shows the dialog's type, message and dialog_id instead, for browser_handle_dialog.

Inputs: tab_id (required), limit, offset, session_id, +2 more.

browser_typeType in browserA
Destructive

Type text into the field that currently has focus in one of the agent's tabs; browser_click the field first. Text is inserted at the caret without clearing existing content: to replace a field's whole value use browser_fill. Use browser_press_key for Enter, Tab, Backspace, arrows and shortcuts such as Cmd+A, and type_text for native apps. Never use the desktop press_key or type_text for a Chrome tab: those go to whichever Chrome window is in front, which can be the user's.

Inputs: text (required), tab_id (required), session_id, return_state, +2 more.

browser_upload_fileAttach files in browserA
DestructiveIdempotent

Attach local files to a file input in one of the agent's tabs, by its browser_snapshot index, as if the user had picked them: Chrome sets the input's files and the page gets its usual input and change events. No file picker opens, the tab is not selected or raised, and no keyboard or mouse input is used. Snapshots list file inputs as input[file], including ones a page hides behind a styled upload button; the index of a label for one works too, and any other element is refused. Paths are absolute paths of regular files on the computer running Chrome and this server, which may not be the machine you run on; each is checked before anything is sent. Several paths need an input that takes several files (multiple). This only stages the files in the input: it never submits the form, and the site has not received them until the page shows it has. Submit or press the page's upload button yourself when the user wants that, then check the page. The reply names the files the input now holds. Never click a file input or its upload button to open the picker: the native dialog cannot be driven from here.

Inputs: index (required), paths (required), tab_id (required), session_id, +3 more.

browser_use_tabAdopt user's tabA
Idempotent

Take over a tab the user already has open, instead of opening a new one. Use this when the page is already signed in or mid-flow — a checkout, a draft, a dashboard behind SSO — and re-opening the URL would lose that state. Find the tab_id with browser_list_tabs all=true. The tab stays exactly where it is in the user's window; it is not moved into the agent's group, not activated, and not reloaded. It is never closed by cleanup — call browser_release_tab to hand it back. Ask the user before taking over a tab they are actively working in.

Inputs: tab_id (required), session_id.

clickClickA
Destructive

Click an element by element_id (preferred: it uses the accessibility press action, so it works even when the element is scrolled out of view) or at absolute screen coordinates taken from a screenshot or zoom. Pass element_id or x and y, not both. When element_id is clicked by position (web content, or no press action), the point is checked with the app's own hit test first, and the element is scrolled into view if needed; if something else is there the call fails without clicking. A checkbox or radio button says what it is now, and a click that left it unchanged is reported as an error. Use browser_click for pages in the agent's Chrome tabs, right_click for context menus, and drag for press-move-release. The click reaches the target app for real and can trigger any action the user could, so read the target with get_app_state first. The agent's own pointer overlay moves to the target. On macOS the user's mouse pointer never moves; on Windows and Linux a target that ignores background input gets real mouse input, which moves the user's pointer, and the result then says via cursor.

Inputs: x, y, element_id, click_count, +2 more.

clipboard_readRead clipboardA
Read-onlyIdempotent

Read the plain text on the system clipboard, for example after press_key cmd+c (ctrl+c off macOS) copied a selection, or when the user says they copied something for you. Prefer get_app_state, browser_snapshot or screenshot to read what is on screen; use this for text that was deliberately copied. Images and files on the clipboard are reported as no text. Read-only, but the clipboard can hold private data the user copied, such as passwords, so do not repeat it beyond what the task needs.

Inputs: max_chars.

clipboard_writeWrite clipboardA
DestructiveIdempotent

Replace the system clipboard with plain text, typically so a long or multi-line value can be pasted with press_key cmd+v (ctrl+v off macOS) where set_value is rejected and type_text would be slow. This tool does not paste anything itself. Side effect: whatever the user had on the clipboard is overwritten and not restored, so tell the user when you use it. Prefer set_value or type_text when they work.

Inputs: text (required).

dragDragA
Destructive

Press at one point, move, and release at another to drag and drop, move a slider, or select a range. Give each end as an element id or as screen coordinates; the two ends may use different forms. A drop can move or reorder items in the app, so verify the result with get_app_state. On macOS a drag must stay inside one app window and never moves the user's pointer; on Windows and Linux most drags use real mouse input, which moves the user's pointer (the result then says via cursor).

Inputs: to_x, to_y, from_x, from_y, +4 more.

get_app_stateRead accessibility treeA
Read-onlyIdempotent

Read an app's accessibility tree as an indented outline in which interactive elements carry ids like [e12] that click, type_text, set_value, scroll, hover and select_text accept. Use it instead of screenshot whenever you intend to act: it is far cheaper in tokens and gives exact targets. Call it before interacting and again after the UI changes, because ids are per-snapshot. Each id is rechecked before an action: one whose element is gone or has changed fails instead of acting on something else. The outline lists what is on screen: rows of long lists and content scrolled out of view are skipped (scroll, or pass offscreen), and while a dialog, sheet or popover blocks a window only its controls are listed. When an app exposes little to accessibility, its on-screen text is read with OCR and listed with ids too. Read-only; it does not change focus.

Inputs: app (required), ocr, query, max_depth, +2 more.

hoverHoverA
Idempotent

Move the agent pointer over an element or screen position without clicking, to reveal hover menus, toolbars, tooltips or drag handles. Follow with get_app_state or screenshot to see what appeared. Use click to activate. Pass element_id or x and y, not both. On macOS the user's own mouse pointer is not moved; on Windows and Linux it may be, and the result then says via cursor.

Inputs: x, y, element_id, state_query, +1 more.

list_appsList running appsA
Read-onlyIdempotent

List running applications with their bundle id, pid, window count, and which one is frontmost. Call it first to learn the exact app value that get_app_state, screenshot and activate_app accept. One app can have several running instances and only some own windows, so prefer the instance that has windows. Instances that share a name each show their main window title, and a closing note lists their pids: pass the pid to choose one. Read-only: no window or input is touched.

Inputs: no named parameters.

list_displaysList displaysA
Read-onlyIdempotent

List every attached display with its index, resolution and position, for use with screenshot(display: N) and for interpreting screen coordinates on multi-monitor setups. Read-only.

Inputs: no named parameters.

press_keyPress keyA
Destructive

Press one named key, optionally with modifiers held, e.g. key='s' modifiers=['cmd'] to save or key='return' to submit. Use it for shortcuts and navigation keys; use type_text for literal text. Never use it for a Chrome tab the agent works in: it goes to whichever Chrome window is in front, which can be the user's; use browser_press_key, browser_type or browser_fill there. The key goes to the app you are working in (the one you last clicked or focused); on macOS and Windows the user can keep typing elsewhere meanwhile, so call activate_app only when nothing has targeted the app yet. Shortcuts can close windows or delete content, so confirm the target before pressing.

Inputs: key (required), modifiers, state_query, return_state.

right_clickRight-clickA
Destructive

Right-click (secondary click) an element or screen position to open its context menu. Follow with get_app_state to read the menu items, then click one. Use click for normal activation. Pass element_id or x and y, not both. The user's pointer is treated as for click.

Inputs: x, y, element_id, state_query, +1 more.

run_stepsRun stepsA
Destructive

Run a sequence of actions in one call, in order, stopping at the first step that fails. Each step names another tool and its arguments, and runs exactly as if you had called it: the same app and site rules, element id checks and password-field refusals apply. Use it for steps you already know will follow each other, like click a field, type, press return, after reading the app with get_app_state. Element ids from that snapshot stay valid until a step reads the app again. The result lists every step's outcome; when a step fails, the steps after it do not run and the call reports an error. Put return_state on run_steps, not on a step, to get the state after the last step. At most 20 steps; run_steps cannot contain run_steps.

Inputs: steps (required), state_query, return_state.

screenshotScreenshotA
Read-onlyIdempotent

Capture an app's largest window, or a whole display, as an image. The result text states the capture's screen origin and pixels-per-point so an image pixel can be converted into click or hover coordinates. Prefer get_app_state for interaction, which is cheaper and returns clickable element ids; use screenshot to verify an outcome or to see content the accessibility tree cannot describe (canvas, video, custom drawing), and zoom to read small text. Read-only; the captured window is not raised or focused.

Inputs: app, format, display, quality, +1 more.

scrollScrollA

Scroll up, down, left or right by a number of lines, over element_id when given so the right pane scrolls. Use it to bring off-screen content into view before get_app_state or screenshot. It only scrolls; nothing is clicked or selected. On macOS it scrolls the target app in the background without moving the user's pointer. On Windows and Linux, without element_id it scrolls whatever is under the user's pointer, and an element with no background route gets the real pointer moved onto it (the result then says via cursor).

Inputs: x, y, amount, direction, +3 more.

select_textSelect textA
Idempotent

Select a character range inside a text element through the accessibility API, for example to copy part of a value or to replace just that part with type_text. Defaults to selecting from start to the end of the value. Use set_value to replace the whole value instead. Only the selection changes; the text is not modified.

Inputs: element_id (required), start, length, state_query, +1 more.

set_valueSet field valueA
DestructiveIdempotent

Replace a text field's entire contents in one step through the accessibility API, without keystrokes. Prefer it over type_text for long values or when the field already holds text; fall back to click plus type_text if the field rejects it, which the result reports. The previous value is discarded.

Inputs: value (required), element_id (required), state_query, return_state.

type_textType textA
Destructive

Type literal text as keystrokes into the field that currently has focus, optionally focusing element_id first. Use it for short entries and for fields that reject set_value; use set_value to replace a long value in one step, and press_key for shortcuts or keys such as return and tab. Never use it for a Chrome tab the agent works in (it goes to whichever Chrome window is in front, which can be the user's); use browser_type or browser_fill there. Text is inserted at the caret without clearing what is already there. It does not take over the user's keyboard: on macOS and Windows the text goes to the app you are working in, not the window the user is typing in, so do not call activate_app first. Typing into password fields is refused by default (see COMPUTER_USE_ALLOW_SECURE_FIELD_INPUT).

Inputs: text (required), element_id, state_query, return_state.

waitWaitA
Read-onlyIdempotent

Pause before the next action so the UI can catch up: page loads, animations, dialogs opening, apps launching. Follow with get_app_state or screenshot to confirm the new state instead of guessing. Sends no input.

Inputs: seconds.

zoomZoom into regionA
Read-onlyIdempotent

Capture one region of the screen at full resolution, to read small text, dense tables, file names or tiny controls that a normal screenshot blurs. Give the region as two corners in screen coordinates (the same space click uses); the result text explains how to map pixels in the zoomed image back to screen coordinates. Use screenshot for a whole window and get_app_state when the text is exposed by accessibility. Read-only.

Inputs: x0 (required), x1 (required), y0 (required), y1 (required), +1 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.2/5.0

    Scored across 37 tools

    Disambiguation4/5

    The browser_ prefix clearly separates Chrome-tab tools from native-app tools, and descriptions repeatedly cross-reference the correct counterpart. However, paired tools such as browser_click vs click, browser_type vs type_text, and browser_fill vs set_value still require careful reading to avoid misselection.

    Naming Consistency4/5

    Most tools follow a predictable snake_case verb_noun pattern, with the browser_ prefix consistently marking browser operations. Minor deviations such as screenshot, browser_snapshot, browser_page_errors, and clipboard_read/clipboard_write keep it from being perfectly uniform.

    Tool Count3/5

    The server has 37 tools across two large domains, browser automation and native desktop control. Each tool has a defensible role, but the surface is heavy and contains many near-paired operations that increase selection burden.

    Completeness4/5

    Coverage is strong: browser lifecycle, navigation, snapshots, reading, input, uploads, dialogs, credentials, and page errors are present, alongside native app state, screenshots, input, clipboard, and multi-step execution. Minor gaps exist for browser-specific hover, scrolling, and drag interactions, though many tasks can work around them.

    Maintenance

    ActivityActive
    ResponsivenessUsually handled

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      An open-source MCP server for macOS and Windows that provides native desktop control via Accessibility APIs, OCR, and Chrome CDP. It enables AI agents to interact with applications, manage browser sessions, and automate workflows with high-speed native UI actions.
      75 npm
      15
      AGPL 3.0
    • A
      license
      Not graded
      quality
      B
      maintenance
      An MCP server that gives any AI assistant eyes and hands on your desktop — screenshots, clicking, typing, OCR, window management, accessibility-tree queries, workflow recording.
      5
      Apache 2.0
    • A
      license
      Not graded
      quality
      B
      maintenance
      An MCP server that gives AI agents real OS-level control of macOS, enabling them to click real buttons, type real keys, and observe rendered screens just like a human would.
      1
      MIT
    • F
      license
      Not graded
      quality
      D
      maintenance
      macOS MCP server that enables AI agents to directly control the host OS, including mouse, keyboard, windows, files, and accessibility automation for computer-use workflows.
      1
      -