desk-mcp
Enables automating Counter-Strike (Source 2013, CS:GO/CS2) on the Windows desktop: prescribes aiming through relative mouse movement instead of absolute positioning, accepting dx/dy/steps/stepMs parameters so synthetic motion reads as a hand rather than a flick, and documents the raw-input caveats per title (Counter-Strike: Source needs no switch since the engine reads WM_MOUSEMOVE, while CS:GO/CS2 require cl_rawinput 0). Also notes that windowed D3D9 titles such as Counter-Strike: Source render correctly through PrintWindow for screenshots.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@desk-mcpscreenshot the active window and click the Save button in Notepad"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
desk-mcp

MCP server for automating the Windows desktop: screenshots, OCR, mouse and
keyboard, and accessibility trees from UI Automation, MSAA and Chrome DevTools
Protocol. Runs on Node.js and the PowerShell that ships with Windows — no
Python, no uvx, no native modules.

What it does
Eyes
computer_screenshot— the whole screen, an arbitrary region, or a specific window (viaPrintWindow, so it works on occluded and minimized windows); scaling, PNG and JPEG.computer_ocr— text straight from pixels, using the OCR engine built into Windows (Windows.Media.Ocr, ru/en). Words and lines come back with bounding boxes, so you can click on what was read.
Interface tree
computer_read_screen— UI Automation, with automatic fallback to MSAA and automatic growth of the traversal depth.computer_find— find an element by name, role orautomationId.computer_element_at— what is at a given point.computer_browser_tree— the page DOM with ready-to-use CSS selectors (CDP).
Hands
computer_click(with modifiers),computer_move,computer_drag,computer_scroll,computer_mouse_button,computer_type,computer_key,computer_key_down/_up,computer_wait.computer_invoke— presses throughInvokePatternwithout capturing the mouse and without bringing the window forward.computer_set_value— writes throughValuePatternwithout focus.computer_batch— up to 50 tools in a single call, stopping at the first error.
Windows
computer_windows,computer_focus,computer_wait_window,computer_active_window,computer_window_set_frame,computer_close_window,computer_launch.computer_desktop— virtual desktops, on the builds of Windows that have them.
Verification
computer_verify_state— predicates: exists /value_equals/enabled/selected.unknownis reported honestly and is not success.computer_browser_clickreturns evidence that the click landed: an in-page probe or a URL change.computer_selftest— checks the whole channel at once.
42 tools in total. Schema and descriptions: run node server.mjs, or connect any
MCP client.
Related MCP server: Windows-MCP
Install
git clone https://github.com/DigitalDog1/desk-mcp.git
cd desk-mcp
npm installRequires Node.js 20+ and Windows 10 1809 / 11. No PowerShell install needed — it is already there.
Connect
mcp.json:
{
"mcpServers": {
"desk-mcp": {
"command": "node",
"args": ["C:\\path\\to\\desk-mcp\\server.mjs"]
}
}
}How it works
server.mjs MCP server in Node, stdio, CDP client
worker.ps1 long-running PowerShell: user32, UI Automation, MSAA, OCROne worker for the whole life of the server, not a process per call: PowerShell
takes about 400 ms to start, and Add-Type takes longer still to compile the C#.
The exchange is line-based with base64 responses — otherwise Cyrillic breaks on
the console code page.
The three read layers
The order matters, because for Chromium the first two are nearly useless: UIA
returns a tree wrapped in nameless PANEL elements, and only when Chromium was
started with --force-renderer-accessibility. An empty tree is not an error, it
is the signal to fall through to the next layer.
Layer | What it gives |
UIA | native windows: exact |
MSAA |
|
CDP | the real page DOM with ready-to-use CSS selectors |
How to work with it

The most expensive mistake is not "the tool didn't work", it is "the tool worked,
but not on the right thing". So the loop is: observe → act by meaning → verify.
computer_invoke may come back with via: "pixel", which means there was no
pattern and the click went to the center of the bounds — treat the result with
more suspicion. A tool that found nothing refuses honestly: that is a finding,
not a reason to click blind coordinates.
Games
Aiming in a shooter is the one case where a plain click-and-move is useless, and the reason is not a defect in the tool.
Most shooters read mouse movement through raw input: the engine consumes only hardware mouse packets and ignores synthetic input completely, so buttons fire while the camera does not turn. The fix is to aim with relative movement instead of absolute positioning, and to disable raw input where the game exposes that switch:
Game | Switch | Verified |
Counter-Strike: Source (Source 2013) | none needed — | yes, live |
CS:GO / CS2 |
| not verified |
Steam Input ("Force Steam Input") | turn off per game — it injects raw input of its own | not verified |
Aim with relative movement:
{ "tool": "computer_mouse_move", "args": { "dx": 220, "dy": -40, "steps": 20, "stepMs": 8 } }steps matters: games apply sensitivity to every mouse event, so one 220 px jump
reads as a flick and twenty small steps read as a hand. The tool also measures
how far the cursor actually moved and tops up the remainder, because Windows
coalesces injected motion.
Drawing apps need the opposite trick — they ignore a click with no motion at all between button-down and button-up:
{ "tool": "computer_click", "args": { "x": 800, "y": 400, "nudge": 1 } }And if your coordinates came from a downscaled screenshot, pass the same scale rather than doing the arithmetic yourself:
{ "tool": "computer_screenshot", "args": { "region": "0,0,2560,1440", "scale": 0.5 } }
{ "tool": "computer_click", "args": { "x": 640, "y": 360, "scale": 0.5 } }Safety
computer_close_window and computer_launch are destructive and irreversible.
Both require an explicit confirm: true and refuse without it. Text on screen
is untrusted data, not instructions.
When a window hangs
UI Automation talks to other applications through COM, and this is the one place where the server can genuinely hang: an application with a modal dialog, a frozen UI thread or old WPF holds the RPC open and the call never returns.
The worker is single-threaded, so a full isolation of UIA in an STA thread with
a hard cancellation is not available here: a PowerShell ScriptBlock is
bound to its runspace and refuses to execute on a foreign thread. What is
available, and what this server does:
A hard budget of 8 s per UIA call instead of 15. For scale: a full UIA traversal of every window on this machine measures 116 ms, so the budget is still generous by two orders of magnitude.
A circuit breaker, per window. After a timeout the worker is restarted and the key
tool|windowis blocked for 90 s. While blocked, calls to that window fail immediately with an explanation instead of stalling again. Other windows keep working — one application's bug is not everyone's outage.Three-layer degradation on reads. UIA → MSAA → OCR.
computer_read_screennever returns an empty window list quietly: if both accessibility trees are empty it falls through to OCR and says so inbackendanddegraded.
DESK_UI_TIMEOUT_MS and DESK_UI_COOLDOWN_MS override the two budgets.
What is still open: the UIA call itself is not cancellable, so the 8 s is real
time lost on the first call against a hung window, and the worker restart also
drops any in-flight non-UIA call. Solving that properly means moving the tree
walk out of PowerShell and into C#. The OCR fallback captures the window through
PrintWindow, which is also a synchronous call into the target — same risk.
computer_batch is executed step by step on the server side, not as one
recursive call inside the worker. That is what makes it subject to the same
budgets and the same breaker: a batch step is just another tool call.
Limitations
Exclusive fullscreen (games, video):
CopyFromScreenreturns black. This needs DXGI Desktop Duplication. Note that windowed D3D9 titles do come throughPrintWindow— verified on Counter-Strike: Source.UWP windows expose neither a UIA nor an MSAA tree; reads fall through to OCR, which means no element names.
Virtual desktops depend on the Windows build: on 10 19035
VirtualDesktopManager.dllis absent and the tool returns an error.
Tests
npm testExpected tail: ИТОГ: 49 ок, 0 провалов. The suite does not move the
cursor — read-only: screenshots, windows, trees, OCR, clipboard, CDP reads.
computer_batch is covered too: MCP tool names inside steps, name
normalization, and stopOnError on both paths. The UIA circuit breaker is
covered by booting a second server with a deliberately absurd 1 ms budget and
checking that the first call hangs, the second is blocked, and both say why.
Windows gotchas
Everything below was reproduced in practice, not taken from documentation.
The worker calls
SetProcessDpiAwarenessContext(PER_MONITOR_AWARE_V2)before any UI call. Without it, UIA and OCR report logical units whileSendInputacts in physical pixels, and clicks land tens of pixels off target on a scaled display.A
.ps1file must be UTF-8 with BOM: without it, PowerShell 5.1 reads the file as ANSI and silently breaks parsing of Cyrillic.In
KEYBDINPUTthe fields areushort, notuint. Declareuintand the struct becomes 32 bytes instead of 24,dwFlagsends up in the wrong place, andSendInputneither fails, nor complains, nor returns an error. It silently does nothing.PowerShell has no
[ushort]type; it is[uint16].A static C# method cannot be named
MoveorWheel: the call fails with "does not contain a method named…". Worked around withMoveTo/ScrollWheel.SendKeyscannot type Cyrillic at all. OnlySendInput+KEYEVENTF_UNICODEworks.ConvertTo-Json -Depthfor a UI tree must be larger than 12: a node is about two levels of nesting, so a shallow depth silently substitutes a string holding a .NET type name.UIA can report infinite bounds — check
IsInfinityandNaNbefore casting toInt32.COM objects (
AutomationElement) must not be cached: they go stale as the tree is repainted. Only identifiers are cached, with a fresh lookup before every action.Add-Typecompiles as C# 5, so no$"..."string interpolation.Interpolating a name before a colon turns
"$Owner:$Token"into$Owner:, which PowerShell reads as a scoped variable. Write${Owner}`.Some drawing apps need a mouse move between button-down and button-up. MS Paint ignores a clean click:
WM_LBUTTONDOWN→WM_LBUTTONUPwith noWM_MOUSEMOVEin between draws nothing, and the flood-fill tool does not fire at all. Drag by one pixel instead of clicking —computer_dragwithtoX = fromX + 1is enough. If a click "does nothing", suspect this before suspecting the click itself.Paint's Color 1 defaults to white, and the fill tool fills with Color 1. Filling a white canvas white looks exactly like a broken click. Pick a color from the palette first.
Change history: CHANGELOG.md. Attribution of ideas and dependency licenses: THIRD_PARTY_NOTICES.md. Read CONTRIBUTING.md before editing — it lists traps that the code alone does not reveal.
License
Apache License 2.0. Windows public APIs are used per Microsoft's documentation.
This server cannot be deployed
Maintenance
Related MCP Connectors
Eyes and hands on real Windows PCs — observe, click, type via Glasswarp API.
Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.
Use your Mac, Windows or Linux computer from ChatGPT, Claude or Codex: files, commands, documents.
Hosted browser for AI agents: screenshots, post-JS DOM, console, WCAG. No install, no API key.
Related MCP Servers
- AlicenseAqualityBmaintenanceAllows AI clients to see and control Windows 10/11 desktops via MCP, with screenshots, UI Automation, Chrome CDP, keyboard/mouse, and terminal using semantic element targeting.30344 npmMIT
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to interact with the Windows operating system, performing tasks such as file navigation, application control, UI interaction, and QA testing.MIT
- FlicenseNot gradedqualityBmaintenanceEnables AI assistants to control Windows GUI by listing and focusing windows, capturing element snapshots via UIA/OCR/CDP, performing clicks/inputs/scrolls, verifying changes, waiting for screen updates, taking screenshots, and obtaining visual descriptions.2-
- AlicenseNot gradedqualityAmaintenanceEnables AI assistants to autonomously control Windows 11 and 10 desktops via sub-10ms screen capture, native UI Automation element inspection, and zero-lag keyboard and mouse input. Combines a visual plane with a semantic plane and stall detection so agents can operate real applications reliably without vision-only guessing.34 npm178 PyPI4MIT