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: Omarchy Computer MCP

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.

Maintenance

ActivityNo data
ResponsivenessSyncing

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
    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
  • A
    license
    Not graded
    quality
    A
    maintenance
    An MCP server that lets agents observe and control an Omarchy or Hyprland desktop: list monitors, windows, workspaces, and accessibility controls, capture screenshots, then move the pointer, type text, send shortcuts, and manage windows and workspaces. It works over stdio with any MCP client, guarding actions with session-lock and single-driver checks while treating on-screen content as untrusted data.
    Apache 2.0