Skip to main content
Glama

hypr-cua-mcp

Status: stable · maintained CI PyPI Python License: MIT

A Hyprland/Wayland computer-use MCP server. It gives an MCP client (Claude Code, etc.) the ability to see your desktop, understand it via the Hyprland tree, and drive it with mouse + keyboard. It runs locally over stdio.

Why this exists: generic Linux computer-use servers are blind to the compositor — they hand the model a giant multi-monitor screenshot and hope its pixel guesses land. This server crops to one output (or one window) with grim, downscales to the vision-API budget, and exposes hyprctl's tree so the model targets windows by identity, not luck. Coordinates are always given in the screenshot the model was just shown; the server maps them back to global layout pixels.

Built for Wayland/Hyprland: grim (capture) + a self-owned uinput device (input) + hyprctl (tree/control) + wl-clipboard. It is the Wayland successor to i3-computer-use-mcp (X11).

Tools

Window ids are Hyprland addresses (0x…) from list_windows — not integers.

See

Tool

What it does

list_windows

Every Hyprland window: id (address), class, title, workspace, output, global rect, floating/focus/visibility

get_outputs

Active monitors: global geometry, scale, workspace

screenshot(output?)

Screenshot one output (default: focused), downscaled to the API budget

screenshot_window(con_id)

Full-res crop of a single window (by address) — best for small text

get_mouse

Current global pointer location

Act

Tool

What it does

click / double_click / move_mouse / drag / scroll

Pointer actions; coords in a screenshot's pixel space (pass its frame_id). move_mouse(follow=True) also focuses the window under the pointer

type_text(text, con_id?) / key(keys, con_id?)

Keyboard input; with a con_id, focus that window first and refuse to type if focus doesn't land

read_clipboard(selection?) / write_clipboard(text, selection?, secret?)

Exact text in/out of the clipboard/primary selection (no OCR); write returns only a length, never the value

Control Hyprland (needs a Lua config, see Requirements)

Tool

What it does

focus_window(con_id)

Focus a window by address (best-effort; see the follow_mouse caveat under Safety)

launch(command)

Launch an app via Hyprland's exec dispatcher

goto_workspace(ws) / move_window_to_workspace(con_id, ws)

Switch to / move a window (by address) to a workspace

set_floating(con_id, enable?)

Toggle a window's floating state

layout_message(message)

Send a message to the active layout engine (e.g. togglesplit)

run_dispatch(dsp_expr)

Raw Hyprland dispatcher escape hatch (exit blocked)

Work off-screen (no window, no focus stolen)

Tool

What it does

tmux_list / tmux_new / tmux_run / tmux_keys / tmux_read / tmux_kill

Drive terminals in detached tmux sessions on an isolated hypr-cua socket; it never touches your own tmux sessions

task_start / task_status / task_list / task_send / task_stop

Run a command as a background task and poll it (running → done + exit code); answer prompts with task_send

cdp_targets / cdp_open / cdp_navigate / cdp_read / cdp_eval / cdp_click / cdp_type / cdp_close

Drive tabs in a dedicated Chrome over the DevTools protocol, never your main profile (see Browser channel)

Related MCP server: claude-workman

The coordinate model (the important bit)

The Hyprland tree uses global layout pixels spanning all monitors. Screenshots are cropped + downscaled, so every screenshot records a mapping frame (region origin + per-axis scale). Pointer tools accept coordinates in the screenshot's pixel space and translate them:

global_x = region.x + screenshot_x / scale_x
global_y = region.y + screenshot_y / scale_y

Practically: take a screenshot (it returns a frame_id), then call click(x, y, frame_id=…) with the pixel you see in that image. Pointer tools reject a missing or stale frame_id rather than guessing against the wrong one.

The uinput pointer positions absolutely: global pixels map to the device's abs axes via the layout extent read from hyprctl monitors (verified ~1px).

Requirements

  • Hyprland on Wayland. Developed and tested on Hyprland 0.56 across three monitors at scale 1.

  • A Lua config (hyprland.lua, Hyprland 0.55+) for the window-control tools. These tools go through hyprctl eval and Hyprland's Lua hl.dispatch API: focus_window, launch, goto_workspace, move_window_to_workspace, set_floating, layout_message, run_dispatch, plus type_text/key with a con_id and move_mouse(follow=True). Everything else works with any config: screenshots, pointer and keyboard input, the window tree, the clipboard, and the tmux/task/browser channels.

  • Read-write access to /dev/uinput for mouse and keyboard input (see below).

  • Python 3.11+ and uv.

  • It runs inside your Hyprland session: it needs WAYLAND_DISPLAY, XDG_RUNTIME_DIR and HYPRLAND_INSTANCE_SIGNATURE from the environment. MCP clients launched from your desktop pass these through.

Install the dependencies

The server shells out to a handful of CLI tools. screenshot needs grim, the clipboard tools need wl-clipboard, and everything reads/drives Hyprland through hyprctl; tmux and a Chromium/Chrome are only needed for the optional terminal and browser channels.

Tool

Needed for

Arch

Debian/Ubuntu

hyprctl

tree + control (required)

bundled with hyprland

bundled with hyprland

grim

screenshot / screenshot_window (required)

pacman -S grim

apt install grim

wl-clipboard

read_clipboard / write_clipboard

pacman -S wl-clipboard

apt install wl-clipboard

uv

runs the server + syncs Python deps

pacman -S uv

see astral.sh/uv

tmux

tmux_* / task_* tools (optional)

pacman -S tmux

apt install tmux

Chromium / Chrome

cdp_* browser tools (optional)

pacman -S chromium

apt install chromium

Arch, in one go (Hyprland assumed already installed):

sudo pacman -S grim wl-clipboard uv tmux chromium

Input uses a self-owned uinput device, so the process needs read-write on /dev/uinput. It needs no root and no ydotoold. Run getfacl /dev/uinput. If it lists your user with rw (typically through a uaccess udev rule), you're set. If not, add a rule that grants access to the user logged in at the seat:

echo 'KERNEL=="uinput", SUBSYSTEM=="misc", TAG+="uaccess", OPTIONS+="static_node=uinput"' \
  | sudo tee /etc/udev/rules.d/60-hypr-cua-uinput.rules
sudo udevadm control --reload && sudo udevadm trigger

The Python deps (mcp, pillow, evdev, websocket-client) are synced by uv on first launch.

Miss one and the matching tool says so: calling screenshot without grim returns "grim not found; install it …" straight to the model, so it can tell you what to install instead of failing on a cryptic traceback.

Register with your MCP client

It's a local stdio server. The client starts it on demand, and you don't need a clone.

Claude Code:

claude mcp add hypr-cua -- uvx hypr-cua-mcp

Clients with an mcpServers JSON config (Cursor, Gemini CLI, Cline, …):

{
  "mcpServers": {
    "hypr-cua": {
      "command": "uvx",
      "args": ["hypr-cua-mcp"]
    }
  }
}

Codex CLI (~/.codex/config.toml):

[mcp_servers.hypr-cua]
command = "uvx"
args = ["hypr-cua-mcp"]

From a local checkout: the wrapper syncs deps on first launch and makes the browser channel attach-only (see Browser channel).

claude mcp add hypr-cua -- /path/to/hypr-computer-use-mcp/bin/hypr-cua-mcp

Reconnect your client (in Claude Code: /mcp → reconnect) to pick up the server. Smoke-test the plumbing first (read-only, no clicks):

uvx hypr-cua-mcp --selftest

Browser channel

The cdp_* tools drive Chrome/Chromium tabs over the DevTools protocol, so browser work needs no window focus. They never open your main profile. By default they attach to a browser on port 9222. If nothing is listening there, they launch a throwaway Chrome with a fresh temp profile.

For logins that persist, run a dedicated automation profile and make the server attach-only with HYPR_CUA_CDP_AUTOLAUNCH=0. The checkout's bin/hypr-cua-mcp wrapper sets that for you, and bin/hypr-cua-chrome starts the browser. With uvx, the equivalent is:

chromium --user-data-dir="$HOME/.local/share/hypr-cua-chrome" \
  --remote-debugging-port=9222 --remote-allow-origins=http://127.0.0.1:9222 \
  --no-first-run --no-default-browser-check

Log in only to what you're happy to automate. Any local process that can reach the debug port can drive that profile.

Variable

Default

Effect

HYPR_CUA_CDP_PORT

9222

DevTools port to attach to (or launch on)

HYPR_CUA_CDP_AUTOLAUNCH

1

0 = attach-only: never launch a browser, error if nothing is listening

HYPR_CUA_CDP_USER_DATA_DIR

fresh temp dir

Profile directory for an auto-launched browser

HYPR_CUA_CDP_BIN

first Chrome/Chromium on PATH

Browser binary to auto-launch

Set them in your client's server config. In Claude Code: claude mcp add hypr-cua -e HYPR_CUA_CDP_AUTOLAUNCH=0 -- uvx hypr-cua-mcp. In JSON configs, add an "env": {…} block next to command.

Safety

  • No auth — it's a local stdio server. Don't expose it over a socket.

  • There is no sandbox on Wayland: it drives your real desktop and can click anything you can — terminals with sudo, your password manager, Slack, email. Supervise live runs.

  • Emergency stop: kill the MCP server (its uinput device dies with it).

  • follow_mouse=1 caveat: Hyprland keyboard focus tracks the pointer, so focus_window(address) is best-effort — the reliable way to focus a window for typing is to click it via screenshot coordinates. type_text(con_id=…) raises rather than typing into an unverified window.

  • Input is real uinput, so it resets the compositor idle timer naturally (no synthetic-input self-lock, unlike X11). See docs/gotchas.md.

Known limitations

  • Typing is US-ASCII only: type_text skips any other character. For anything else, write_clipboard and paste.

  • Capture on monitors with scale ≠ 1 is untested, and the crop is likely off there.

  • The synthetic pointer doesn't hover-focus. Click to focus, or use move_mouse(follow=True).

  • cdp_click / cdp_type inject JavaScript, so pages that insist on trusted input events won't react.

Details, plus the task runner's edge cases: KNOWN-ISSUES.md.

Project status

Stable and maintained. hypr-cua-mcp does what it set out to do, so the commit log is quiet on purpose. Expect bug fixes, Hyprland-compatibility updates and dependency bumps, not a stream of new features. A quiet repo here means done, not abandoned. Issues and PRs still get answered.

Contributing

Bug reports and fixes are welcome; for anything bigger, open an issue first. See CONTRIBUTING.md, and AGENTS.md for setup, tests, and the Hyprland conventions that bite.

Registry

On PyPI as hypr-cua-mcp and in the MCP registry as com.smirnovlabs/hypr-cua-mcp (manifest: server.json).

mcp-name: com.smirnovlabs/hypr-cua-mcp

License

MIT — see LICENSE.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server for controlling Hyprland Wayland compositor: screen capture, mouse/keyboard injection, and automation via native Hyprland IPC and wlr protocols.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables MCP clients to control a Linux/X11 desktop like a human: see the screen, move the mouse, click UI elements via the accessibility tree, type text, and manage windows.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables MCP clients to inspect and control an Omarchy/Hyprland desktop by taking screenshots, focusing windows, moving/clicking the mouse, scrolling, typing text, and sending keyboard shortcuts.
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables AI agents to control Hyprland desktops via MCP, including pointer/keyboard input, screenshots, launching apps, and natural-language window targeting using a local decision model.
    14
    1
    MIT