quickshell-mcp
Enables driving a Quickshell QML app inside a headless compositor, with tools to screenshot windows, inspect their object trees, and read or write QML state.
README
quickshell-mcp
An MCP server that runs a quickshell (QML) app and drives it.
The server starts the app inside a headless sway. It takes screenshots of
windows, dumps the object tree of a window, and reads or writes QML state. The
compositor supplies the display; a profile supplies the backends.
The server is app-agnostic. The profile tells it which config directory to run, which entry file to load, which backend processes to start, and which macros to offer. You give the server one profile, and the server drives that app.
Run it
nix run starts the server. It speaks MCP over stdio.
Run the counter example:
nix run ~/git/quickshell-mcp -- --profile ~/git/quickshell-mcp/examples/counter/profile.json
Point --profile at any app's profile JSON.
Drive the app
You operate the app through tools. Each tool call returns a result. A refused
call returns an MCP error with the message <code>: <detail>.
The loop:
screenshot(window_index): see the app as a PNG.windows(): list the windows.tree(window_index, root_path, max_depth): map a window's visual tree. Each node reports apath(a child-index chain like0/2/1) and itsobjectName.find(name): get the child-index paths of nodes whoseobjectNameisname.- Build a selector that names one node and one member.
get_property/set_property/invoke/qml_eval/macro: read or change state.screenshot(window_index)again: see the result.
Selectors
A selector names one node and one member. It takes one of three forms:
wN.prop: windowNitself, then memberprop. Example:w0.count. A window's own properties live here.wN/childpath.prop: windowN, then a child-index path under its content item, then memberprop. Example:w0/0/2.text.@objectName.prop: the first node with thatobjectName, then memberprop. Example:@saveBtn.enabled.
find returns bare paths like 0/2/1. Turn one into a selector as
wN/<path>.<prop>, or pass it to tree(root_path=...).
An @objectName selector resolves to the first match. An app that repeats a
control per item suffixes the item name into the objectName
(activateToggle-alpha), so each control has a unique name.
screenshot needs the window's content item to hold exactly one visual child
that covers the window.
Tools
| Tool | Arguments | Effect |
|---|---|---|
ping |
none | Returns ready once the app loaded, loading before. |
windows |
none | Lists the windows: index, type, title, visible, size. |
screenshot |
window_index=0, full_page=false |
Grabs a window as a PNG, compositing the content over the window's (opaque) background colour so it is never transparent. full_page captures a scroll view's whole content. |
tree |
window_index=0, root_path="", max_depth=0 |
Dumps a window's visual tree as JSON. Password fields stay hidden. |
get_property |
selector |
Reads a QML property as JSON. |
set_property |
selector, json_value |
Writes a QML property from a JSON value. |
invoke |
selector, args_json="[]" |
Calls a QML method with a JSON array of arguments. |
qml_eval |
expr |
Runs a JavaScript expression in the app scope. win(i) and app are in scope. |
find |
name, window_index=0 |
Lists the child-index paths of nodes whose objectName is name. |
macro |
name, params_json="{}" |
Runs a profile macro, with ${param} values filled in. |
up |
none | Boots the app. Idempotent while the app is alive. |
reset |
none | Tears the app down, then boots a fresh one with re-seeded fixtures. |
down |
none | Tears the app, the backends, and the compositor down. A tool call while down returns an error. |
qml_eval reaches any state in the app. win(i) gives window i;
app gives the app root.
Error codes
A refused call returns an MCP error. The code is one of: unresolved,
no-member, not-callable, too-many-args, no-window, no-node,
not-visible, zero-size, grab-refused, multi-root, partial-window,
bad-payload, threw.
Profiles
A profile is a small JSON file you write for each app. --profile is
required.
Every field lives in
quickshell_mcp/profile.cue, with a doc-comment
on each. The loader checks your profile against that schema with cue vet
before boot, so a broken profile fails at start with the schema error.
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.
See examples/counter/profile.json for a macro, and
examples/async-form/profile.json for a backend with a readiness gate, a seeded
fixture, and env_out.
Generate a JSON Schema for your editor from the same source:
cue def --out jsonschema -e '#Profile' quickshell_mcp/profile.cue
Examples
Each example is self-contained and doubles as a test. Each ships a check.py
that boots it for real and asserts the states its README documents.
python examples/check.py # every example
python examples/check.py counter # one of them
counter: a tiny, self-contained QML app.async-form: a frontend/backend form flow with a fake backend over a unix socket, a service list, per-profile forms with validation, keyed in-flight state, and a streaming setup flow with prompts. Seeexamples/async-form/README.mdfor the state tables and the selectors.
Contributing
Launch the dev-shell:
nix develop ~/git/quickshell-mcp
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.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
Neon Database
MCP server for interacting with Neon Management API and databases
E2B
Using MCP to run code via e2b.
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.