New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

hypr-cua-mcp

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

hypr-cua-mcp

Hyprland/Wayland computer-use MCP server — screenshot (grim) + click/type (uinput) with hyprctl-tree targeting

pipPyPI
Version
0.1.2
Weekly downloads
215
377.78%
Maintainers
1
Weekly downloads
 
Created

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

ToolWhat it does
list_windowsEvery Hyprland window: id (address), class, title, workspace, output, global rect, floating/focus/visibility
get_outputsActive 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_mouseCurrent global pointer location

Act

ToolWhat it does
click / double_click / move_mouse / drag / scrollPointer 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)

ToolWhat 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)

ToolWhat it does
tmux_list / tmux_new / tmux_run / tmux_keys / tmux_read / tmux_killDrive 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_stopRun 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_closeDrive tabs in a dedicated Chrome over the DevTools protocol, never your main profile (see Browser channel)

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.

ToolNeeded forArchDebian/Ubuntu
hyprctltree + control (required)bundled with hyprlandbundled with hyprland
grimscreenshot / screenshot_window (required)pacman -S grimapt install grim
wl-clipboardread_clipboard / write_clipboardpacman -S wl-clipboardapt install wl-clipboard
uvruns the server + syncs Python depspacman -S uvsee astral.sh/uv
tmuxtmux_* / task_* tools (optional)pacman -S tmuxapt install tmux
Chromium / Chromecdp_* browser tools (optional)pacman -S chromiumapt 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.

VariableDefaultEffect
HYPR_CUA_CDP_PORT9222DevTools port to attach to (or launch on)
HYPR_CUA_CDP_AUTOLAUNCH10 = attach-only: never launch a browser, error if nothing is listening
HYPR_CUA_CDP_USER_DATA_DIRfresh temp dirProfile directory for an auto-launched browser
HYPR_CUA_CDP_BINfirst Chrome/Chromium on PATHBrowser 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.

Keywords

ai-agents

FAQs

Related posts