Repository navigation
Releases: jslatten/claude-linux-mcp
Release list
v1.2.0 — Installs what it needs
Two things this release does that the last one did not: it installs what it is
missing, and it clicks.
Clicks were never clicking
click was registered against _click_context, the helper that describes what
sits under a point — it had been inserted between @server.tool("click", ...)
and the function that decorator was meant to bind, so the server bound the
diagnostic as the handler and tool_click was never registered at all.
The failure was silent and convincing. _click_context takes (x, y), exactly
what the schema sends, and returns on "Firefox" (firefox). Every click
reported the window it would have hit, and none of them pressed a button. Only
a screenshot afterwards would have contradicted the text.
Fixed, and confirmed against the registry rather than by eye:
server.tools["click"].handler is tool_click again.
Missing dependencies now explain themselves, then install
Before: a tool that failed for want of an installation said so in the language
of whatever failed — a Wayland interface name, an errno on /dev/uinput — and
the setup tool could only finish the parts needing no root, because it ran
setup.sh --no-sudo. Everything else came back as commands to paste.
Now every tool with a dependency is wrapped by _needs(component). When one
fails it asks whether a missing component is the reason, and if so the failure
carries the original error plus what to tell you, what is about to be
installed, and whether a password dialog is coming:
InputError: /dev/uinput: Permission denied
mouse and keyboard control is not available on this desktop yet: /dev/uinput
is not writable, so mouse and keyboard control is unavailable.
The extension can install this itself. Before calling anything, tell the user,
in your own words:
mouse and keyboard control is not set up on this machine yet. I will
install what is missing - your desktop will ask for your password, and
that prompt is this installation.
The check runs after the attempt, not before it. The setup report is
deliberately conservative — it calls input unavailable whenever /dev/uinput
is unwritable, even on an X11 session where xdotool would have served
perfectly well — so refusing up front on that basis would have broken working
desktops. Reacting to a failure that actually happened cannot.
Root steps happen, through the desktop's own prompt
provision.apply() runs setup.sh --gui-sudo, which reaches root through
pkexec. That is the only route available: the server has no controlling
terminal, so sudo has nowhere to ask for a password, while pkexec hands the
prompt to the session's polkit agent.
An unexplained polkit dialog raised by a background process is
indistinguishable from malware, so nothing runs until you have been told twice
— once by Claude, from the text the failure handed it, and once by a desktop
notification posted immediately before the run. Declining is a normal outcome
rather than an error: the command lands in manual_steps for you to run
yourself. The new Install missing dependencies automatically setting
(LCU_ALLOW_INSTALL) turns the path off entirely, leaving the server to report
and explain and never install.
Packages go through the new scripts/install-packages.sh in a single root
invocation — pkexec raises a separate dialog per command, so installing five
packages one at a time would have meant five dialogs. It keeps the old
best-effort behaviour: a batch install first, then one at a time so a package
name that does not exist on your release cannot take the rest down with it.
Two bugs only pkexec exposed
install-uinput-rule.sh derived its target from ${SUDO_USER:-$USER}. Under
pkexec there is no SUDO_USER and USER is already root, so it would have
added root to the input group and left the real user without access. It
now takes the user as an argument, falls back to PKEXEC_UID, and refuses to
configure root.
apply() passed the server's stdin — the JSON-RPC stream — to setup.sh,
where a password prompt would have consumed the client's messages. It is now
/dev/null.
GNOME: listing works, control needs the extension
Mutter implements ext_foreign_toplevel_list_v1, so a GNOME Wayland session
resolves to the foreign-toplevel backend and lists windows without the helper
extension. That protocol carries no requests, and it had taken the install
instructions off the path that needs them most: every GNOME user is in exactly
this state between running setup.sh and logging back in.
activate, close, set_state, and move_resize now check up front and
raise the helper's install steps instead of a bare protocol error. Every other
route into Mutter is closed — the Shell's Eval endpoint has been disabled
since GNOME 41, and Introspect.GetWindows is restricted to an allowlist of
XDG portals.
scripts/test-gnome-messaging.py covers this from KDE or X11, where it is
otherwise unreachable.
Also
status()is cached for 30 seconds and hands out copies; it is now on the
path of every tool call with a dependency, and re-runninggdbusand
gsettingsbefore each click would cost more than the click.- A desktop notification at startup names what is unavailable, so an incomplete
install is not something you first learn about mid-task.
Install
Download linux-computer-use-1.2.0.mcpb and drag it onto Claude Desktop's
Settings → Extensions pane. Then ask Claude to check its setup.
v1.1.1 — Corrected selftest
A verification fix. Runtime behaviour is unchanged from v1.1.0 — upgrade only to get the corrected selftest.
The selftest was measuring a bug that did not exist
scripts/selftest.py reported clicks landing up to 217px off on a fractionally-scaled desktop. That was the check's own arithmetic, not the pointer.
It computed a window origin as target - surface_local and treated any variation as positioning error. Those are different units: targets are framebuffer pixels, while the surface-local coordinates GTK reports are logical pixels. Under fractional scaling the difference drifts with position instead of staying constant. On a 1.15-scaled desktop the predicted spread is (4053-2384) x (1 - 1/1.15) = 217px — exactly what it kept reporting.
Measured against KWin's workspace.cursorPos, which is independent of GTK, the pointer was landing within ±1px at every target across both monitors and the far corner. Confirming the unit mix directly: cursor_logical - gtk_local equalled the window's logical origin (1920, 0) exactly, for every click.
The corrected check
It now fits target = a x local + b by least squares rather than assuming a == 1. The slope recovers the scale, the intercept the window origin, and the residuals are the real positioning error. Fitting rather than asking the compositor keeps it working on any desktop.
On the development machine it now reports:
clicks land on target - 4/5 landed in-window, scale 1.149x1.149, origin (2208, 0), max error 0.0px
Both figures are independently correct — 1.149 against KWin's measured 1.15002, and (2208, 0) is the second monitor's true framebuffer position, recovered purely from click data.
Two flakes fixed alongside
Settling time. The test drove input 0.6s after fullscreening. KWin is still placing the window at that point, which scattered the coordinates and intermittently cost the window keyboard focus — the real cause of the typing check failing at random. Now 1.2s.
Stray keystrokes. The window collects every printable key it sees, so an event arriving during the click phase was compared against the sample and failed it; one run captured 6Hello Claude 123 !@#. The buffer is now cleared immediately before typing, scoping the check to what typing actually produced.
Five consecutive runs pass 6/6.
Install
Download linux-computer-use-1.1.1.mcpb and open it with Claude Desktop, then restart Claude Desktop — the tool list is cached, and the updated tools will not appear without one.
For system setup a bundle cannot do itself (a udev rule for /dev/uinput, or the GNOME Shell extension), ask Claude to "set up computer use", or clone the repo and run ./scripts/setup.sh.
Checksum
0cbe198317cff7e734adaeeb8fbc5afececd2ff5819e79cc16bc7197cac38b1a linux-computer-use-1.1.1.mcpb
v1.1.0 — Cheaper observation, verified clicks
Screenshot, input, window, clipboard, and app-launch control for Claude on Linux desktops.
This release is about token cost. A review of a real session found the action tools already near-optimal while the observation tools dominated; everything below was measured on a 4768x1440 dual-monitor KDE Wayland desktop.
Fixed: crops reported the wrong coordinate space
Capturing a single monitor or region described itself as downscaled "from 4768x1440" and gave no crop origin. For a monitor starting at desktop x=2208, every coordinate read off that image was wrong by 2208px — and the failure looked like a mis-aimed click rather than a bad conversion. Crops now state where they start and how to convert back:
Screenshot 1193x671, downscaled 0.466x from 2560x1440, cropped from desktop (2208, 0).
Desktop coords: x = 2208 + ix/0.466, y = 0 + iy/0.466.
If you used monitor: or region: before, this is the reason to upgrade.
click now reports what it hit
It previously returned success unconditionally, so a click on empty desktop or the wrong monitor read exactly like one that worked, and only a screenshot could tell them apart. It now names the window under the point, resolving overlaps with KWin's stackingOrder, and says when a point is empty desktop. Backends without stacking order report ambiguity rather than naming a window they cannot confirm. Costs about 15 tokens; replaces a 789–1470 token verification screenshot.
Screenshots gained a pixel cap
Clamping only the longest side made cost depend on aspect ratio — the whole desktop fit in 1400x423 (789 image tokens) while one 2560x1440 monitor became 1400x788 (1470). Cropping to a single screen cost nearly twice as much as capturing both, which is backwards. LCU_MAX_IMAGE_PIXELS (default 800,000) applies alongside the dimension limit.
The dimension default stays at 1400. Dropping it to 1000 was tested and rejected: at 1000 a full-desktop capture becomes 1000x302, where terminal text is unreadable, for a saving of only ~390 tokens.
Cheaper listings
| Before | After | |
|---|---|---|
list_applications (default) |
2,661 tok | 634 |
list_windows (filtered) |
568 tok | 79 |
| screenshot, one monitor | 1,470 tok | 1,067 |
list_windows takes query and limit. list_applications defaults to 25 results and omits exec and categories unless detail: "full" — two thirds of the payload, and not needed to launch anything.
Also
get_desktop_info reports focus_policy, because text arriving in the wrong window looks identical whether the cause is focus-follows-mouse, focus stealing prevention, or a click that missed.
Install
Download linux-computer-use-1.1.0.mcpb and open it with Claude Desktop, then restart Claude Desktop — the tool list is cached, and without a restart the updated tools will not appear.
For system setup that a bundle cannot do itself (a udev rule for /dev/uinput, or the GNOME Shell extension), ask Claude to "set up computer use", or clone the repo and run ./scripts/setup.sh.
Verifying
python3 scripts/selftest.py opens a test window and drives the real backends against it. Results depend on whether that window takes keyboard focus, so run it from a terminal you are sitting at and expect the input checks to vary if it does not.
Checksum
bcf9be54a17093fc35d3f7839e4c43544d3881bae40474d7532d42525e986e17 linux-computer-use-1.1.0.mcpb
v1.0.0 — Linux Computer Use
Screenshot, input, window, clipboard, and app-launch control for Claude on Linux desktops.
Install
Download linux-computer-use-1.0.0.mcpb below and open it with Claude Desktop, then restart Claude Desktop so it picks up the tool list.
Some capabilities need system setup that an extension bundle cannot perform on its own — a udev rule for /dev/uinput, or the bundled GNOME Shell extension. To finish those, either ask Claude to "set up computer use" (the setup tool does everything not needing root and reports the rest), or run:
git clone https://github.com/jslatten/claude-linux-mcp.git
cd claude-linux-mcp
./scripts/setup.shThe script detects your distro family, architecture, and every installed desktop, does what it can unattended, and lists anything left that needs a password or a re-login.
What works where
| Screenshots | Input | Window control | |
|---|---|---|---|
| GNOME (Wayland) | portal | uinput / ydotool | bundled Shell extension, needs a re-login |
| GNOME (X11) | X11 tools | xdotool | wmctrl — nothing to install |
| KDE Plasma 5/6 | portal / spectacle | uinput / ydotool | KWin scripting — built in |
| sway, Hyprland | grim | uinput / wtype | compositor IPC |
| river, Wayfire, niri | grim | uinput / wtype | wlroots foreign-toplevel |
| Other X11 | X11 tools | xdotool | wmctrl |
Window control on KDE is a pure-Python D-Bus client driving KWin's scripting engine, so there is no helper binary to compile and x86_64 and aarch64 behave identically.
Verifying
python3 scripts/selftest.py opens a test window and drives the real backends against it. Run it from a terminal you are sitting at — it needs the test window to take keyboard focus, so it will report a typing failure if launched from a background or automation context.
Checksum
bdb44e4a5dd903b042af6418ab150e207301955fac3ae1c22d7d3f090f2866d5 linux-computer-use-1.0.0.mcpb