kwin-mcp-server 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kwin_mcp_server-0.2.0/LICENSE +21 -0
- kwin_mcp_server-0.2.0/PKG-INFO +380 -0
- kwin_mcp_server-0.2.0/README.md +349 -0
- kwin_mcp_server-0.2.0/kwin_bridge/__init__.py +15 -0
- kwin_mcp_server-0.2.0/kwin_bridge/_env.py +61 -0
- kwin_mcp_server-0.2.0/kwin_bridge/a11y.py +394 -0
- kwin_mcp_server-0.2.0/kwin_bridge/atspi_dbus.py +408 -0
- kwin_mcp_server-0.2.0/kwin_bridge/doctor.py +187 -0
- kwin_mcp_server-0.2.0/kwin_bridge/input.py +412 -0
- kwin_mcp_server-0.2.0/kwin_bridge/preflight.py +109 -0
- kwin_mcp_server-0.2.0/kwin_bridge/screenshot.py +190 -0
- kwin_mcp_server-0.2.0/kwin_bridge/server.py +422 -0
- kwin_mcp_server-0.2.0/kwin_bridge/windows.py +201 -0
- kwin_mcp_server-0.2.0/kwin_mcp_server.egg-info/PKG-INFO +380 -0
- kwin_mcp_server-0.2.0/kwin_mcp_server.egg-info/SOURCES.txt +20 -0
- kwin_mcp_server-0.2.0/kwin_mcp_server.egg-info/dependency_links.txt +1 -0
- kwin_mcp_server-0.2.0/kwin_mcp_server.egg-info/entry_points.txt +4 -0
- kwin_mcp_server-0.2.0/kwin_mcp_server.egg-info/requires.txt +9 -0
- kwin_mcp_server-0.2.0/kwin_mcp_server.egg-info/top_level.txt +1 -0
- kwin_mcp_server-0.2.0/pyproject.toml +47 -0
- kwin_mcp_server-0.2.0/setup.cfg +4 -0
- kwin_mcp_server-0.2.0/tests/test_a11y_semantic.py +228 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Samuel Ukpai (Samthesurf)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kwin-mcp-server
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: MCP server that controls native Wayland windows on KDE Plasma (the cua-driver X11 gap, solved). Includes a doctor readiness report, semantic AT-SPI targeting, and an MCP safety contract.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/Samthesurf/kwin-mcp
|
|
7
|
+
Project-URL: Repository, https://github.com/Samthesurf/kwin-mcp
|
|
8
|
+
Project-URL: Issues, https://github.com/Samthesurf/kwin-mcp/issues
|
|
9
|
+
Keywords: mcp,computer-use,kde,plasma,wayland,accessibility,at-spi
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: X11 Applications
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: mcp<2.0,>=1.0
|
|
23
|
+
Requires-Dist: python-uinput>=1.0.1
|
|
24
|
+
Requires-Dist: Pillow>=10.0.0
|
|
25
|
+
Requires-Dist: jeepney>=0.8.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest; extra == "dev"
|
|
28
|
+
Requires-Dist: build; extra == "dev"
|
|
29
|
+
Requires-Dist: twine; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# kwin-mcp
|
|
33
|
+
|
|
34
|
+
An MCP (Model Context Protocol) server that controls **native Wayland windows
|
|
35
|
+
on KDE Plasma** from an AI agent.
|
|
36
|
+
|
|
37
|
+
It does what `cua-driver` cannot on Linux/Wayland: see and drive the real
|
|
38
|
+
desktop. `cua-driver` (trycua) only enumerates X11/XWayland clients, so on a
|
|
39
|
+
KDE Wayland session it sees 1 of ~20 windows. `kwin-mcp` sees all of them.
|
|
40
|
+
|
|
41
|
+
It is built entirely on KDE-native primitives, so it needs no modifications to
|
|
42
|
+
trycua's binary and no root daemon. You point your MCP client (Claude Code,
|
|
43
|
+
Codex, Hermes, etc.) at `server.py` and get the same capabilities cua offers on
|
|
44
|
+
X11: window listing, screenshots, clicks, typing, dragging, key presses, and
|
|
45
|
+
(optionally) AT-SPI element targeting.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Install (about 3 minutes)
|
|
50
|
+
|
|
51
|
+
kwin-mcp targets **KDE Plasma on Wayland**. It is a Python MCP server, so any
|
|
52
|
+
MCP host (Claude Code, Codex, Cursor, Zed, Hermes) can use it.
|
|
53
|
+
|
|
54
|
+
1. **System deps + input permission** (one-time)
|
|
55
|
+
```bash
|
|
56
|
+
sudo pacman -S kdotool spectacle # Arch
|
|
57
|
+
sudo usermod -aG input "$USER" # allow /dev/uinput
|
|
58
|
+
# log out and back in so the new group applies
|
|
59
|
+
```
|
|
60
|
+
Not on Arch? See the Dependencies table below for the per-distro package
|
|
61
|
+
names.
|
|
62
|
+
|
|
63
|
+
2. **Install uv** (once) - powers the one-command launcher
|
|
64
|
+
```bash
|
|
65
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
3. **Wire it into your agent**
|
|
69
|
+
```bash
|
|
70
|
+
git clone https://github.com/Samthesurf/kwin-mcp.git /tmp/kwin-mcp && cd /tmp/kwin-mcp
|
|
71
|
+
./setup.sh hermes # or: claude | codex | cursor | zed
|
|
72
|
+
```
|
|
73
|
+
`setup.sh` runs a preflight, prints exactly what is missing if anything is
|
|
74
|
+
(and stops, never half-wiring), then adds the server to your agent's config.
|
|
75
|
+
Restart the agent and you are done. To be sure it launches the real server
|
|
76
|
+
(uv + build + deps + live window listing), run `./setup.sh verify` and read
|
|
77
|
+
the readiness report. `./setup.sh help` prints usage; `./setup.sh check`
|
|
78
|
+
just runs the preflight without wiring anything.
|
|
79
|
+
|
|
80
|
+
You can also install the Python package directly and run it yourself, e.g.:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
uv tool install --from git+https://github.com/Samthesurf/kwin-mcp kwin-mcp
|
|
84
|
+
kwin-mcp --doctor # print the readiness report
|
|
85
|
+
kwin-mcp # start the stdio MCP server
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
> **Prefer pip/pipx?** kwin-mcp is published to PyPI as `kwin-mcp-server`. Once
|
|
89
|
+
> released there, install is a single command with no git clone:
|
|
90
|
+
>
|
|
91
|
+
> ```bash
|
|
92
|
+
> pipx install kwin-mcp-server # or: uv tool install kwin-mcp-server
|
|
93
|
+
> kwin-mcp --doctor # print the readiness report
|
|
94
|
+
> ```
|
|
95
|
+
>
|
|
96
|
+
> To build + upload a release yourself, run `./scripts/publish.sh` (see that
|
|
97
|
+
> script for the PyPI token / TestPyPI options).
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## What it can do
|
|
102
|
+
|
|
103
|
+
| Tool | Purpose |
|
|
104
|
+
|------|---------|
|
|
105
|
+
| `list_windows` | Enumerate **every** top-level window (native Wayland + XWayland), with UUID, title, class, pid, geometry |
|
|
106
|
+
| `active_window` | Return the currently focused window |
|
|
107
|
+
| `capture` | Screenshot the desktop (`mode=desktop`) or a specific window (`mode=window`, `window_id=...`); crops to exact window bounds |
|
|
108
|
+
| `click` / `double_click` | Click at screen or window-local coordinates, OR target an element by `element_index` or semantic `role`/`name`/`text` |
|
|
109
|
+
| `drag` | Drag between two points (screen or window-local) |
|
|
110
|
+
| `type` | Type a string into the focused target |
|
|
111
|
+
| `press_key` | Press a key, optionally with modifiers (e.g. `["ctrl"]`) |
|
|
112
|
+
| `scroll` | Scroll the wheel up/down |
|
|
113
|
+
| `get_window_state` | AT-SPI accessibility tree for a window (index, role, name, bounds, state flags, actions, editable) |
|
|
114
|
+
| `click_element` | Click an AT-SPI element by index |
|
|
115
|
+
| `perform_action` | Invoke any AT-SPI action on an element (press, activate, toggle, ...) |
|
|
116
|
+
| `set_value` | Write a value to a settable element (text fields, sliders, spinners) |
|
|
117
|
+
| `activate` / `raise` / `minimize` / `close_window` | Window management |
|
|
118
|
+
| `get_cursor_position` | Current pointer location |
|
|
119
|
+
| `health` | Environment/dependency diagnostics |
|
|
120
|
+
| `doctor` | One JSON readiness report (platform, windowing, input, AT-SPI, screenshot, portals, blockers) |
|
|
121
|
+
|
|
122
|
+
Windows are identified by a stable KDE window UUID of the form
|
|
123
|
+
`{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}` (exactly what `kdotool` prints).
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Readiness report (`doctor`) and safety contract
|
|
128
|
+
|
|
129
|
+
### `doctor` / `kwin-mcp-doctor`
|
|
130
|
+
|
|
131
|
+
Run `kwin-mcp --doctor` (or `kwin-mcp-doctor`) to get a single structured JSON
|
|
132
|
+
document describing the desktop, the windowing backend (with a *live* window
|
|
133
|
+
list probe), the input path, AT-SPI, the screenshot path, and XDG portal
|
|
134
|
+
availability. It ends with a `readiness` summary carrying explicit `blockers`
|
|
135
|
+
and a `recommended_next_step`, so an MCP host or a human can render one report
|
|
136
|
+
instead of parsing prose:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
kwin-mcp --doctor | jq .readiness
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The same report is exposed as the `doctor` MCP tool.
|
|
143
|
+
|
|
144
|
+
### MCP safety annotations
|
|
145
|
+
|
|
146
|
+
Since v0.2 every tool carries an MCP `ToolAnnotations` so hosts can warn before
|
|
147
|
+
invoking a mutating tool:
|
|
148
|
+
|
|
149
|
+
| Class | Tools | Contract |
|
|
150
|
+
|-------|-------|----------|
|
|
151
|
+
| Read-only observation | `list_windows`, `active_window`, `get_window_state`, `get_cursor_position`, `health`, `doctor` | `readOnlyHint=true` |
|
|
152
|
+
| UI-state mutators | `capture`, `activate`, `raise_window`, `minimize`, `scroll` | `readOnlyHint=false`, `destructiveHint=false` |
|
|
153
|
+
| Desktop-action mutators | `click`, `click_element`, `drag`, `type_text`, `press_key`, `perform_action`, `set_value`, `close_window` | `destructiveHint=true` (+ `openWorldHint=true`) |
|
|
154
|
+
|
|
155
|
+
Annotations are safety hints, not an authorization system. Treat any call that
|
|
156
|
+
could submit, delete, send, or purchase as requiring user approval.
|
|
157
|
+
|
|
158
|
+
## Dependencies
|
|
159
|
+
|
|
160
|
+
### System packages (must be installed on the machine)
|
|
161
|
+
These are the KDE/Wayland tools the server shells out to. Install with your
|
|
162
|
+
distro's package manager.
|
|
163
|
+
|
|
164
|
+
| Tool | Package (Arch) | Package (Debian/Ubuntu) | Used for |
|
|
165
|
+
|------|----------------|--------------------------|----------|
|
|
166
|
+
| `kdotool` | `kdotool` (AUR) | `kdotool` (build from source) | Window enumeration, geometry, focus |
|
|
167
|
+
| `spectacle` | `spectacle` | `kde-spectacle` | Screen capture |
|
|
168
|
+
| `ydotool` | `ydotool` | `ydotool` | (Optional) alternative input backend reference |
|
|
169
|
+
| `grim` | `grim` | `grim` | (Optional) future per-output capture |
|
|
170
|
+
|
|
171
|
+
On Arch this machine already had `kdotool`, `spectacle`, `grim`, `ydotool`,
|
|
172
|
+
`slurp`, and `busctl` available.
|
|
173
|
+
|
|
174
|
+
### Kernel / group requirements (input)
|
|
175
|
+
Synthetic input is sent through a virtual device on `/dev/uinput`. You must:
|
|
176
|
+
|
|
177
|
+
1. Be a member of the `input` group:
|
|
178
|
+
```bash
|
|
179
|
+
groups | grep -w input || sudo usermod -aG input "$USER"
|
|
180
|
+
# then log out and back in
|
|
181
|
+
```
|
|
182
|
+
2. Have write access to `/dev/uinput` (group `input` owns it:
|
|
183
|
+
`crw-rw---- root input`). No root daemon (`ydotoold`) is required because
|
|
184
|
+
`python-uinput` opens the device directly as a group member.
|
|
185
|
+
|
|
186
|
+
Verify with:
|
|
187
|
+
```bash
|
|
188
|
+
ls -l /dev/uinput # should show group 'input' with rw
|
|
189
|
+
id -nG | tr ' ' '\n' | grep -x input # should print 'input'
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Python packages
|
|
193
|
+
```bash
|
|
194
|
+
python -m venv .venv
|
|
195
|
+
. .venv/bin/activate
|
|
196
|
+
pip install -r requirements.txt
|
|
197
|
+
# AT-SPI element/action/value targeting + semantic clicks work out of the box:
|
|
198
|
+
# kwin-mcp talks to AT-SPI directly over D-Bus via jeepney (already a
|
|
199
|
+
# dependency), so no pyatspi is required. On distros where the legacy pyatspi
|
|
200
|
+
# module happens to be installed, it is used as a fallback backend.
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Installed and verified on this build: `mcp 1.28.1`, `python-uinput 1.0.1`,
|
|
204
|
+
`Pillow 12.3.0` (Python 3.14).
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Running
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
. .venv/bin/activate
|
|
212
|
+
|
|
213
|
+
# dependency preflight (also run automatically by setup.sh)
|
|
214
|
+
python server.py --check
|
|
215
|
+
|
|
216
|
+
# JSON readiness report
|
|
217
|
+
python server.py --doctor
|
|
218
|
+
|
|
219
|
+
# stdio MCP server (for Claude/Codex/Hermes MCP clients)
|
|
220
|
+
python server.py
|
|
221
|
+
|
|
222
|
+
# or via the convenience wrapper
|
|
223
|
+
python run.py
|
|
224
|
+
|
|
225
|
+
# Streamable HTTP transport on 127.0.0.1:8080
|
|
226
|
+
python server.py --http 8080
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The smoothest path, however, is the one-command `uvx` setup described in the
|
|
230
|
+
next section, which needs no local venv at all.
|
|
231
|
+
|
|
232
|
+
### Wiring into an MCP client (one command)
|
|
233
|
+
|
|
234
|
+
The recommended way is `uvx`, the Python equivalent of `npx`: it downloads and
|
|
235
|
+
runs the server on first use, then caches it. No clone, no venv, no manual
|
|
236
|
+
install. After `uvx` runs once, the agent just launches
|
|
237
|
+
`uvx --from git+https://github.com/Samthesurf/kwin-mcp kwin-mcp`.
|
|
238
|
+
|
|
239
|
+
**Automatic (recommended):** run the setup script, which checks dependencies
|
|
240
|
+
and injects the correct config into your agent.
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
git clone https://github.com/Samthesurf/kwin-mcp.git /tmp/kwin-mcp && cd /tmp/kwin-mcp
|
|
244
|
+
./setup.sh hermes # or: claude | codex | cursor | zed | check
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
`setup.sh` runs a preflight first. If a system dependency is missing it prints
|
|
248
|
+
exactly what to install (e.g. `sudo pacman -S kdotool`) and stops, so you never
|
|
249
|
+
end up with a half-wired, broken server. If all checks pass it writes the
|
|
250
|
+
`uvx --from ... kwin-mcp` entry into the chosen agent's config.
|
|
251
|
+
|
|
252
|
+
**Manual:** point the client at the `uvx` launcher. Example
|
|
253
|
+
(`mcp-config.example.json`):
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
{
|
|
257
|
+
"mcpServers": {
|
|
258
|
+
"kwin-mcp": {
|
|
259
|
+
"command": "uvx",
|
|
260
|
+
"args": ["--from", "git+https://github.com/Samthesurf/kwin-mcp", "kwin-mcp"]
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
- **Hermes**: `./setup.sh hermes` writes it under `mcp_servers` in
|
|
267
|
+
`~/.hermes/config.yaml`, or paste the JSON there. Restart Hermes to load it.
|
|
268
|
+
This replaces `cua-driver` for the `computer_use` toolset on a Wayland box.
|
|
269
|
+
- **Claude Code**: `claude mcp add kwin-mcp -- uvx --from git+https://github.com/Samthesurf/kwin-mcp kwin-mcp`
|
|
270
|
+
- **Codex / Cursor / Zed**: `./setup.sh codex|cursor|zed`, or paste the JSON
|
|
271
|
+
into their MCP config file.
|
|
272
|
+
|
|
273
|
+
The server is self-sufficient about its environment: when an MCP client does
|
|
274
|
+
not forward `DBUS_SESSION_BUS_ADDRESS` / `WAYLAND_DISPLAY` / `DISPLAY` /
|
|
275
|
+
`XDG_RUNTIME_DIR`, the server discovers the correct session values from
|
|
276
|
+
`/run/user/<uid>/` so `kdotool` and `spectacle` always work.
|
|
277
|
+
|
|
278
|
+
No API keys, no network calls, no cloud. Everything runs locally against your
|
|
279
|
+
compositor.
|
|
280
|
+
|
|
281
|
+
### Running from a local checkout (alternative)
|
|
282
|
+
|
|
283
|
+
If you prefer a local venv instead of `uvx`:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
python -m venv .venv && . .venv/bin/activate
|
|
287
|
+
pip install -r requirements.txt
|
|
288
|
+
python server.py # stdio MCP server
|
|
289
|
+
python server.py --check # dependency preflight
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## How it works (and the Wayland caveats)
|
|
295
|
+
|
|
296
|
+
On Wayland there is no X server between apps and the compositor, so input
|
|
297
|
+
cannot be injected "into a specific window" the way cua-driver does on X11.
|
|
298
|
+
The bridge follows a **focus-then-inject** model:
|
|
299
|
+
|
|
300
|
+
1. `kdotool windowactivate <uuid>` raises and focuses the target window.
|
|
301
|
+
2. The virtual pointer (a `python-uinput` device) is moved to the target
|
|
302
|
+
coordinate. Because the compositor applies mouse acceleration and uinput
|
|
303
|
+
only emits *relative* motion, movement is **closed-loop**: read the real
|
|
304
|
+
cursor, emit a bounded delta, re-read, repeat until within ~3 px. This makes
|
|
305
|
+
absolute positioning deterministic.
|
|
306
|
+
3. The click / key / drag is emitted on the now-focused window.
|
|
307
|
+
|
|
308
|
+
What this costs versus X11 (inherent to Wayland, not a bug):
|
|
309
|
+
|
|
310
|
+
- **No background targeting.** The window must be focused first; the real
|
|
311
|
+
cursor moves. It is not invisible the way background X11 input can be.
|
|
312
|
+
- **Single cursor.** Parallel multi-pointer drags (cua's `parallel_mouse_drag`)
|
|
313
|
+
are not available on Wayland.
|
|
314
|
+
- **Secure-input surfaces** (some password fields, the lock screen) may reject
|
|
315
|
+
synthetic input.
|
|
316
|
+
- **Small focus race.** Between focusing and injecting there is a brief window
|
|
317
|
+
where focus could shift; the code waits ~250 ms after activation.
|
|
318
|
+
|
|
319
|
+
Screenshots use `spectacle` in background/non-interactive mode. On KDE Wayland
|
|
320
|
+
`--background` can occasionally race the compositor and capture the lock-screen
|
|
321
|
+
splash instead of the live desktop; the capture path adds a settle delay and a
|
|
322
|
+
variance-based validation that retries up to 3 times, so the returned frame is
|
|
323
|
+
always the real desktop.
|
|
324
|
+
|
|
325
|
+
AT-SPI (`get_window_state`, `click_element`, `perform_action`, `set_value`,
|
|
326
|
+
semantic clicks) works for GTK/Qt/KDE apps that expose an accessibility tree.
|
|
327
|
+
It talks to AT-SPI directly over D-Bus (via `jeepney`, a pure-Python client),
|
|
328
|
+
so it needs no `pyatspi` and works on Arch; the legacy `pyatspi` module is used
|
|
329
|
+
only as a fallback if present. It degrades gracefully to coordinate input when
|
|
330
|
+
no AT-SPI backend is available.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Project layout
|
|
335
|
+
|
|
336
|
+
```
|
|
337
|
+
kwin-mcp/
|
|
338
|
+
├── server.py # MCP server (FastMCP) exposing all tools
|
|
339
|
+
├── run.py # convenience entry point
|
|
340
|
+
├── requirements.txt
|
|
341
|
+
├── pyproject.toml
|
|
342
|
+
├── mcp-config.example.json
|
|
343
|
+
├── README.md
|
|
344
|
+
└── kwin_bridge/
|
|
345
|
+
├── __init__.py
|
|
346
|
+
├── windows.py # kdotool wrapper: enumerate/geometry/focus/close
|
|
347
|
+
├── screenshot.py # spectacle wrapper + crop + retry/validate
|
|
348
|
+
├── input.py # /dev/uinput virtual pointer+keyboard, closed-loop move
|
|
349
|
+
├── a11y.py # AT-SPI front-end (semantic resolve / action / value)
|
|
350
|
+
├── atspi_dbus.py # pure-D-Bus AT-SPI backend (jeepney, no pyatspi)
|
|
351
|
+
├── doctor.py # structured JSON readiness report
|
|
352
|
+
└── preflight.py # actionable dependency check
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Testing
|
|
358
|
+
|
|
359
|
+
A quick smoke test against the live desktop:
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
. .venv/bin/activate
|
|
363
|
+
python - <<'PY'
|
|
364
|
+
from kwin_bridge import windows, screenshot, input as inp
|
|
365
|
+
ws = windows.list_windows()
|
|
366
|
+
print("windows:", len(ws))
|
|
367
|
+
wid = ws[0].window_id
|
|
368
|
+
print("capturing", wid)
|
|
369
|
+
p = screenshot.capture_window(wid, "/tmp/test.png")
|
|
370
|
+
print("shot:", p)
|
|
371
|
+
inp.click_window(wid, 100, 100)
|
|
372
|
+
inp.type_text("hello from kwin-mcp")
|
|
373
|
+
PY
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## License
|
|
379
|
+
|
|
380
|
+
MIT. Use it, fork it, ship it.
|