Skip to main content
Glama

quickshell-mcp

An MCP server that gives a coding agent hands and eyes on a quickshell (QML) app.

Point it at your app. The agent sees screenshots, reads the widget tree, and changes QML state, so it can build a UI and check its own work instead of asking you what the screen looks like.

What the agent does with it

The server drives one app in a loop:

  1. screenshot returns the window as a PNG.

  2. tree and find map the widgets and their names.

  3. get_property, set_property, invoke, qml_eval, and macro change state.

  4. screenshot again shows the result.

The agent learns this loop when it connects.

Every observation puts a PNG in the agent's context.

Related MCP server: kwin-mcp

Does it fit your setup

Your app must be a quickshell config directory with a QML entry file. screenshot needs each window's content item to hold exactly one visual child that covers the window.

The agent reaches a widget by its objectName, or by a child-index path from the window root. A path changes when you edit the layout, so set objectName on every widget the agent should drive.

The server runs the app under a headless sway with the pixman software renderer, so it works over SSH and in CI. Linux only, because the app needs a wayland compositor with layer-shell.

Tested on x86_64-linux and aarch64-linux, if you run other platforms, add i.e. riscv64-linux to flake.nix, run python examples/check.py, and open a PR with the output.

Nix supplies every dependency: quickshell, sway, and cue.

One server drives one app. You can run more servers for iterating on multiple apps.

Try it

Run the counter example:

nix run github:hsjobeki/quickshell-mcp#demo

It starts a compositor, boots the counter app, drives it, and saves a screenshot per step:

window 0: Counter, 1276x693
  count: 0 -> quickshell-mcp-demo/1-start.png
  count: 1 -> quickshell-mcp-demo/2-increment.png
  count: 41 -> quickshell-mcp-demo/3-macro.png
  count: 0 -> quickshell-mcp-demo/4-reset.png

Open 3-macro.png. The label reads count: 41, set through the server. Those four PNGs are what your agent sees.

Connect it to your agent

Add the server to your MCP client config, or ask your agent to do it:

{
  "mcpServers": {
    "quickshell": {
      "command": "nix",
      "args": [
        "run",
        "github:hsjobeki/quickshell-mcp",
        "--",
        "--profile",
        "/absolute/path/to/your-app/profile.json"
      ]
    }
  }
}

The client spawns this command directly, without a shell, so the profile path is absolute. It speaks MCP over stdio. --profile is required.

The server boots the app on the agent's first tool call and tears everything down when the client disconnects. nix run re-evaluates the flake on every start. To cut that from each session, run nix build once and point command at the absolute path of result/bin/quickshell-mcp.

To point your agent at the counter example instead of your own app, use github:hsjobeki/quickshell-mcp#counter as the flake reference above and drop the -- and --profile arguments.

The profile

A profile is a small JSON file, one per app. It tells the server which config directory to run, which entry file to load, which backend processes to start, and which macros to offer.

The smallest profile runs a self-contained app:

{ "config_dir": "." }

config_dir is the only required field. It is absolute, or relative to the profile file. entry defaults to shell.qml.

Inside any string value, ${PROFILE_DIR}, ${CONFIG_DIR}, ${WORK}, and ${XDG_RUNTIME_DIR} expand at boot.

A macro names a QML expression and its parameters, so the agent triggers an app action by name instead of composing the expression:

{
  "name": "set_count",
  "params": ["n"],
  "expr": "(function(){win(0).count=${n};return win(0).count;})()"
}

Every field lives in quickshell_mcp/profile.cue, with a doc-comment on each. The loader runs cue vet against that schema before boot, so a broken profile fails at start with the schema error.

Generate a JSON Schema for your editor from the same source:

cue def --out jsonschema -e '#Profile' quickshell_mcp/profile.cue

Examples

Each example doubles as an end-to-end test. Its check.py boots it for real and asserts the states it renders.

python examples/check.py              # every example
python examples/check.py counter      # one of them
  • counter: a tiny QML app with one macro.

  • async-form: a form flow over a fake backend on a unix socket. It covers validation, keyed in-flight state, and a streaming setup flow with prompts. Its profile shows a backend with a readiness gate, a seeded fixture, and env_out. See examples/async-form/README.md for the state tables and the selectors.

Contributing

Launch the dev-shell:

nix develop .

pytest tests runs the unit tests: they stub the harness, so they need no compositor. nix flake check runs the same tests in a sandbox. The examples double as the end-to-end checks and do boot a real compositor: python examples/check.py.

MCP_BACKEND_DELAY_MS passes to every backend a profile spawns. A backend that honours it slows async transitions enough to observe them between screenshots.

The agent-facing instructions live in quickshell_mcp/server.py.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI agents to automate Linux desktop GUI by launching and interacting with Wayland applications in isolated virtual KWin sessions, or connecting to live desktops for collaborative automation.
    48
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    MCP server for automating and introspecting native Qt applications (QWidget and QML) without source changes. Enables AI agents to control running Qt apps through UI snapshots, element lookup, and real input simulation.
    25
    2
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables MCP clients to start and stop local read-only Windows 11 screen observation sessions, then inspect redacted screen state, UI trees, frames, and wait for changes, title matches, or idle periods through stdio tools.
    Apache 2.0