da-mcp
The da-mcp server is a cross-platform desktop automation MCP server that enables AI agents to interact with the local desktop environment through tools for screen capture, OCR with UI element classification, mouse and keyboard control, and program launching. It supports Linux (X11/Wayland), macOS, and Windows with automatic backend fallbacks.
Capture and Recognition
Capture full-screen or display-specific screenshots as PNG images.
List all connected displays with their IDs, bounds, scale factors, rotation, and primary status.
Perform OCR on a display to extract text and classify UI elements into categories like buttons, input fields, labels, checkboxes, radio buttons, menus, menu items, and icons, aiding targeted automation.
Mouse Control
Get the current cursor position in screen coordinates.
Move the cursor to absolute coordinates, optionally with a duration for smooth movement.
Click, double-click, and drag with configurable mouse buttons (left, right, middle, back, forward) at any location.
Scroll the mouse wheel horizontally and vertically by pixel deltas.
Keyboard Control
Type text at the current keyboard focus with optional per-character delays.
Press single keys or key chords (e.g., Ctrl+C, Alt+Tab) with support for modifiers (ctrl, alt, shift, meta/super) and hold duration.
Program Launch
Launch applications by name or absolute path, with optional working directory, environment variables, detached mode, and execution timeout.
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., "@da-mcp Take a screenshot and click the OK button at those coordinates"
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.
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.
For AI agents and automated installers: Do NOT chain
apt install … && npm install && npm run buildby hand. Use the bundled installer scripts — they handle the platform-specific prerequisites (tesseract, xdotool, Node 22+) and surface native-binding / TCC failures as actionable errors instead of silent broken builds. See Install (automated / AI agents) below.
Features
20 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 (Linux X11 usesxdotool getmouselocation --shell, Wayland usesydotool, Windows uses PowerShell +user32!GetCursorPos). macOS is stubbed in v1.0.0 — surfaces a "not implemented" error (tracked by #19).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_click_text— OCR-then-click: find a UI element by visible text (exact or fuzzy) and click its center. ReturnsNOT_FOUNDif no match.da_find_text— Same OCR+match pipeline asda_click_textbut stops before the click — returns the bbox/center/confidence so the agent can decide what action to take (click vs. drag vs. right-click).da_double_click— Convenience wrapper for double-click.da_drag— Drag from (x1, y1) to (x2, y2).da_draw_path— Trace a multi-point mouse path with optionalModifier[]held throughout (try/finally guarantees modifier cleanup). Used for freeform shapes (circles, signatures) and for constrained drawing in Paint (modifiers:["shift"]).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).
Stability / verification
da_wait_for_window— Pollda_window_listuntil a window with a matching title appears (substring/exact/regex). Use afterda_launchto wait for the app to paint before clicking inside.da_wait_for_text— Poll the OCR text-match pipeline untiltextappears on screen. Use after any state-changing action to confirm the new state is visible before continuing.da_verify_pixels— Poll the screen until a pixel-level predicate holds:{kind:"color", rgb, minCount}(count matching pixels) or{kind:"diff", baseline, threshold}(fraction differing from a baseline PNG). E.g. wait until 200+ red pixels appear on the canvas region after drawing a circle in Paint.
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).
Window
da_window_list— Enumerate all visible top-level windows (hwnd/pid/title/bounds/visibility). Cross-platform:wmctrl(Linux X11 + Wayland via XWayland),osascript+ System Events (macOS), PowerShell +user32!EnumWindows(Windows).da_window_focus— Bring a window to the foreground byhwnd,pid, or title match (exact/regex/substring, case-insensitive). Title matching uses pure-JS resolver; multi-window Paint-style flows return aNOT_FOUNDerror when nothing matches.
Skills (for OpenCode / Claude Desktop agents)
This repo ships a generic desktop-orchestration skill that any agent can install to drive the 16 da_* tools through a 6-step loop: Orient → Observe → Locate → Act → Verify → Iterate. It is application-agnostic — Paint, browsers, IDEs, dialogs, file managers, native apps.
Source-of-truth location: docs/skills/da-ui-orchestrator.md. The file is deliberately kept out of .opencode/skills/ so that this repo doesn't auto-load as an OpenCode skill on its own — it stays a portable artefact that you copy into your client.
Install the skill on your machine:
# OpenCode:
mkdir -p ~/.config/opencode/skills/da-ui-orchestrator
cp docs/skills/da-ui-orchestrator.md ~/.config/opencode/skills/da-ui-orchestrator/SKILL.md
# Claude Code:
mkdir -p ~/.agents/skills/da-ui-orchestrator
cp docs/skills/da-ui-orchestrator.md ~/.agents/skills/da-ui-orchestrator/SKILL.mdRestart your MCP client — da-ui-orchestrator will appear in available_skills. After git pull on this repo, re-copy the file to refresh.
UI element classification (OCR post-processing)
The da_ocr classifier tags each detected text region with one of:
Category | Examples |
| "OK", "Cancel", "Apply" |
| Text fields, search boxes |
| Static descriptive text |
| "☑ Enable", "☐ Dark mode" |
| "◉ Local", "○ Network" |
| Top-level menu headers ("File", "Edit") |
| Dropdown entries ("New", "Open…") |
| Toolbar / sidebar icons |
Related MCP server: desktop-touch-mcp
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 (screenshot-desktop→ OS CLI shell-out:scrot/grim/screencapture/PowerShell BitBlt) inbackends.ts. No native NAPI binary — see #12.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}.tsplus per-OS backendsmouse-{macos,windows}.ts,keyboard-{macos,windows}.ts,scroll-{macos,windows}.ts,clipboard.ts. Shared routing helpers (runCli,resolveRouting,requireTool,isMockMode,validateCoords,Routing) inrouting.ts. Linux paths shell out toxdotool/ydotool/wtype. Windows path uses PowerShell +user32(keybd_event,mouse_event,SetCursorPos,GetCursorPos); Unicode text goes via clipboard + Ctrl+V. macOS is a stub in v1.0.0 (tracked by #19) — surfaces a "not implemented in #13" error. No native NAPI binary — see #13.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 20 tools, wraps handler results intoCallToolResultwithstructuredContent(Buffers stripped tonumber[]for JSON-safety), installs SIGINT/SIGTERM shutdown.Server instructions —
src/server-instructions.ts. ExportsSERVER_INSTRUCTIONS, a string surfaced to the AI agent via the MCPinstructionsfield (MCP spec,ServerOptions.instructions). Tells the agent it IS the orchestrator — call the 14da_*tools directly through the MCP client, do NOT write an orchestrator script that imports/spawns the server. Edit this string to update the agent-facing announcement.Window —
src/window/{types,list,list-linux,list-macos,list-windows,focus,resolve,index}.ts. Per-OS list backends (wmctrl / osascript / PowerShell+EnumWindows) keep each file under the 250 LOC ceiling; pure-JSmatchOneresolver inresolve.tsfor title matching.
Backend dispatch
Capability | Primary | Fallback 1 | Fallback 2 |
Screenshot (Linux X11) |
|
| — |
Screenshot (Linux Wayland) |
|
| — |
Screenshot (macOS) |
|
| — |
Screenshot (Windows) |
| PowerShell BitBlt ( | — |
Input (Linux X11) |
| — | — |
Input (Linux Wayland) |
|
| — |
Input (Windows) | PowerShell + | — | — |
Input (macOS) | Stub in v1.0.0 — surfaces "not implemented in #13" error (tracked by #19) | — | — |
OCR (any OS) |
|
| — |
Window list + focus (Linux) |
| — | — |
Window list + focus (macOS) |
| — | — |
Window list + focus (Windows) | PowerShell + | — | — |
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 (automated / AI agents)
If you are an AI agent setting this up on behalf of a user, or running scripted/CI installation, use the bundled installer scripts. Do not improvise a manual npm install chain — manual install bypasses the prompts that catch missing system tools and PATH issues, and turns platform-specific gotchas into silent broken builds.
OS | Command |
Linux |
|
macOS |
|
Windows |
|
What each script does:
Verifies / installs system prerequisites — tesseract, xdotool/ydotool (Linux), Node.js 22+ (Xcode CLT only needed if Homebrew is missing it — handled via
brew installif so)Runs
npm ci— locked, reproducible install. Avoidnpm install(which resolves ranges and is slower)Builds TypeScript with
npm run buildRuns
DA_MCP_TEST_MODE=mock npm testso the build is verified before you declare successPrints the MCP client config snippet to drop into Claude Desktop / OpenCode / etc.
If a script fails, read its output — the next step is printed at the end of each failure path. Do not retry by hand without first understanding what the script detected.
For manual / sandboxed installs where you cannot run the scripts, see Install below.
Install (Windows, single-binary)
For Windows users, a self-contained single-binary release is available — no Node.js, no npm install, no build step. The binary embeds Node 22 + the bundled JavaScript via Node SEA (scripts/build-sea.sh); the recommended system dependency is tesseract on $PATH (for fast OCR — see OCR backend fallback below).
# Download latest release asset from GitHub
curl.exe -L -o da-mcp.exe https://github.com/cioinside/da-mcp/releases/latest/download/da-mcp-win32-x64.exe
# First run — prints CLI help and exits 0
.\da-mcp.exe help
# Run stdio MCP server (default — point your MCP client at this binary)
.\da-mcp.exe
# Optional: install Tesseract OCR CLI for fast OCR (auto-elevates via UAC)
.\da-mcp.exe install-tesseractCaveats:
Unsigned binary —
postjectstrips Authenticode when injecting the SEA blob, so Windows SmartScreen will warn on first launch. Click "More info" → "Run anyway".No native NAPI deps —
screenshot-desktop, MCP SDK,zod,tesseract.jsare all inlined in the binary. Onlytesseractis recommended (for fast OCR; not required — see OCR backend fallback).Linux + macOS binaries are not part of the v1.0.0 release — see
BUILD.mdfor the local cross-platform build path (Windows SEA build runs onwindows-latestCI only).
OCR backend fallback
da_ocr tries backends in order; the first one that succeeds is used:
Order | Backend | Speed | Requires | Used when |
1 | Tesseract CLI ( | ~0.5–2 s / screenshot |
| Recommended path |
2 | tesseract.js WASM (in-binary, with pre-bundled | ~5–15 s / screenshot | Nothing — runs offline | Tesseract not installed |
3 | tesseract.js WASM (downloads traineddata) | ~15–30 s on first call, then ~5–15 s | Internet on first call | tesseract.js fallback if no pre-bundled data |
If all backends fail, da_ocr returns OCR_FAILED with a multi-line remediation hint pointing you to the install command for your platform.
Install Tesseract for fast OCR (recommended):
OS | Command |
Windows |
|
macOS |
|
Linux |
|
Configure the tessdata cache directory with DA_MCP_TESSDATA_DIR (default ./tessdata — relative to the process CWD).
For source installs (Linux/macOS/Windows dev workflow), continue to Install below.
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 testUpgrading
da-mcp upgrade is a single CLI command that self-updates whichever way you installed it — the entry point auto-detects binary vs source mode (process.execPath === process.argv[1]), so the same command works for both the single-binary release and a source checkout:
# Binary install (Windows single-binary release):
.\da-mcp.exe upgrade
# Source install (Linux/macOS/Windows dev workflow):
node /projects/da-mcp/dist/server-dispatch.js upgrade
# or:
npm run upgradeBoth modes accept --force (alias -f). Pass it to reinstall even when the version comparison says you're up to date, or (in source mode) to discard uncommitted local changes.
Binary mode (da-mcp.exe upgrade)
For a Node SEA single-binary install (e.g. Windows da-mcp-win32-x64.exe):
Query GitHub Releases —
GET https://api.github.com/repos/cioinside/da-mcp/releases/latestreturns the latest non-prerelease release with its asset list and sha256 digests.Compare versions — the embedded build-time constant (
process.env.DA_MCP_VERSION, injected by esbuild--defineinscripts/build-sea.sh) is compared againsttag_name. If the running version is already ≥ the release, the command is a no-op (unless--force).Pick the matching asset —
da-mcp-{platform}-{arch}[.exe]for the currentprocess.platform+process.arch.Download to
${execPath}.new.<ts>(sibling of the running binary, never overwriting it in place).Verify sha256 if the asset has a
digest: sha256:…field — mismatches abort before any rename.Atomic replace — rename the running binary to
${execPath}.old.<ts>(Windows allows renaming a running executable; the process keeps its file handle), then rename the staged file toexecPath. On failure, the staged file is left behind and the original is untouched.Restart the service if one is registered via
install-service(see below). If no service is installed, prints a one-line reminder to restart your MCP client manually.
The previous binary is kept at ${execPath}.old.<ts> so the operator can roll back manually if the new binary misbehaves on first launch.
Source mode (npm run upgrade)
For a source checkout (any OS):
Refuse dirty trees —
git status --porcelainmust be empty unless--forceis passed.git fetch origin <branch>+git reset --hard origin/<branch>— fast-forward to the latest commit on the current branch.npm ci— locked, reproducible dependency install.npm run build— TypeScript compile todist/.npm run typecheck— strict-mode smoke check.Restart the service if one is registered, or print a reminder to restart the MCP client manually.
The command refuses to run on a detached HEAD — check out a branch first.
Run da-mcp as a system service (auto-restart)
For long-running installations, da-mcp registers as a managed service so that upgrade can bounce it without manual intervention:
# Install (one-shot, needs root / Administrator):
node /projects/da-mcp/dist/server-dispatch.js install-service
# Uninstall later:
node /projects/da-mcp/dist/server-dispatch.js uninstall-serviceOS | Service type | Restart command used by |
Linux |
|
|
macOS |
|
|
Windows |
|
|
Templates live in scripts/systemd/, scripts/launchd/, and scripts/windows/. Service installation requires elevated privileges (sudo / Run as Administrator). The default transport after install-service is HTTP with token auth (so multiple MCP clients can share one daemon); use DA_MCP_TRANSPORT=stdio if you prefer per-client stdio.
Run
stdio (default)
The server speaks MCP over stdio. Configure your MCP client to launch node /projects/da-mcp/dist/server-dispatch.js (or npx tsx src/server-dispatch.ts for dev).
HTTP (opt-in, token-protected)
Set DA_MCP_TRANSPORT=http to expose the server on http://0.0.0.0:3000/<token>. A 256-bit random token is generated on first start and persisted at:
OS | Token path |
Linux |
|
macOS |
|
Windows |
|
The token file is created with mode 0o600 (owner-only). Rotate it any time:
node /projects/da-mcp/dist/server-dispatch.js token regenerate
# → http://0.0.0.0:3000/<43-char-base64url-token>
# (substitute the host's LAN IP for 0.0.0.0 when configuring the remote client)Override defaults with env vars:
DA_MCP_HTTP_HOST— bind address (default0.0.0.0— LAN-reachable, token-gated); supports IPv4, IPv6 ([::1]), and hostnameDA_MCP_PORT— port (default3000)DA_MCP_TOKEN_PATH— override token storage path
The URL is a bearer-style token — anyone with the token can call tools (mouse, keyboard, screenshot, launch). Default 0.0.0.0 bind means the daemon is reachable from any host that can route to this machine (LAN, VPN, public IP). The token is the sole auth — its 256-bit entropy is unguessable, but treat it as a password: protect the token file, and rotate it (token regenerate) if it may have leaked. To restrict the bind to the loopback interface only, set DA_MCP_HTTP_HOST=127.0.0.1 — the server prints a one-line confirmation at startup.
Remote access from another host on the LAN
Because the default DA_MCP_HTTP_HOST=0.0.0.0 already listens on all interfaces, no special launcher is needed:
DA_MCP_TRANSPORT=http npm start
# → server boots, binds 0.0.0.0:3000, prints URL with token to stderrOn the remote machine, configure your MCP client with http://<lan-ip>:3000/<token> — replace 0.0.0.0 with the host's actual LAN IP (hostname -I, ipconfig getifaddr en0, ipconfig).
Open the host firewall for inbound TCP on DA_MCP_PORT (default 3000) once per OS — this requires elevation and varies per platform:
OS | Command |
Linux (firewalld) |
|
Linux (ufw) |
|
macOS | System Settings → Network → Firewall → allow incoming for the |
Windows (PowerShell, admin) |
|
OpenCode / Claude Desktop example config
stdio (default — per-client process)
{
"mcpServers": {
"da-mcp": {
"command": "node",
"args": ["/projects/da-mcp/dist/server-dispatch.js"],
"env": {
"DISPLAY": ":0",
"DA_MCP_LOG": "info"
}
}
}
}HTTP (token-protected — share one daemon across clients / hosts)
Start the server with DA_MCP_TRANSPORT=http (see HTTP section above for bind/port/token details), then grab the URL it printed at startup — or regenerate the token any time with node /projects/da-mcp/dist/server-dispatch.js token regenerate. Paste the result into the url field below (substitute the server host's LAN IP for 0.0.0.0 when configuring a remote client).
OpenCode (~/.config/opencode/opencode.json):
{
"mcpServers": {
"da-mcp": {
"type": "remote",
"url": "http://<host>:<port>/<token>"
}
}
}Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"da-mcp": {
"url": "http://<host>:<port>/<token>"
}
}
}Cross-platform notes
OS | Screenshot | Input | Notes |
Linux X11 |
|
|
|
Linux Wayland |
|
|
|
macOS |
| Stub in v1.0.0 — see #19; Windows SEA binary is the recommended path | First screenshot call may need Screen Recording permission (TCC) |
Windows |
| PowerShell + | The v1.0.0 single-binary release target — no native NAPI deps |
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 vitestTest inventory
28 unit test files + 2 e2e (e2e skip in mock mode)
540 tests passing / 18 skipped / 0 failed in
DA_MCP_TEST_MODE=mock npm test(e2e require real X11/tesseract; input dispatcher tests cover per-OS stubs for macOS + Windows PowerShell paths). Post-tool additions:da_find_text,da_wait_for_window,da_wait_for_text,da_verify_pixels,install-tesseractCLI subcommand bring the total to 540 passing.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.tssourceAll
spawn*calls withshell: falseAll native errors wrapped in
DaMcpErrorwith typedcodefromErrorCodeunionPublic surface re-exported from
src/screenshot/index.tsandsrc/input/index.ts— consumers import from there, not from per-operation filesForbidden:
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 (default0.0.0.0— LAN-reachable, token-gated); supports IPv4, IPv6, hostnameDA_MCP_TOKEN_PATH— override the auth token storage path
License
MIT
Available Tools
20 toolsda_clickB
Click a mouse button (defaults to "left", count 1). When x/y are given, the cursor is moved first.
Inputs: x, y, count, button.
da_click_textA
Click the on-screen element whose text label matches text. Uses OCR + UI-element classification to locate the text, then clicks the center of its bounding box. Set fuzzy: true for case-insensitive matching with whitespace normalization. Returns the clicked coordinates, the matched bbox, and the recognized text. Throws NOT_FOUND when no element matches.
Inputs: text (required), fuzzy, displayId.
da_double_clickB
Double-click the left mouse button. Moves to (x, y) first when provided.
Inputs: x, y.
da_dragB
Drag from (x1, y1) to (x2, y2). Button defaults to "left"; non-left buttons are accepted in the schema but currently ignored.
Inputs: x1 (required), x2 (required), y1 (required), y2 (required), +1 more.
da_draw_pathA
Draw a multi-point path with the mouse by tracing through points with the given button held. Useful for freeform shapes, signatures, circles (as N points on the circumference), etc. modifiers are held pressed for the duration of the trace (e.g. ["shift"] for constrained-drawing in Paint). Strict modifier pairing is enforced even on errors.
Inputs: points (required), button, modifiers, durationMs.
da_find_textA
Locate the on-screen element whose text label matches text (OCR + UI-element classification). Returns the bounding box, the center coordinates, the recognized text, and the OCR confidence — but does NOT click. Set fuzzy: true for case-insensitive matching with whitespace normalization. Throws NOT_FOUND when no element matches. Use this when you need to decide an action (click vs. drag vs. type-into) based on element position; use da_click_text when you already know the action is "click the center".
Inputs: text (required), fuzzy, displayId.
da_get_mouse_positionA
Return the current cursor position as { x, y } in screen coordinates.
Inputs: no named parameters.
da_keyA
Press a key by name, optionally with modifiers (e.g. {"key":"c","modifiers":["ctrl"]} for Ctrl+C). holdMs > 0 issues a press-hold-release.
Inputs: key (required), holdMs, modifiers.
da_launchB
Launch a program by name or absolute path. argv[0] is required. cwd, env, timeoutMs, and detached are optional.
Inputs: argv (required), cwd, env, detached, +1 more.
da_list_displaysA
List connected displays with id, bounds, scale factor, rotation, and primary flag.
Inputs: no named parameters.
da_move_mouseC
Move the cursor to absolute (x, y) screen coordinates.
Inputs: x (required), y (required), durationMs.
da_ocrC
Run OCR (Tesseract) on a display and return recognized text plus classified UI elements.
Inputs: lang, displayId.
da_screenshotA
Capture a PNG screenshot of the primary display (when displayId is null/undefined) or a specific display id (0..2147483647).
Inputs: displayId.
da_scrollA
Scroll the wheel at the current (or given) cursor position by (dx, dy) pixel deltas. Positive dy = down, positive dx = right.
Inputs: dx (required), dy (required), x, y, +1 more.
da_typeA
Type a string at the current keyboard focus. Empty string is a no-op. Optional per-char delay (ms).
Inputs: text (required), perCharDelayMs.
da_verify_pixelsA
Block until a pixel-level predicate holds on the next screenshot, or timeoutMs (default 10000, max 60000) elapses. Predicates: {kind:"color", rgb:[r,g,b], tolerance?, minCount} (counts matching pixels, succeeds when count >= minCount); {kind:"diff", baseline:"", threshold, tolerance?} (succeeds when >= threshold fraction of pixels differ from the baseline PNG). Optional region clips the check to a rectangle. Polls every intervalMs (default 200). Throws NOT_FOUND on timeout. Use this to verify visual state after an action (e.g. "wait until 200+ red pixels appear on the canvas").
Inputs: predicate (required), region, displayId, timeoutMs, +1 more.
da_wait_for_textA
Block until text appears on screen (OCR + UI-element classification), or timeoutMs (default 5000, max 60000) elapses. Match strategy is identical to da_click_text / da_find_text — substring by default, set fuzzy: true for case-insensitive + whitespace-normalized matching. Polls every intervalMs (default 200, min 50, max 5000). Throws NOT_FOUND on timeout. Use this after a click / key / dialog-open to confirm the new state is painted before continuing.
Inputs: text (required), fuzzy, displayId, timeoutMs, +1 more.
da_wait_for_windowA
Block until a window with a matching title appears in da_window_list, or timeoutMs (default 5000, max 60000) elapses. Match strategies: "substring" (default, case-insensitive), "exact", or "regex" (full match). Polls every intervalMs (default 200, min 50, max 5000). Throws NOT_FOUND on timeout — use this after da_launch to wait for a newly-spawned app to finish painting before clicking inside it.
Inputs: title (required), match, timeoutMs, intervalMs.
da_window_focusA
Bring a top-level OS window to the foreground so subsequent da_click / da_type / da_key land on it. Resolves the window either by hwnd (exact match) or by a case-insensitive substring of its title (optionally narrowed by pid). bringToTop: true (default) also calls SetWindowPos(HWND_TOP) on Windows so the window is at the top of the Z-order. Returns { hwnd, pid, title, foreground } where foreground: false indicates the OS refused the foreground change (e.g. another process holds the foreground lock on Windows). Throws NOT_FOUND when no visible window matches. Linux requires wmctrl on PATH; macOS uses osascript; Windows uses PowerShell + Win32 SetForegroundWindow.
Inputs: pid, hwnd, title, bringToTop.
da_window_listA
List all visible top-level OS windows. Returns one WindowInfo per window with hwnd (platform-specific integer handle), pid (owning process id), title, rect (x/y/width/height), and isVisible. The hwnd can be passed to da_window_focus to bring the window to the foreground. Linux requires wmctrl on PATH; macOS uses osascript; Windows uses PowerShell + Win32 EnumWindows.
Inputs: no named parameters.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 20 tools
Each tool targets a distinct action and resource: mouse positioning vs. clicking vs. dragging, text finding vs. clicking text vs. waiting for text, window listing vs. focusing vs. waiting. Even similar tools like da_find_text and da_click_text are clearly differentiated by whether they click, and da_move_mouse vs. da_drag is clarified by the button-hold behavior.
All tools share the da_ prefix and snake_case, but the pattern varies: most are verb_noun (da_move_mouse, da_list_displays), some are bare verbs (da_click, da_scroll, da_key), and a few are object-first (da_window_list, da_window_focus) or verb_prep_noun (da_wait_for_text, da_wait_for_window). This mix is readable but not fully predictable.
20 tools is on the high side, but the desktop automation domain genuinely requires coverage for mouse, keyboard, windows, displays, OCR, screenshots, and launching. Every tool earns its place and the count feels justified rather than bloated.
The surface covers core desktop automation workflows well: input, text location, window focus, waiting, and launching. Minor gaps exist—no window resize/move/close, no clipboard access, no region-specific screenshot capture—but agents can work around these without major dead ends.
Maintenance
Related MCP Connectors
Web scraping, browser automation, screenshots and AI data extraction for AI agents via MCP.
OCR, transcription, file extraction, and image generation for AI agents via MCP.
Melaya is a remote MCP server. It gives an assistant hands on your own Android phone and browser: it reads the screen through the accessibility tree, then taps, types and navigates inside the apps and sites you allow-list, with no per-app API. It also builds, schedules and runs agent pipelines across 6k+ connected tools. OAuth 2.1, nothing to install.
MCP server for your apps' tools and custom tools, plus hosted AI agents and approval-gated workflows
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceA desktop automation MCP server that enables AI agents to interact with Linux environments through screenshots, window inspection, and input simulation. It provides tools for mouse control, keyboard input, and screen capture using xdotool and XDG Desktop Portals.MIT
- AlicenseAqualityAmaintenanceWindows desktop automation MCP server — screenshot, mouse, keyboard & UI Automation. Lets LLM agents see and control your Windows desktop directly.30677 npm25MIT
- AlicenseNot gradedqualityBmaintenanceAn MCP server that gives any AI assistant eyes and hands on your desktop — screenshots, clicking, typing, OCR, window management, accessibility-tree queries, workflow recording.5Apache 2.0
- AlicenseNot gradedqualityBmaintenanceGives AI agents and MCP clients direct control over native desktop apps, Chrome/Electron browsers, and Android devices with screenshots, OCR, accessibility-based element lookup, input simulation, window management, CDP, and ADB in one local server.141MIT