Skip to main content
Glama

qsmcp

An MCP server for developing Quickshell widgets with an AI coding agent. Its headline tool renders one widget to a PNG — offscreen, sandboxed, without capturing your screen and without touching your running shell.

The image comes back in the tool result, so the model actually sees what it built.

Why

Iterating on a Quickshell widget normally means restarting the whole shell and screenshotting the entire desktop. That is slow, leaks everything else on screen, and disturbs the live session. qs_preview renders just the component.

The render primitive is Qt's Item.grabToImage(callback, targetSize), which re-renders the item subtree into a framebuffer object. It is not a screen grab and not a window readback, and it works on an item far larger than its host window — a 1920×40 bar is captured from an 8×8 window.

Related MCP server: can-see

Requirements

  • Quickshell (qs) and Qt 6

  • Node 22+ — Node strips the TypeScript natively, so there is no build step

  • kwin_wayland (optional) — only needed to preview PanelWindow-rooted components

Install

git clone https://github.com/fedsfarm/qsmcp ~/Projects/qsmcp
cd ~/Projects/qsmcp && npm install

Register it with Claude Code, project-scoped so it only loads inside your shell repo. QSMCP_SHELL_ROOT is the directory holding your shell's root QML file:

cd ~/my-shell-repo
claude mcp add qsmcp --scope local \
  -e QSMCP_SHELL_ROOT=$HOME/my-shell-repo/quickshell \
  -- node $HOME/Projects/qsmcp/src/index.ts serve

Or write it into .mcp.json / any MCP client config by hand:

{
  "mcpServers": {
    "qsmcp": {
      "type": "stdio",
      "command": "node",
      "args": ["/home/you/Projects/qsmcp/src/index.ts", "serve"],
      "env": { "QSMCP_SHELL_ROOT": "/home/you/my-shell-repo/quickshell" }
    }
  }
}

With QSMCP_SHELL_ROOT unset it falls back to $XDG_CONFIG_HOME/quickshell.

Verify the handshake without a client:

printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}' \
  | QSMCP_SHELL_ROOT=~/my-shell-repo/quickshell node src/index.ts serve

Tools

tool

what it does

qs_preview

Render one component to a PNG and return the image plus structured QML diagnostics

qs_symbol

A component's root type, imports, properties, signals, functions, and a preview hint

qs_list_components

List components with root types, optionally filtered

qs_live_instances

Read-only. Running Quickshell instances

qs_live_ipc_describe

Read-only. qs ipc show — the live IPC surface

qs_live_prop_get

Read-only. qs ipc prop get

qs_live_logs

Read-only. qs log -t N, never follows

No tool mutates the running shell. There is deliberately no ipc call, no reload and no screen capture.

qs_preview arguments

arg

meaning

component

Bare name ("ClockWidget"), path relative to the shell root ("components/Bar.qml"), or an absolute path

source

Raw QML to render instead of an existing component. A complete document; relative imports like import "../config" resolve normally, and nothing is written to your tree

props

Property values to set on the component, e.g. {"open": true, "tab": "notepad"}

width / height

Explicit logical size. Omit to use the component's implicit size

dpr

Device pixel ratio, 0.25–4. Default 2

background

theme (default, the shell's panel colour), solid, checker, transparent

bg_color

Colour for background: "solid"

padding

Logical px around the component. Default 12

model_data

Value for a required modelData property; "screen:0" for the first monitor. Auto-filled when the component requires it

pre_script

QML evaluated before mounting, e.g. "Anim.enabled = false" to freeze animations

ready_expr

QML that must be true before capture, e.g. "Config.ready && Theme.themesLoaded"

max_ms

Deadline for reaching a stable frame. Default 8000

save_to

Also copy the PNG to this path

Two traps worth knowing:

  • A component gated on an open/visible property renders as a solid rectangle and still reports ok: true. Pass the gating props, or you are looking at a confidently-empty image. qs_symbol lists which props exist.

  • width also sets the virtual screen width for PanelWindow components, and they size themselves against it. Pass the real monitor width and crop afterwards rather than shrinking the surface.

How isolation works

Offscreen (default). The generated harness sets its own environment before QGuiApplication exists, using Quickshell's pragma block:

//@ pragma Env QT_QPA_PLATFORM=offscreen
//@ pragma ShellId qsmcp
//@ pragma DataDir/StateDir/CacheDir <scratch>

The server additionally unsets WAYLAND_DISPLAY and HYPRLAND_INSTANCE_SIGNATURE, so the Wayland backend and Hyprland IPC stay down — a preview cannot map a surface, capture a screen or dispatch a compositor command. DISPLAY is kept, because libqoffscreen has a GLX path (QOffscreenX11GLXContext) that gives hardware GL, which is what makes ShaderEffect and MultiEffect blur render correctly. No window is ever mapped on it.

Nested. PanelWindow cannot even be constructed offscreen ("No PanelWindow backend loaded"), so window-rooted components get a private kwin_wayland --virtual instance on its own XDG_RUNTIME_DIR. KWin implements zwlr_layer_shell_v1, so the type resolves. That virtual output exposes no usable EGL config to clients, so Qt Quick falls back to the software renderer there — reported as degraded: [shadereffect, blur, layer_effects] rather than silently producing a flat image. Blur fidelity still means a real session.

The overlay

Quickshell.shellDir is the directory of the root QML file, and real configs derive shellDir + "/themes" and shellDir + "/scripts" from it. So the harness runs from an overlay: a scratch directory under $XDG_RUNTIME_DIR/qsmcp/<hash>/shell that mirrors your tree file-by-file.

That buys three things:

  1. Script stubs. Scripts that apply GTK/Qt/kitty themes, set wallpaper or change hardware are replaced with executable no-ops. The stub must exist, not be absent — some configs fall back to a hardcoded absolute path when the shellDir copy is missing, so a missing stub would silently run the real theme applier. Anything a preview tried to invoke is reported as side_effects_blocked. Only executables are ever stubbed; QML is never touched.

  2. Correct relative imports. A file loaded as <overlay>/components/Foo.qml resolves import "../config" against its URL, so singletons instantiate exactly once. Mixing real and overlay URLs would create two Theme objects.

  3. A shadow $HOME. Per-entry symlinks to your real home, except the config directories your shell writes to, which are real copies — so Config.set() and equivalents land in scratch. Directories referenced via Quickshell.env("HOME") + "/..." are detected automatically; large trees (wallpaper libraries) stay symlinked.

Nothing is ever written under your shell tree. $XDG_RUNTIME_DIR is tmpfs, so logout is a hard reset.

Settle

Previews never sleep a fixed interval. The harness waits for ready_expr, then grabs a downscaled probe frame every 120 ms and requires two byte-identical frames before the real capture. A component that never settles (a clock with seconds, a spinner) returns the image with settled: false and reason: "timeout" rather than pretending. pre_script: "Anim.enabled = false" usually fixes it.

CLI

The same renderer without a client, for scripting or debugging:

QSMCP_SHELL_ROOT=~/my-shell/quickshell node src/index.ts render ClockWidget --out /tmp/clock.png
QSMCP_SHELL_ROOT=~/my-shell/quickshell node src/index.ts render Bar --width 1920 --height 40 --dpr 1 --padding 0
node src/index.ts render --help
node src/index.ts info      # resolved config, scratch paths, detected copy paths

The CLI writes JSON to stdout and progress to stderr; you then have to open the PNG yourself. Through MCP the image is in the tool result, which is why the server is the intended path.

Environment

var

effect

QSMCP_SHELL_ROOT

Directory holding the shell's root QML. Defaults to $XDG_CONFIG_HOME/quickshell

QSMCP_QS_BIN

Path to the qs binary. Default qs

QSMCP_STUB_EXTRA

Comma-separated script names to stub on top of the defaults

QSMCP_STUB_EXEMPT

Comma-separated script names to force-keep live

QSMCP_ALLOW_MUTATING

1 disables the stub layer entirely. Off by default, and off is the point

Known limits

  • Contact sheets, synthetic hover/press (QtTest), and a warm daemon are not implemented; every render is a cold spawn (~2 s).

  • The Quickshell API knowledge layer (parsing *.qmltypes and the annotated headers) is not built yet.

  • qmllint integration is not wired. Note that /usr/bin/qmllint is often Qt 5.15; the usable one is /usr/lib/qt6/bin/qmllint.

  • Nested (window-rooted) previews are software-rendered, so blur and shader effects do not appear.

probes/RESULTS.md records the containment and non-interference probes the design is built on.

License

GPL-3.0-or-later. See LICENSE.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    -
    quality
    D
    maintenance
    An AI-agent-first framework for building MCP servers that deliver interactive React widgets directly within AI chat interfaces like ChatGPT and Claude. It includes automated visual testing and a zero-config local development environment designed for autonomous agent workflows.
    MIT
  • A
    license
    -
    quality
    D
    maintenance
    MCP server for capturing screenshots of desktop windows on Windows. Allows AI assistants to see what's on screen for UI development, debugging, and iterating on designs.
    MIT

View all related MCP servers

Related MCP Connectors

  • MCP server for Wan AI video generation

  • Screenshot and HTML render MCP server for AI agents

  • MCP server for Hailuo (MiniMax) AI video generation

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/fedsfarm/qsmcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server