da-mcp
Cross-platform desktop automation MCP server that lets AI agents capture screenshots, run OCR with UI-element classification, control mouse/keyboard, and launch programs on Linux, macOS, and Windows.
README
da-mcp — Crossplatform Desktop Automation MCP Server
A Model Context Protocol (MCP) server that lets AI agents (OpenCode, Claude Desktop, etc.) interact with a local desktop environment: screenshots, OCR with UI-element classification, mouse/keyboard control, and program launch — on Linux, macOS, and Windows.
Features
12 tools registered under the da_* namespace:
Capture
da_screenshot— Capture full screen or a specific display as PNG.da_ocr— Run OCR (Tesseract) on a screenshot and return structured text + UI element classification.da_list_displays— List connected displays with id, bounds, scale factor.
Input
da_get_mouse_position— Read current cursor position (X11/Linux usesxdotool getmouselocation --shell, Wayland usesydotool, macOS/Windows usesrobotjs).da_move_mouse— Move the cursor to (x, y).da_click— Click at (x, y) with optional button (left/right/middle/back/forward) and count.da_double_click— Convenience wrapper for double-click.da_drag— Drag from (x1, y1) to (x2, y2).da_scroll— Scroll wheel at (x, y) by (dx, dy).da_type— Type a string at the current focus.da_key— Press a single key or chord (e.g.Ctrl+C).
Launch
da_launch— Launch a program by name or path; returns a spawn handle with PID + POSIX signal exit codes (SIGINT=130, SIGTERM=143, SIGHUP=129, SIGKILL=137, SIGQUIT=131, SIGABRT=134).
UI element classification (OCR post-processing)
The da_ocr classifier tags each detected text region with one of:
| Category | Examples |
|---|---|
button |
"OK", "Cancel", "Apply" |
input |
Text fields, search boxes |
label |
Static descriptive text |
checkbox |
"☑ Enable", "☐ Dark mode" |
radio |
"◉ Local", "○ Network" |
menu |
Top-level menu headers ("File", "Edit") |
menu-item |
Dropdown entries ("New", "Open…") |
icon |
Toolbar / sidebar icons |
Architecture
- Language: TypeScript 7.0 (strict, ESM, Node 22+);
exactOptionalPropertyTypes,noUncheckedIndexedAccess,noFallthroughCasesInSwitchall on. Exact version pins (no^/~). - MCP SDK: v2 (
@modelcontextprotocol/server@2.0.0) overStdioServerTransport(production) andInMemoryTransport(tests). - Module layout (250 LOC ceiling per file):
- Screenshot —
src/screenshot/{png,backends,index,types}.ts. PNG validation/encoding isolated inpng.ts; backend dispatch (node-screenshots → screenshot-desktop → Windows CLI) inbackends.ts. - OCR —
src/ocr/{cli,index,mock,parse,wasm,types,classify,classify-rules}.ts. CLI backend (runCli), WASM fallback (runWasm), parser, mock; orchestrator inindex.tsrethrows asOCR_FAILEDwhen both backends fail. - Input —
src/input/{routing,mouse,keyboard,scroll,drag,types,index}.ts. Shared routing helpers (runCli,resolveRouting,requireTool,loadRobotjs,isMockMode,validateCoords,Routing) inrouting.ts; per-input-type operations in dedicated files. - Launch —
src/launch/{launch,types}.ts.open(1)+child_process.spawn(shell:false);SIGNAL_EXIT_CODESmap for POSIX signal mapping. - Platform —
src/platform/{detect,types}.ts.detectPlatform()returns{ os, display, tools, home };assertPlatformSupported()throwsPLATFORM_INIT_FAILEDon unsupported combos. - Server —
src/server.ts. Registers 12 tools, wraps handler results intoCallToolResultwithstructuredContent(Buffers stripped tonumber[]for JSON-safety), installs SIGINT/SIGTERM shutdown.
- Screenshot —
Backend dispatch
| Capability | Primary | Fallback 1 | Fallback 2 |
|---|---|---|---|
| Screenshot (Linux X11) | node-screenshots (XCap) |
screenshot-desktop |
— |
| Screenshot (Linux Wayland) | node-screenshots (XCap portal) |
screenshot-desktop |
— |
| Screenshot (macOS) | node-screenshots (CG) |
screencapture |
— |
| Screenshot (Windows) | node-screenshots (GDI) |
PowerShell BitBlt (windowsCliBackend, ships in backends.ts) |
— |
| Input (Linux X11) | xdotool CLI |
— | — |
| Input (Linux Wayland) | ydotool CLI |
wtype (keyboard only) |
— |
| Input (macOS / Windows) | robotjs (native) |
— | — |
| OCR (any OS) | tesseract CLI |
tesseract.js@7 WASM |
— |
Every spawnSync/spawn call uses shell:false. Permission-gated errors (e.g. macOS ScreenCaptureKit, Windows access denied) are detected via a tightened pattern (screen…permission, screencapturekit, access is denied) and re-thrown as DaMcpError('PERMISSION_DENIED').
Install
# System dependencies (apt/dnf/brew; see scripts/install-system-deps.sh)
sudo ./scripts/install-system-deps.sh
# npm deps
npm install
# Build
npm run build
# Verify type-check (strict mode)
npm run typecheck
# Run all tests (mock mode — skips real native calls)
DA_MCP_TEST_MODE=mock npm test
Run
stdio (default)
The server speaks MCP over stdio. Configure your MCP client to launch node /projects/da-mcp/dist/server.js (or npx tsx src/server.ts for dev).
HTTP (opt-in, token-protected)
Set DA_MCP_TRANSPORT=http to expose the server on http://127.0.0.1:3000/<token>. A 256-bit random token is generated on first start and persisted at:
| OS | Token path |
|---|---|
| Linux | $XDG_CONFIG_HOME/da-mcp/token or ~/.config/da-mcp/token |
| macOS | ~/Library/Application Support/da-mcp/token |
| Windows | %APPDATA%\da-mcp\token |
The token file is created with mode 0o600 (owner-only). Rotate it any time:
node /projects/da-mcp/dist/server.js token regenerate
# → http://127.0.0.1:3000/<43-char-base64url-token>
Override defaults with env vars:
DA_MCP_HTTP_HOST— bind address (default127.0.0.1); supports IPv4, IPv6 ([::1]), and hostnameDA_MCP_PORT— port (default3000)DA_MCP_TOKEN_PATH— override token storage path
The URL is unauthenticated token (bearer-style): anyone with the token can call tools. Bind only to 127.0.0.1 (default) — do not expose this to a network without adding an upstream auth proxy.
OpenCode / Claude Desktop example config
{
"mcpServers": {
"da-mcp": {
"command": "node",
"args": ["/projects/da-mcp/dist/server.js"],
"env": {
"DISPLAY": ":0",
"DA_MCP_LOG": "info"
}
}
}
}
Cross-platform notes
| OS | Screenshot | Input | Notes |
|---|---|---|---|
| Linux X11 | node-screenshots (X11 native) |
xdotool |
Requires libxtst-dev libpng-dev for robotjs build |
| Linux Wayland | node-screenshots (XCap portal) |
ydotool (daemon) |
XWayland fallback if available |
| macOS | node-screenshots (CG) |
robotjs (CGEvent) |
First call needs Screen Recording permission (TCC) |
| Windows | node-screenshots (GDI) |
robotjs (SendInput) |
VS Build Tools required; PowerShell BitBlt fallback if GDI fails |
Development
# Strict type-check (no emit)
npx tsc --noEmit
# All tests in mock mode (CI default)
DA_MCP_TEST_MODE=mock npx vitest run
# Single test file
npx vitest run test/unit/screenshot.test.ts
# Watch mode
npx vitest
Test inventory
- 18 test files: 15 unit (
test/unit/) + 3 e2e (test/e2e/) - 216 tests passing / 17 skipped in mock mode (e2e require real X11/tesseract)
- Test runtime:
process.env['DA_MCP_TEST_MODE'] === 'mock'short-circuits native calls;_mock.tsmodules inject deterministic native modules
Conventions
- 250 LOC ceiling per file (measured as non-blank, non-comment lines:
awk '!/^[[:space:]]*$/ && !/^[[:space:]]*(\/\/|#|--)/' <file> | wc -l) - ESM imports use
.jssuffix even for.tssource - All
spawn*calls withshell: false - All native errors wrapped in
DaMcpErrorwith typedcodefromErrorCodeunion - Public surface re-exported from
src/screenshot/index.tsandsrc/input/index.ts— consumers import from there, not from per-operation files - Forbidden:
as any,@ts-ignore,@ts-expect-error,console.log,shell: true, auto-commits
Environment variables
DISPLAY— X11 display (Linux only)WAYLAND_DISPLAY— Wayland display socketDA_MCP_LOG— log level (trace|debug|info|warn|error), defaultinfoDA_MCP_TESSERACT_BIN— path totesseractbinary, defaulttesseractDA_MCP_OCR_BACKEND—cli(default) orwasmDA_MCP_TEST_MODE—mockskips real native calls in tests; e2e tests skip when setDA_MCP_SCREENSHOT_BACKEND— force a screenshot backend (node-screenshots|screenshot-desktop|windows-cli); default auto-detectDA_MCP_TRANSPORT—stdio(default) orhttp;httpenables the opt-in HTTP transportDA_MCP_PORT— HTTP port whenDA_MCP_TRANSPORT=http(default3000)DA_MCP_HTTP_HOST— HTTP bind address (default127.0.0.1); supports IPv4, IPv6, hostnameDA_MCP_TOKEN_PATH— override the auth token storage path
License
MIT
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
Neon Database
MCP server for interacting with Neon Management API and databases
E2B
Using MCP to run code via e2b.
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.