ai-plugins/skills/slint/reference/debugging-and-mcp.md
width/height (may be 0 outside a
layout), visible, opacity, clip, and z-order (later siblings on top).ListView for long lists — it virtualizes, while for
inside a ScrollView instantiates every row. Keep opacity/clip layers
flat; each nested level adds a render pass.Image smooths by default; for pixel art set
image-rendering: pixelated.debug("msg", expr) prints to stderr at runtime.SLINT_DEBUG_PERFORMANCE=refresh_lazy,console prints frame diagnostics.SLINT_BACKEND=winit-skia / winit-femtovg / winit-software selects the
backend and renderer; SLINT_BACKEND=headless (needs --features slint/mcp)
runs with no display at all — see the MCP section.Window::take_snapshot() (Rust) renders the window to a pixel buffer..slint file (slint-viewer --check)slint-viewer --check ui/main.slint compiles the file and prints diagnostics
without opening a window (1.17+): exit 1 on errors, 0 otherwise
(warnings still print). -I/-L/--style apply. Use this as the fast
per-file compile check; the host build is only needed for the interop side.
.slint file (slint-viewer --screenshot)Renders a component to an image with no windowing system and no app code
(1.17+):
slint-viewer --screenshot out.png ui/main.slint # .png/.jpg, or - for stdout
slint-viewer --screenshot out.png --component MyCard ui/widgets.slint
slint-viewer --screenshot out.png --load-data props.json ui/main.slint
--backend, the viewer uses its own headless software backend (no
X/Wayland/Xvfb), renders at the component's preferred size, and exits.
--component (default: last exported), --style, -I/-L, and
SLINT_SCALE_FACTOR apply.Error: take_snapshot() called on window with invalid size means the
component's preferred size is zero — give the root explicit
width/height. (For compile checking alone, use --check instead.)--load-data file.json sets the root component's properties and global
singletons — dot-qualify ({"Theme.dark": true}) or nest
({"AppData": {"rows": [...]}}). It runs no host-language logic, so for
real application state use the MCP server or Window::take_snapshot().Rule of thumb: viewer for previewing components/layout/theme; MCP server for the running app with real data and interactions.
When the assistant host can display local images inline, include the rendered screenshot in the chat. In CLI-only hosts, print the absolute image path and summarize what was visually checked.
Slint ships an embedded MCP server (1.17+): walk the UI tree, read
accessibility properties, screenshot, and simulate clicks, drags, and typing
in the running app — the best way to verify real interactions. (If
--features slint/mcp fails with "unknown feature", the Slint version is too
old.)
Always set SLINT_EMIT_DEBUG_INFO=1 at app build time (preserves element
IDs/source locations) and SLINT_MCP_PORT to a free TCP port at run time (the
server binds 127.0.0.1:<port> and logs Slint MCP server listening on …). The
mcp feature must also be compiled into the Slint library:
Rust: pass --features slint/mcp on the command line (do not put mcp
in Cargo.toml):
SLINT_EMIT_DEBUG_INFO=1 SLINT_MCP_PORT=9315 cargo run --features slint/mcp
C++: released packages don't carry the mcp feature — build Slint from
source with -DSLINT_FEATURE_MCP=ON.
Headless (CI, container, agent sandbox — no display server or GPU): also
set SLINT_BACKEND=headless; the whole MCP toolset including
take_screenshot then works with no X/Wayland/Xvfb. Suffix a renderer
(headless-software, headless-skia) to force a specific rasterizer. If
SLINT_BACKEND is unset and the regular backend fails to initialize, the
headless backend kicks in as a fallback. It is an MCP-oriented entry point
(needs the slint/mcp feature) and the exact value may change between
releases:
SLINT_EMIT_DEBUG_INFO=1 SLINT_MCP_PORT=9315 SLINT_BACKEND=headless cargo run --features slint/mcp
Node.js (slint-ui): install the optional slint-ui-dev package as a dev
dependency at the same version as slint-ui. It ships a binary with the MCP
server (and system-testing) compiled in, which slint-ui loads automatically —
but only when SLINT_MCP_PORT (or SLINT_TEST_SERVER) is set, so ordinary runs
stay on the lean release binary. There is nothing to import from it; set the
variable before launching node:
npm install --save-dev slint-ui-dev
SLINT_EMIT_DEBUG_INFO=1 SLINT_MCP_PORT=9315 node app.js
Python (slint): install the optional slint-dev wheel at the same
version as slint (uv add "slint[dev]"). It carries the MCP-enabled binary,
which slint loads automatically — but only when SLINT_MCP_PORT (or
SLINT_TEST_SERVER) is set, so ordinary runs stay on the lean release binary.
Set the variable before importing slint (there is nothing to import from
slint-dev):
uv add "slint[dev]"
SLINT_EMIT_DEBUG_INFO=1 SLINT_MCP_PORT=9315 uv run app.py
Connect to the MCP endpoint (for example, http://localhost:9315/mcp) using
Streamable HTTP / JSON-RPC. When mentioning local MCP endpoints in chat or
status updates, always format them as inline code or inside a code block; never
write a bare URL, because some hosts auto-render http://127.0.0.1... as a web
preview even though the endpoint is not a browser UI. From a shell, curl is
the most reliable client — include the Accept header:
# List tools (confirms the server is up)
curl -s -X POST http://127.0.0.1:9315/mcp \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
# Screenshot — pipe to a file; the base64 payload breaks naive inline JSON parsing
curl -s -X POST http://127.0.0.1:9315/mcp \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"take_screenshot","arguments":{"windowHandle":{"index":"1","generation":"1"}}}}' > shot.json
list_windows, get_window_properties, get_element_tree,
get_element_properties, find_elements_by_id (qualified id like
MyComponent::my-button), query_element_descendants, take_screenshot,
click_element, drag_element, invoke_accessibility_action,
set_element_value, dispatch_key_event, start/stop_event_recording.
Most take element/window handles returned by list_windows/the tree calls.
foo := Rectangle {}), then target them via
find_elements_by_id (ComponentName::id).click_element, dispatch_key_event), then take_screenshot
to verify the result..mcp.json; declaring the server there
lets it attach automatically while the app runs:
{"mcpServers": {"my-app": {"type": "http", "url": "http://localhost:9315/mcp"}}}.