Skip to main content
Glama

desktop-mcp

Desktop vision and control for LLMs on Linux, including Wayland — the model takes a screenshot of the whole screen and clicks, types and scrolls on what it sees. Plain MCP over stdio, so it works in Claude Code, Claude Desktop, Cursor, or any other MCP client.

Read this first: you may not need this

Anthropic ships computer use natively, and where that works you should use it instead — it is better integrated and actually maintained:

  • macOS and Windows: Claude Desktop has computer use built in, in Cowork and Claude Code. It drives your real desktop. (docs)

  • Building your own agent: the Claude API has a first-party computer tool (GA as the computer_toolset_20260801 toolset). (docs)

The gap this fills is Linux. As of August 2026 computer use is not available in the Linux desktop app, and desktop automation on Wayland is genuinely awkward in a way X11 never was:

  • COSMIC's compositor does not implement wlr-screencopy, so grim fails outright and every screenshot tool needs a per-compositor path.

  • There is no protocol to query the cursor position, and none to enumerate another client's windows.

  • Input has to go in below the compositor via /dev/uinput, because the commonly packaged ydotool exposes a relative pointer whose long jumps get mangled by pointer acceleration.

If you are on macOS or Windows, this repo is mostly redundant. If you are on Linux — particularly Wayland — it is the working option.

Desktop-automation MCP servers are not a new category; there are thousands of MCP servers. What is specific here is the Wayland-native absolute-uinput input path, measured pixel-exact on COSMIC.

Related MCP server: mcp-linux-desktop

Status

Written to be cross-platform, but only one configuration has actually been exercised. The rest is code paths, not evidence — treat it accordingly.

Platform

Screenshot

Input

Tested

Linux / Wayland

cosmic-screenshot, grim, spectacle, GNOME D-Bus

/dev/uinput

Yes — COSMIC only

Linux / X11

maim, ImageMagick import, mss

/dev/uinput, xdotool

No

macOS

screencapture

pyautogui

No

Windows

mss, PowerShell

pyautogui

No

What was tested, and how

Verified end-to-end on:

  • Pop!_OS 24.04 LTS, kernel 7.0.11, COSMIC (cosmic-comp 1.0.0), Wayland, single 2560x1440 display, Python 3.13.

Results:

Check

Method

Result

Screenshot

cosmic-screenshot backend

2560x1440 captured, downscaled to 1400x788

Pointer accuracy

5 target points, read back through XWayland

5/5 exact, 0px error

Buttons and scroll

xev event log

ButtonPress/Release for buttons 1, 3, 4

Drag

xev motion count

26 MotionNotify events between press and release

Typing

Both wtype and raw uinput keycodes

Text landed in a focused editor

Chords

ctrl+a in an editor

Applied

Click via MCP

Full stdio round trip against a native Wayland app

New tab opened

Untested and worth knowing: multi-monitor layouts, HiDPI scaling, non-US keyboard layouts on the raw-keycode path (wtype handles those correctly; uinput keycodes assume US), and every non-COSMIC compositor.

Reports from other platforms are welcome — desktop_info dumps everything needed to diagnose a backend problem.

Install

git clone <this repo> && cd desktop-mcp
uv venv && uv pip install -e .

macOS, Windows, and Linux/X11 also need the input dependencies:

uv pip install -e ".[gui]"

Linux: one-time permission setup

Injecting input on Wayland means writing to /dev/uinput, which is root-owned by default:

bash scripts/setup-linux.sh   # needs sudo once
# then log out and back in

macOS: one-time permission setup

Grant Screen Recording and Accessibility to whichever app launches the server (Terminal, iTerm, Claude Desktop) in System Settings → Privacy & Security.

Wire it up

Claude Code:

claude mcp add desktop -- /absolute/path/to/desktop-mcp/.venv/bin/python -m desktop_mcp.server

Claude Desktop (claude_desktop_config.json) and Cursor (.cursor/mcp.json) use the same shape:

{
  "mcpServers": {
    "desktop": {
      "command": "/absolute/path/to/desktop-mcp/.venv/bin/python",
      "args": ["-m", "desktop_mcp.server"]
    }
  }
}

Tools

Tool

Purpose

screenshot

Capture the desktop

click

Left/right/middle click, single or double

move

Move the pointer without clicking (hover)

drag

Press, move, release — selections, sliders, window moves

scroll

Wheel scroll under a point

type_text

Type into the focused element

key

Chords like ctrl+s, alt+tab, cmd+space

cursor_position

Where the pointer is

list_windows

Open windows, where the OS permits it

wait

Pause, then look again

desktop_info

Environment, active backends, safety settings

Coordinates

Screenshots are downscaled (1400px long edge by default) before being sent, so the model works in image pixels; the server maps them back to screen pixels on every call. Pass exactly the coordinates you read off the screenshot.

This is the detail most home-grown computer-use tools get wrong. Miss it and every click lands at a consistent fraction of where it was aimed.

Why /dev/uinput instead of ydotool on Linux

Commonly packaged ydotool builds expose a relative pointer, so a jump to (1280, 720) is delivered as one large relative motion — which the compositor runs through pointer acceleration. The cursor lands near the target rather than on it, and the error grows with distance. An absolute uinput device sidesteps acceleration: measured 5/5 exact on a 2560x1440 screen. ydotool remains a fallback when /dev/uinput is not writable.

Click timing

Compositors dispatch pointer motion asynchronously, and toolkits ignore a button press that arrives before they have processed the motion that put the cursor over the widget. Measured on COSMIC: a 30ms gap between move and press reliably highlights a button but never activates it — the hover state is proof the coordinates are right, which makes this failure easy to misread as a targeting bug. 250ms activates it. A press also has to be held (~90ms) to register as a real click rather than a stray event.

Both delays are tunable; lower them only if your desktop tolerates it.

Safety

This gives a model the same reach over the machine as the person sitting at it. It can read anything on screen — open password managers, private messages, tokens — and click anything, including destructive buttons. Treat a session as if you had handed someone your keyboard.

Environment variables:

Variable

Default

Effect

DESKTOP_MCP_READ_ONLY

off

Screenshots allowed, all input refused

DESKTOP_MCP_RATE_LIMIT

240

Max input actions per minute

DESKTOP_MCP_BLOCK_KEYS

—

Extra comma-separated chords to refuse

DESKTOP_MCP_AUDIT_LOG

—

Append every action to this file

DESKTOP_MCP_MAX_EDGE

1400

Screenshot long edge in pixels

DESKTOP_MCP_MOVE_SETTLE

0.25

Seconds between moving the pointer and pressing

DESKTOP_MCP_PRESS_HOLD

0.09

Seconds a mouse button is held down

ctrl+alt+F1–F12 are always blocked; VT switching can drop the graphical session out from under you.

A read-only reviewer setup:

DESKTOP_MCP_READ_ONLY=1 python -m desktop_mcp.server

Verify

.venv/bin/python scripts/mcp_smoke.py   # real stdio MCP round trip

Available Tools

11 tools
clickB

Click at (x, y), in screenshot-image coordinates.

button: "left", "right" or "middle". count: 2 for a double-click.

Inputs: x (required), y (required), count, button, +1 more.

cursor_positionA

Report where the pointer is, in both image and screen coordinates.

Inputs: no named parameters. Returns: result

desktop_infoA

Report the environment, the chosen backends, and the active safety limits.

Worth calling once at the start of a session, or whenever something fails.

Inputs: no named parameters.

dragB

Press at one point, move, and release at another.

Use for selecting text, moving windows, and dragging sliders.

Inputs: to_x (required), to_y (required), from_x (required), from_y (required), +2 more.

keyB

Press a key or chord, e.g. "enter", "ctrl+s", "alt+tab", "cmd+space".

Modifiers: ctrl, alt, shift, super (aka win), cmd. "cmd" is Command on macOS and Super elsewhere, so one chord works across platforms.

Inputs: combo (required), presses, screenshot.

list_windowsA

List open windows with their titles and geometry, where the OS allows it.

Inputs: no named parameters.

moveB

Move the pointer to (x, y) without clicking -- useful for hover states.

Inputs: x (required), y (required), screenshot.

screenshotA

Capture the desktop and return it as an image.

Take one before acting, and after any action whose result you need to see. All other tools expect coordinates measured on this image.

Inputs: no named parameters.

scrollA

Scroll under the pointer at (x, y).

dy is in wheel detents: negative scrolls down the page, positive scrolls up.

Inputs: x (required), y (required), dx, dy, +1 more.

type_textA

Type text into whatever currently has keyboard focus.

Click the target field first. Newlines in the text are typed as Enter.

Inputs: text (required), screenshot.

waitC

Pause, then look again. Use while a page loads or an animation finishes.

Inputs: seconds, screenshot.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.7/5.0

    Scored across 11 tools

    Disambiguation5/5

    Each tool maps to a distinct input modality or query—screenshot, pointer actions, keyboard actions, and state queries—so there is little chance of selecting the wrong tool. The only adjacent pair is type_text vs key, but their descriptions clearly separate literal text entry from chords/hotkeys.

    Naming Consistency4/5

    Most names are short, lowercase imperative verbs (click, move, drag, scroll, wait), with a few compound state/query names (cursor_position, desktop_info) and two verb_noun pairs (type_text, list_windows). The style is readable and predictable, though not every tool follows a uniform verb_noun pattern.

    Tool Count5/5

    11 tools cover the core desktop-automation surface without redundancy or bloat. Each tool addresses a distinct need, so the count feels intentional and well-scoped.

    Completeness5/5

    The set provides observation (screenshot, cursor_position, list_windows, desktop_info), mouse control (click, move, drag, scroll), keyboard control (type_text, key), and a synchronization primitive (wait), covering the full cycle of GUI automation. I don't see an obvious missing operation that would leave an agent stuck in normal desktop flows.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      A
      maintenance
      Controls a real Linux desktop from any MCP host. Reads accessibility trees, takes screenshots, and drives clicks, scrolls, and keystrokes across GNOME, KDE/KWin, Hyprland, i3, and COSMIC.
      2,975 npm
      876
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables full Linux desktop control including windows, mouse, keyboard, clipboard, audio, screenshots, OCR, accessibility, and system management through MCP-compatible AI agents.
      1
      MIT
    • F
      license
      Not graded
      quality
      C
      maintenance
      An MCP server that gives a model eyes and hands on a Linux Wayland desktop, enabling screenshot capture, mouse/keyboard control, OCR, and icon detection via OmniParser.
      1
      -
    • A
      license
      Not graded
      quality
      C
      maintenance
      MCP server for controlling Linux desktops over Wayland, enabling AI agents to perform mouse, keyboard, window, and screenshot operations on Fedora KDE Plasma.
      AGPL 3.0