Windows desktop-control MCP server: screenshot, window mgmt, mouse/keyboard input, and screen-recording. Config-gated tool groups (observe/window/input/record) -- input disabled by default. Local-only, single-machine.
Desktop-mcp has 18 tools with consistent naming (verb_noun pattern: screenshot, list_windows, focus_window, mouse_click, etc.) and complete input schemas with type definitions. Descriptions are present but brief (10-80 chars), below the 50-200 char LLM-optimized baseline. Output schemas are documented in descriptions (e.g., 'Returns {path, w, h, monitor}') but not formally specified in JSON Schema. Error handling is present in input_tools.py with structured error objects (ok/error/type/message), but recovery guidance is minimal. Security gating via environment variables (DESKTOP_MCP_ENABLE_*) is well-designed. Major gap: no parameter descriptions for optional fields like 'region' (dict structure undefined), 'monitor' (index semantics unclear), and 'downscale_max_px' (units/constraints missing). Rate limiting is implemented but not documented in tool descriptions.
Bring a window to the foreground by title substring. Requires DESKTOP_MCP_ENABLE_WINDOW=1.
Return the currently focused/active window, or null if none.
Press a key combination, e.g. ['ctrl', 'c']. Requires DESKTOP_MCP_ENABLE_INPUT=1.
Press a single key (pyautogui key name, e.g. 'enter', 'a'). Requires DESKTOP_MCP_ENABLE_INPUT=1.
List all visible top-level windows with title and bounds.
Minimize a window by title substring. Requires DESKTOP_MCP_ENABLE_WINDOW=1.
Click the mouse at (x, y) or the current position. Requires DESKTOP_MCP_ENABLE_INPUT=1.
Parameter descriptions are minimal or missing for complex types. 'region' is described as 'Region dict with left, top, width, height, optional' but lacks field-level documentation. 'monitor' index semantics (0=virtual desktop, 1..N=physical) are buried in description, not in parameter docs. LLMs cannot infer nested object structure.
Tool descriptions are too brief (10-80 chars, below 50-200 char baseline). 'List all visible top-level windows with title and bounds' (54 chars) lacks context on when to call it vs window_info. 'Return the currently focused/active window, or null if none' (60 chars) does not explain use case or return structure.
Output schemas are documented in prose ('Returns {path, w, h, monitor}') but not formally specified in JSON Schema. LLMs cannot parse prose descriptions reliably. Formal output schemas enable downstream tool chaining and reduce hallucination.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | D | 55 | 2026-07-28+ | v2 |
Drag the mouse from (x1, y1) to (x2, y2). Requires DESKTOP_MCP_ENABLE_INPUT=1.
Move the mouse cursor to (x, y) in virtual-desktop coordinates. Requires DESKTOP_MCP_ENABLE_INPUT=1.
Scroll the mouse wheel by `clicks` (positive=up) at an optional position. Requires DESKTOP_MCP_ENABLE_INPUT=1.
Move and resize a window by title substring. Requires DESKTOP_MCP_ENABLE_WINDOW=1.
Start an ffmpeg gdigrab screen recording with a hard max_duration_s cap. Requires DESKTOP_MCP_ENABLE_RECORD=1.
Check whether a recording is in progress. Requires DESKTOP_MCP_ENABLE_RECORD=1.
Stop the active recording and return {path, bytes, duration_s}. Requires DESKTOP_MCP_ENABLE_RECORD=1.
Restore a minimized window by title substring. Requires DESKTOP_MCP_ENABLE_WINDOW=1.
Capture a PNG screenshot of a monitor, region, or window. Returns {path, w, h, monitor}.
Type a string via simulated keystrokes. Requires DESKTOP_MCP_ENABLE_INPUT=1.
Look up a window by title substring; returns its bounds and state.
Security gating (DESKTOP_MCP_ENABLE_WINDOW, DESKTOP_MCP_ENABLE_INPUT, DESKTOP_MCP_ENABLE_RECORD) is enforced at function level but not documented in tool descriptions. LLMs cannot know which tools are disabled without calling them and hitting errors. Add 'Requires DESKTOP_MCP_ENABLE_* environment variable' to each gated tool's description.
Rate limiting (60 actions/min for input tools) is implemented in config.py but not documented in tool descriptions or error messages. When an LLM hits the rate limit, it receives a generic error with no guidance on retry strategy or backoff.