Skip to main content
Glama
Regigicas

win-computer-use

by Regigicas

win-computer-use

An MCP server that gives Claude Code computer use on Windows — screenshots, mouse, keyboard, window and display enumeration — which the CLI officially ships on macOS only:

createCliExecutor called on win32. Computer control is macOS-only.

It does not reimplement anything at a low level. It reuses the native binding that the Claude desktop app installs (@ant/claude-native), which loads fine in plain Node, and adds screen capture on top through GDI (the desktop app captures with Electron's desktopCapturer, which does not exist outside Electron).

WARNING

By default this gives a model unrestricted control of your desktop. Keystrokes and clicks go to whatever window currently has focus, and a screenshot captures the entire screen — password managers, private messages, anything else you have open.

An allowlist can restrict input to named applications, but it is opt-in and it does not restrict what the screenshot sees. Of the four safeguards the Claude desktop app has, this implements one: there is no screenshot masking, no on-screen indicator while computer use is active, and no panic key. Run it only on a machine where that is acceptable to you — and since the install below registers it for every session on that machine, decide deliberately whether to pair it with an allowlist.

NOTE

Unofficial, unsupported, and not affiliated with Anthropic. It depends on internals of the desktop app: if the binding or the MSIX package layout changes, this breaks.

Requirements

  • Windows 10 or 11. Windows-only by construction; the server exits with an error elsewhere.

  • The Claude desktop app, installed (the MSIX package from the Microsoft Store or the official installer). It is where the native binding comes from — the app does not have to be running, it has to be installed.

  • Node.js 18+.

  • Windows PowerShell 5.1 (the built-in powershell.exe), used for screen capture.

No npm dependencies.

Related MCP server: windows-computer-use-mcp

Install

Clone it anywhere, then register it with the Claude Code CLI:

git clone https://github.com/Regigicas/win-computer-use.git
cd win-computer-use
claude mcp add win-computer-use --scope user -- node "$PWD\server.js"

Restart the session for the tools (mcp__win-computer-use__*) to appear. Check it with claude mcp list; remove it with claude mcp remove win-computer-use.

--scope user registers it once for every session on the machine, which is usually what you want from a tool that drives the desktop rather than a project. It also means any session can reach your desktop, so it is the case where an allowlist earns its keep. Use --scope local instead to keep it to the project you are in.

For any other MCP client, the equivalent config entry is:

{
  "mcpServers": {
    "win-computer-use": {
      "command": "node",
      "args": ["C:\\path\\to\\win-computer-use\\server.js"]
    }
  }
}

Tools

Screen

screenshot, zoom, list_displays

Windows

list_windows, focus_window, frontmost_app, list_running_apps

Mouse

move_mouse, click, drag, scroll, cursor_position

Keyboard

type_text, key

Composite

batch

Policy

allowlist_status

Coordinates are screen pixels (display origin + offset), never pixels of the returned image; every capture states the scale it used and how to convert. batch chains several actions and ends with a screenshot, so a click-type-check cycle costs one round-trip instead of three.

Limits and defaults

screenshot scale

Auto-fits the width to ~1400 px; override with scale (0.05–1)

zoom scale

1 (native pixels); values above 1 are not supported

JPEG quality

80

type_text

2000 characters per call, ~8 ms per character

batch

30 actions per call; wait takes 0–5000 ms

Capture timeout

15 s

Restricting which applications it can touch

With no allowlist configured — the default — input reaches whatever window has focus. Configure one and the tools that inject input (click, drag, scroll, type_text, key) refuse to act unless the foreground window belongs to an allowed application, and focus_window refuses to bring a non-allowed one forward.

Set it with an environment variable, which wins over everything else:

claude mcp add win-computer-use --scope user `
  -e WIN_COMPUTER_USE_ALLOWLIST="notepad.exe,*brave*" -- node "$PWD\server.js"

or in %LOCALAPPDATA%\win-computer-use\allowlist.json (point WIN_COMPUTER_USE_ALLOWLIST_FILE elsewhere if you prefer):

{ "allow": ["notepad.exe", "Microsoft.WindowsNotepad_8wekyb3d8bbwe!App", "*brave*"] }

The file is re-read whenever it changes on disk, so you can widen or narrow the policy in the middle of a session without restarting the server or the client.

Value

Meaning

unset, and no file

every application allowed — the behaviour before the allowlist existed

* or all

every application allowed, said explicitly

none, or "allow": []

nothing allowed: enumeration and screenshots only

a list of patterns

only those applications

Patterns are case-insensitive and take * and ?. Each one is matched against the full bundleId (c:\windows\system32\notepad.exe, or an MSIX identifier such as Microsoft.WindowsNotepad_8wekyb3d8bbwe!App), its file name with and without .exe, the package family and package name of an MSIX identifier, and the friendly display name — so notepad.exe, *notepad* and the full identifier all work. list_windows gains an allowed flag while a policy is in force, and allowlist_status reports the policy and whether the app in front right now is on it.

No tool can change the allowlist. A model able to grant itself applications would not be constrained by one; only the environment variable and the file decide. If the file cannot be parsed the server fails closed and refuses input until it is fixed.

Each action is checked on its own — including every action inside a batch — against:

  • the application owning the foreground window;

  • the application under the target coordinates of a click, drag or scroll, so a background window or the desktop behind everything is not a valid target;

  • for type_text, that focus has not moved elsewhere mid-string; if it does, typing stops and reports how many characters were delivered.

What it does not cover

Captures. screenshot and zoom return the whole display whether or not an allowlist is set. The desktop app masks non-allowlisted windows out of the image (cuMaskedWindowRects is right there in the binding); that is not implemented here. An allowlist limits what can be done, not what can be seen.

How the desktop app differs

Worth knowing if you expect identical behaviour. With an allowlist active the desktop app hides every non-allowed application before acting (cuHideApps, then up to five more rounds against whatever is still frontmost) and masks them out of the screenshot, and it grants each application a tier: read (visible in screenshots, no input), click (plain left click only) or full. This server does neither — it refuses the action instead of rearranging your desktop, and every allowed application is at the equivalent of full.

Files

File

What it is

server.js

The MCP server (JSON-RPC over stdio, no dependencies).

capture.ps1

Persistent capture helper (PowerShell 5.1 + System.Drawing).

smoke.js

Test client: node smoke.js '[{"name":"screenshot"}]'.

How the binding is resolved

C:\Program Files\WindowsApps cannot be listed from an ordinary process, and the .node cannot be loaded in place (Acceso denegado / access denied) — but it can be read and copied. So the server:

  1. Resolves the registered package with Get-AppxPackage -Name Claude.

  2. Copies claude-native-binding.node into %LOCALAPPDATA%\win-computer-use\ and loads that copy, re-copying whenever the original's size or mtime changes.

Set CLAUDE_NATIVE_BINDING=<path> to skip all of that and load a specific file.

Nothing from the desktop app is redistributed by this repository. The binding is copied at runtime, on your machine, out of your own installation.

What it writes to disk

  • %LOCALAPPDATA%\win-computer-use\ — the copy of the native binding, plus a source.json stamp recording where it came from. Persistent; safe to delete, it is re-copied on the next run. The optional allowlist.json lives here too, and is only ever read.

  • %TEMP%\win-computer-use\ — each screenshot is written here as a JPEG before being read back and sent, then deleted immediately. Deletion is best-effort: if the server is killed mid-capture, an image of your screen can be left behind in your temp directory.

Nothing is sent anywhere by this server itself; it only answers the MCP client that spawned it.

Things learned the hard way

  • typeText and typeTextPaced break on long strings when driven from outside the desktop app: the first characters land and the rest come out as the last character repeated, or nothing at all. That is why type_text sends one character per call. It costs about a second per 100 characters, but it is exact. Do not "optimise" it back.

  • Shortcuts mean whatever the target app makes them mean, in its own language. On a Spanish Windows, ctrl+a in Notepad is Abrir (open) and select-all is ctrl+e. The binding maps keys correctly; the app on the other end is what differs.

  • Key names are lowercase: ctrl, alt, shift, win, return, escape, tab, space, backspace, delete, arrows, home, end, pageup, pagedown, f1–f12, and single characters. The internal enum names (Num1, …) throw. Chords go through keys([...]), which is the same API the desktop app uses for ctrl+v.

  • GDI capture looks correct in SDR; on an HDR display it can come out washed out. That is a CopyFromScreen limitation, not the state of your screen.

  • The GDI capture does not draw the mouse cursor.

Troubleshooting

the Claude desktop app does not appear to be installed — Get-AppxPackage -Name Claude returned nothing. Install the desktop app, or point CLAUDE_NATIVE_BINDING at a copy of the binding.

binding not found at ... — the package layout changed. Look for claude-native-binding.node under the install location and set CLAUDE_NATIVE_BINDING.

Behaviour that does not match the installed version — Get-AppxPackage returns the registered package, which is not necessarily the newest one staged on disk. If the app has updated but the binding behaves like an older build, that mismatch is the first place to look.

capture helper could not start — powershell.exe could not be spawned: check that Windows PowerShell 5.1 is present and not blocked by policy.

blocked by the allowlist: ... — an allowlist is configured and the app in front (or under the click) is not on it. allowlist_status prints the policy, where it came from, and what is in front; the allowlist section covers how to change it.

Nothing happens in the target app — elevated windows (running as administrator) will not accept injected input from a non-elevated process, and the UAC prompt runs on an isolated desktop that cannot be automated at all. Fullscreen games generally reject the injection too.

Limitations and what is untested

See docs/limitations.md for what has actually been exercised and what has only been written.

License

MIT — see LICENSE.

Related MCP Connectors

Related MCP Servers