hyprcu
hyprcu is an MCP server (also CLI) that gives AI agents dialog-free control of a Hyprland desktop through compositor IPC, virtual input, and accessibility trees.
Desktop discovery:
desktopreturns monitors, workspaces, windows, layers, active window, and cursor in one snapshot.Screenshots and zoom: capture full monitors, windows, or regions;
zoomdoes native-resolution re-capture around a point;stablewaits for frames to settle.Accessibility UI:
uireads AT-SPI trees with global click points and current values;marksoverlays numbered red marks and a legend for one-shot clicking.Mouse control:
pointermoves, clicks, drags, scrolls, supports window-relative coordinates, and can append desktop/screenshot/ui results.Keyboard control:
keyboardtypes text or sends key combos to the focused app or a specified window (does not trigger compositor binds).Click by name/mark:
click_uiclicks controls by accessible name or mark number, with ambiguity handling and optional post-click observation.Window/workspace operations:
hyprfocuses, moves, closes, fullscreens, toggles floating windows, and controls display power over Hyprland IPC.Launching apps:
launchruns commands via Hyprland exec with optional workspace placement and event-driven wait for the new window.Compositor binds:
bindslists the user's keybinds anduse_bindexecutes them directly (synthetic keys can't trigger them).Event waiting:
wait_forblocks on real compositor events (window open/close, workspace changes, layers, urgency, screencast) with filtering and already-held detection.Sequences:
sequenceruns ordered multi-step actions in one call, with optionalstop_on_changeand a final-state observation.Natural-language targeting: window args accept addresses, substrings, or descriptions ("the file browser") resolved locally or via TypeSafe Jev / local kev, with probability gating.
Post-action change detection: acting tools support
then=changesto report what changed on screen after an action.
Provides desktop control for Hyprland, enabling AI agents to interact with windows, workspaces, input (pointer/keyboard), screenshots, and application launching through Hyprland IPC.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@hyprcu take a screenshot of the file browser window"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
hyprcu
Hyprland computer use. Dialog-free desktop control for AI agents: an MCP
server and a CLI over the same primitives. A fork of
hypruse (MIT, IlyasKhallouki) — see Lineage
with the trust, journal, safety-beacon and CLI layers removed, keeping the
Wayland/Hyprland primitives intact. App knowledge and tooling live in docs/ and tools/.
Requires a TypeSafe API key (sends names to TypeSafe)
By default hyprcu resolves descriptions (window="the file browser",
click_ui "submit the form") with TypeSafe's Jev model, a cloud API.
Each such call sends the candidate names to TypeSafe: the titles and
classes of your open windows, or the names and values of the window's
accessible controls, plus your description. Addresses and exact names/
substrings resolve locally and send nothing.
Get a key from TypeSafe and export
TYPESAFE_API_KEYin the environment hyprcu runs in. It is read from the environment only and never written to disk.Without a key, descriptions fail with an explicit message; everything else works.
To keep everything on the machine, set
HYPRCU_CHOOSER=kevand run the local kev model instead (slower and less accurate; see below).
Related MCP server: da-mcp
Which model decides what
hyprcu separates two kinds of judgement, and uses different models for each.
Text decisions (which window is "the file browser", which control does "submit the form"), made from window titles and the accessibility tree:
model | where it runs | what leaves the machine | status |
Jev (TypeSafe), default | cloud | window titles/classes or control names/values + your description | in hyprcu ( |
kev (kev-4b) | local GPU ( | nothing | in hyprcu ( |
Jev is more accurate and faster (28/29 vs 20–25/29 on tools/choice_bench.py)
and can say "none of these"; kev keeps everything local.
Visual decisions (did it work, is a dialog open, what does this say,
which option is selected), made from a screenshot or a --then changes crop.
Jev cannot do these: it reads text only and never sees an image.
model | where it runs | what leaves the machine | status |
kev-vision (Qwen3-VL-4B-Instruct, 4-bit) | local GPU | nothing | benchmark only ( |
Claude Haiku 4.5 / Sonnet 4.6 | cloud (via | the image | benchmark only |
Qwen describes the image, Jev decides from the text | local + cloud | the description | tested and dropped |
kev-vision answers without generating text: it reads the model's probability for each lettered option in one pass (the readout of the Visual Jev paper), and encodes each screenshot once however many questions are asked about it.
Measured on 47 questions an agent asks after acting (32 about the nested test session, in this repo; 15 about the owner's real desktop, kept private), 2026-09-23:
setup | correct | time per question |
Sonnet 4.6 | 45/47 (96%) | ~1.4 s (incl. ~1 s pi startup) |
Haiku 4.5 | 41/47 (87%) | ~1.3 s |
kev-vision, local | 43/47 (91%) | 0.63 s (2 s to encode a full screenshot, then ~0.15 s per question) |
Qwen describes, Jev decides | 36/47 (77%) | 5.9 s |
kev-vision, sending answers below 0.9 confidence to Haiku or Sonnet | 45/47 (96%) | 6% of questions leave the machine |
kev-vision, below 0.995 to Sonnet | 46/47 (98%) | 15% leave the machine |
The local and cloud models fail in opposite ways (the cloud models guess when the image cannot answer; kev-vision hedges on false claims), and kev-vision's confidence drops on its mistakes, which is why escalating only its uncertain answers works. The thresholds were chosen on these same 47 questions, so treat the combined rows as optimistic until the set grows. kev-vision needs ~3.7 GB of GPU memory (4.5 GB peak) and does not fit alongside kev on an 8 GB card.
Run the benchmark (kev-vision needs kev's venv, which has torch):
python3 tools/vision_bench/run.py pi:anthropic/claude-haiku-4-5 pi:anthropic/claude-sonnet-4-6
~/Work/kev/.venv/bin/python tools/vision_bench/run.py kev-visionNext: a kev-vision server and a hyprcu tool that asks it about the latest screenshot or change crop, escalating uncertain answers.
What's kept from upstream
module | what |
| raw |
| AT-SPI tree. hyprcu: in-process D-Bus (jeepney) + Collection fast path instead of one |
| Hyprland socket2 listener → |
| IPC layer; handles both hyprlang and Lua-dispatch Hyprland |
| as upstream, except typing/combos go through our virtual keyboard (wtype only as a fallback) |
| the MCP server; hyprcu adds window description resolution, |
What's replaced with no-op stubs
trust.py, safety.py, skill.py — same public names, every guard passes,
no log call does anything. journal.py
is NOT a stub: it is the training log (see below).
What's removed
waybar/, packaging/, and upstream's init wizard, journal/replay
commands and skill installer.
CLI (shell verbs) — kept
Every MCP tool is also a shell verb (the installed hyprcu binary starts
in ~20 ms cold; a few hundred ms under uv run) for agents that only
have bash (Codex, Claude Code in a terminal, scripts):
hyprcu # no args = MCP server on stdio
hyprcu desktop # one line per monitor/workspace/window
hyprcu screenshot --window 0x… # prints path + {"geometry","scale",…}
hyprcu hypr focus_window 0x…
hyprcu pointer click 800 60
hyprcu keyboard type "hello" --window 0x…
hyprcu sequence '[{"op":"keyboard","action":"type","text":"x"},…]'
hyprcu doctor # check binaries + session
hyprcu stop # kill any running server/verbExit codes: 0 delivered · 1 error · 2 usage · 3 refused (read-only) · 4 no result.
Tests
uv run pytest tests/ --ignore=tests/test_e2e.py → 362 passed, 53 xfailed.
The xfails are enumerated in tests/removed_guard_tests.txt; each asserts
that a guard refuses something. They are strict, so a guard silently
coming back would fail the suite.
Run
uv sync
uv run python -m hyprcu # MCP server on stdioTools: desktop, screenshot, zoom, ui, marks, binds, wait_for, pointer, keyboard, click_ui, hypr, launch, use_bind, sequence.
Measured on this machine (2026-09-20)
launch foot→ 167ms, returns address (event-driven, no sleep)sequencetype+enter → 627ms, one round-tripclose_window→ 3ms, confirms destroy eventuion Strata: 1 element (sidebar toggle only — custom-drawn list)uion Slack: 0 elements (Electron, needs--force-renderer-accessibility)
The a11y tools are only as good as the apps' trees. On this desktop, today,
they're mostly empty. sequence, launch, wait_for and the unified
pointer are the real gains.
What hyprcu adds (the parts that are ours)
pick.py — natural-language window targeting. Every window argument
(hypr, pointer, keyboard, screenshot, ui, click_ui…) accepts an address, a
class/title substring, or a description. Resolution: exact address → unique
substring (free) → kev choice (~200ms, local). Ambiguous substrings are also
tie-broken by kev. Below KEV_GATE (0.5) it fails with "the app may not be
open — check desktop() or launch it", which has been right every time so far.
Results carry [kev: 99% in 229ms] so you can see when it was used.
hyprcu hypr focus_window "the file browser"
hyprcu keyboard type "hello" --window "the shell on workspace 2"Choosers: Jev (default) or kev. By default these choices go to TypeSafe's
Jev (TYPESAFE_API_KEY from the environment) with an explicit "none of these"
option; this sends window titles and control names to TypeSafe (see
Requires a TypeSafe API key).
HYPRCU_CHOOSER=kev uses the local model with a probability gate instead. tools/choice_bench.py (29 synthetic queries, nothing from the live
desktop): Jev 28/29 at ~0.6 s; kev-4b 20-25/29 at 1.7-2.8 s on a laptop GPU
(kev slows as the option list grows).
click_ui by description. When no control is named what you asked,
click_ui treats it as a description and the chooser picks among the
window's clickable controls (browser controls are labelled web page vs
toolbar), or refuses when none fits:
hyprcu click_ui "submit the form" --window "the web browser"
# clicked button 'Send message' ... [jev: 92% in 294ms]See only what changed: --then changes. Every acting verb (pointer,
keyboard, click_ui, hypr, use_bind, sequence) can return its own effect.
hyprcu grabs a raw frame of the focused monitor before the action, waits
until the screen has been still for 0.3 s (up to 2 s), and compares:
nothing changed → one line of text and no image; a small change → one crop
per changed area; most of the screen → the whole screenshot. The mouse
pointer is masked out, so moving it is not a change. Reading an image is the
slow, costly part of computer use, and most effects are small:
hyprcu pointer click 638 696 --then changes
# changes: 4 area(s) changed on TEST (3.2% of it, settled after 1735 ms): ...
hyprcu pointer move 1400 950 --then changes
# changes: nothing changed on TEST (settled after 408 ms)Measured on the nested test session (1600x1000): a raw frame is ~15 ms, a typical call 0.5–0.9 s including the wait; typing a command in foot returned two crops of 0.7% of the screen instead of a full screenshot.
Window-relative clicks. pointer takes window + x_pct/y_pct
(0.0–1.0); the window is focused first and the fraction is mapped to its
current geometry, so the click survives moves and resizes. CLI:
hyprcu pointer click --in "Strata" --at 0.053 0.23.
journal.py — training log. Acting tools append one JSONL row to
~/.local/share/hyprcu/actions.jsonl (HYPRCU_LOG=0 disables): args,
the window list at the time, result, and — when kev chose — query and
probability. Rows are unlabelled; a correct field is meant to be added
later before anything is trained on them.
With HYPRCU_CHOOSER=kev, requires a Jev-compatible server at KEV_URL (default kev-4b on :8009).
The canonical launcher is the systemd user unit kev.service
(systemctl --user start/stop/status kev; runs on CUDA in NF4, offline-safe,
auto-starts at login) — a manual fallback is tools/kev-serve.sh. Without
one, substring and address targeting still work; descriptions fail with an
explicit message.
No built-in judgement (2026-09-20)
hyprcu does what it's asked. Checks and controls belong in the caller, not here. Removed from upstream's acting tools, beyond the trust layer:
was | now |
| runs every step; |
| effectively unbounded |
| caller's value |
| caller's value |
The event stream still serves wait_for steps inside a sequence (so an
event between steps isn't missed) — that's plumbing, not a guard.
tests/test_no_guards.py pins this contract.
Trust/approval language audit (2026-09-20)
Verified against the live MCP wire, not just the source:
No code path can raise a refusal:
grep "raise TrustError"→ 0 hits outside the stub's class definition.Server
instructions(what the model reads at connect): 0 gating terms.Tool descriptions: the three
allow_auth=true overrides the refusal…sentences, "panic-kill guarantees", and "Refused while HYPRCU_CONFINE" removed. Remaining "confirm" is "screenshot to confirm the click worked".allow_authis still an accepted boolean on pointer/keyboard/click_ui — it flows only into no-op stubs, FastMCP emits it with no description, and 8 upstream tests pass it. Left in to keep server.py logic identical to upstream; harmless.HYPRCU_READONLY=1still works as an opt-in "observe only" mode (hides acting tools). Nothing sets it by default.
Lineage
hyprcu is a fork of hypruse by
IlyasKhallouki (MIT). The Wayland/Hyprland primitives — wire.py (virtual
pointer), a11y.py, events.py, hyprctl.py, input.py, screenshot.py
and the MCP server.py — are his work, kept close to upstream so fixes can be
merged. hyprcu removes the trust/journal/safety layers and every built-in
refusal, and adds kev-based natural-language targeting, window-relative
coordinates, and a training log. Upstream reference checkout: ~/Work/hypruse.
Available Tools
14 toolsbindsA
The user's own Hyprland keybinds: combo, action, arg, and a
description when the config provides one. This is how the desktop's
owner drives it: to perform one of these workflows, call use_bind
with the combo (it runs the bound action). An action of lua means the
bind is a closure in a Lua Hyprland config, which nothing can run from
outside: read its description and do the same thing with hypr or
launch. NOTE: the keyboard tool canNOT trigger these compositor
binds (synthetic keys reach apps, not Hyprland's bind matcher), so do
not try to press them.
Inputs: no named parameters. Returns: result
click_uiA
Click a control by its accessible NAME, or by a mark number from
the last marks capture, in ONE call: the exact coordinate comes from
the accessibility tree, the window is focused first, and the click goes
through the real pointer (visible cursor),
so no screenshot and no pixel estimation is spent. Pass exactly one of
name (matched against window's controls, exact accessible name
preferred, substring otherwise) or mark. An ambiguous name returns
the candidates instead of guessing: disambiguate with index (0-based
into that list) or a more specific name. Falls back with a note when
the app exposes no tree (use screenshot + zoom + pointer then).
then ('desktop'|'screenshot'|'ui'|'none') appends the result;
'ui' shows the click's effect on the controls in the same call.
Inputs: mark, name, then, index, +4 more. Returns: result
desktopA
Semantic desktop snapshot: monitors, workspaces, windows (address,
class, title, at + size in global coords), active window, cursor,
and layers: launchers (wofi/rofi), bars, notification popups, and
on-screen keyboards are NOT windows and appear only there, with a
best-effort kind and global geometry you can screenshot by region or
click into. A listed layer is one the compositor tracks, not one you
can see: it may be transparent or dormant, so screenshot when
visibility matters. Call first; act on the addresses it returns.
Inputs: no named parameters. Returns: object
hyprA
Window/workspace ops over IPC (instant, no vision).
action='workspace' (workspace: number/name/'special:name') |
'focus_window' (target: address) | 'move_window' (target + workspace,
silent) | 'close_window' (target) | 'fullscreen' (target?) |
'toggle_floating' (target?) | 'dpms_on' / 'dpms_off' (display power;
screenshots cannot capture a dark display — desktop() reports it and
screenshot errors with a pointer here). then ('desktop'|'screenshot'|'ui'|'none')
appends the result to this call.
Inputs: action (required), then, target, workspace. Returns: result
keyboardA
Keyboard to the focused app. action='type' (text, unicode-safe) |
'key' (keys combo: 'ctrl+shift+t', 'esc', 'F5'; aliases
enter/esc/tab/backspace/pgup/pgdn/arrows, else XKB keysyms). Pass
window (an address from desktop) to focus that window first, so
keystrokes land in the intended app rather than whatever currently
holds focus. This drives shortcuts the focused application handles
(ctrl+t, ctrl+l). It does NOT trigger Hyprland's own keybinds
(super+...): those go through use_bind, and workspace/window actions
through hypr. then ('desktop'|'screenshot'|'ui'|'none') appends the
result to this call.
Inputs: action (required), keys, text, then, +2 more. Returns: result
launchA
Run command via Hyprland exec. Optional workspace placement
(silent, works even for single-instance apps like browsers, whose
window gets moved after it appears) and wait_s (1-30, default 8;
raise for slow apps). Returns the new window's
address/class/title/workspace, or a timeout note.
Inputs: command (required), wait_s, workspace. Returns: result
marksA
Set-of-Marks capture: a screenshot of the window WITH its accessible
controls drawn as numbered red marks, plus a JSON legend mapping each
number to the control's role, name, current value, and exact global
click point. One glance replaces the estimate-zoom-estimate loop for
every control the accessibility tree knows: read the number off the
image and call click_ui(mark=N) (or pointer at the legend's x,y).
window is an address from desktop (default: focused); name filters
the marked controls. Falls back to the plain legend when ImageMagick is
not installed, and to a fall-back-to-vision note when the app exposes
no tree (then use screenshot + zoom).
Inputs: name, window. Returns: result
pointerA
Mouse. action='move' (x,y) | 'click' (optional x,y first; button left/right/middle; double=true) | 'drag' (x,y → to_x,to_y holding button) | 'scroll' (scroll_dy notches, positive = content down; optional x,y first).
Coordinates are GLOBAL logical pixels by default. Pass window (address,
class/title substring, or a description like "the file browser") plus
x_pct/y_pct in 0.0–1.0 to click RELATIVE to that window instead: (0.5,0.5)
is its centre, (0.1,0.05) near its top-left. The window is focused first.
This is the robust form — it survives the window moving or resizing.
then appends the result to this call so you skip a round-trip: 'desktop'
a fresh snapshot, 'screenshot' a stable capture, 'ui' the focused window's
elements with current values, 'none' (default) nothing.
Inputs: action (required), x, y, then, +12 more. Returns: result
screenshotA
Capture the focused monitor, a window (window: "active" or an
address from desktop, cheapest for reading one app), or a region
"x,y,WxH". Returns the image (or a file path to read) + JSON metadata
with geometry/scale for pixel→global mapping. scale 0.1-1.0:
optional deliberate downscale, usually leave unset. stable=true
waits (up to 2s) until two consecutive frames match, so a capture
right after an action is not taken mid-animation; metadata gains
stable. Captures are fast JPEG by default; lossless=true returns
PNG for pixel-exact work. Before clicking a small control, follow with
zoom at the estimated point.
Inputs: scale, region, stable, window, +1 more. Returns: result
sequenceA
Run an ordered list of actions in ONE call, so a click/type/enter
micro-sequence costs one round-trip instead of several. Each step is
{"op": "pointer"|"keyboard"|"click_ui"|"hypr"|"wait_for", ...that tool's
args}, e.g. [{"op":"pointer","action":"click","x":800,"y":60},
{"op":"keyboard","action":"type","text":"hello","window":"0x.."},
{"op":"keyboard","action":"key","keys":"enter"}]. Every step runs, in
order, regardless of what the desktop does in between; a step that
raises stops the run and reports which one. To type into a specific
window reliably give that keyboard step a window= (address, substring
or description; it focuses first). Opt-in stop_on_change=true aborts
between steps if a window opens/closes/moves or the workspace switches
unexpectedly — useful when a dialog might appear, but it also fires on
changes you intended (Ctrl+T opening a tab), so it is off by default.
then observes the final state ('desktop' default, 'screenshot', 'ui',
'none').
Inputs: steps (required), then, stop_on_change. Returns: result
uiA
Read a window's accessibility tree (AT-SPI) and return its elements
with GLOBAL click points, so you can target a control by NAME with no
screenshot and no pixel guessing. window is an address from desktop
(default: the focused window). name filters to elements whose
accessible name contains it (case-insensitive); actionable (default)
keeps only interactive roles (buttons, entries, menu items, ...).
Returns [{role, name, x, y, clickable}] where x,y is the click point:
focus the window, then click it with pointer (the window must be
visible to receive the click), or do both in one call with click_ui.
Controls that carry a CURRENT VALUE also
report it: value (text typed into an entry, or a slider/spinner
number), percent for a slider's position, checked for a box or
toggle. Password fields never report contents, and many dropdowns
expose no value at all, so read the screen with screenshot when a
rendered value matters. Not every app exposes a tree (terminals, and
Electron/Chrome without --force-renderer-accessibility, expose little
or nothing); when it does not, fall back to screenshot + zoom.
Inputs: name, window, actionable. Returns: result
use_bindA
Run one of the user's own Hyprland keybinds by its combo (from the
binds tool), e.g. 'SUPER+F'. This executes the bound action directly
(the only reliable way: synthetic keypresses do not trigger compositor
binds). Use it to drive the owner's configured workflows: launchers,
layout shortcuts, scratchpads. then ('desktop'|'screenshot'|'ui'|'none')
appends the result to this call (handy after a launcher bind).
Inputs: combo (required), then. Returns: result
wait_forA
Block until a desktop event happens (real compositor events, not
polling). event: 'window_open' | 'window_close' | 'workspace' |
'title_change' | 'layer_open' | 'layer_close' (layer-shell surfaces:
launchers, notification popups; match on the namespace, e.g. 'wofi') |
'urgent' (a window demands attention) | 'screencast' (screen sharing
started/stopped). match: optional case-insensitive substring filter
over the event's fields (class/title/workspace name/address/namespace).
timeout_s 1-60, default 10. Returns the event payload, or a timeout
note; a filtered wait whose condition ALREADY holds (the window is
already gone, the workspace already active, the layer already mapped)
returns instantly with already: true instead of missing an event
that fired before it could subscribe. Use it after actions with
delayed effects: app startups, page loads that change a window title,
a launcher bind that pops a layer.
Inputs: event (required), match, timeout_s. Returns: result
zoomA
Native-resolution re-capture around a point: the precision step of
the coarse-to-fine loop. Screenshot first, estimate the target's global
x,y, zoom there, re-estimate on the zoomed image (scale ~1.0, so
global = geometry[:2] + image_pixel), then click. size "WxH" in
logical pixels (default 480x360) is clamped to the screen; window
(an address from desktop) clamps to that window instead. The metadata
echoes the requested point back as point; stable=true waits for
the frame to settle first; lossless=true returns PNG.
Inputs: x (required), y (required), size, stable, +2 more. Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 14 tools
Each tool targets a distinct capability: hypr for compositor window/workspace ops, desktop for state snapshots, screenshot/zoom for capture, ui/marks/click_ui for accessibility-driven interaction, pointer/keyboard for raw input, binds/use_bind for user-defined shortcuts, wait_for for events, launch for starting apps, and sequence for batching. Overlaps are minimal and clearly delineated (e.g., click_ui vs pointer).
Names are a mix of single-word nouns (desktop, ui, marks, binds, pointer, keyboard, sequence) and verbs (launch, zoom, screenshot), plus compound verb-noun forms (wait_for, click_ui, use_bind). The inconsistency is not chaotic but lacks a uniform pattern like verb_noun throughout.
14 tools is well-scoped for a desktop automation server covering input, capture, state, events, launching, and UI interaction. Each tool has a clear role with no redundancy, and the count is neither thin nor bloated.
The surface covers the full lifecycle of desktop automation: observing (desktop, screenshot, zoom), interacting (pointer, keyboard, click_ui, hypr), waiting for changes (wait_for), launching (launch), and executing user workflows (binds, use_bind). No obvious gaps for the stated purpose of controlling Hyprland desktops.
Related MCP Connectors
Persistent memory, knowledge graph, task management and more for AI agents via MCP.
Self-hosted AI workspace: semantic RAG document search, long-term memory, and AI chat via MCP.
Connect ChatGPT, Claude, Cursor or VS Code to your Windows PC: files, terminal, browser, apps.
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to interact with yo…
Related MCP Servers
- AlicenseAqualityAmaintenanceAn MCP server for Hyprland that enables AI agents to control workspaces, windows, mouse, keyboard, and take screenshots on a Wayland desktop.14618 PyPI50MIT
- AlicenseAqualityCmaintenanceCross-platform desktop automation MCP server that lets AI agents capture screenshots, run OCR with UI-element classification, control mouse/keyboard, and launch programs on Linux, macOS, and Windows.201MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to automate a Linux desktop natively on Wayland, with 68 tools for screen capture, input simulation, browser automation, OCR, accessibility, media, audio, clipboard, notifications, filesystem, system, memory, and network operations through the MCP protocol.MIT
- AlicenseNot gradedqualityCmaintenanceEnables an AI agent to see and control a Linux desktop via Wayland/Hyprland, providing screenshots, structured desktop state, pointer/keyboard input, semantic window/workspace tools, and a policy engine for safe execution.Apache 2.0