An MCP server that lets Claude (or any MCP
client) design LVGL UIs and export them as SquareLine Studio .spj project
files — preset for the Elecrow CrowPanel 5.0" (ESP32, 800×480) and the
exact LVGL 8.3.11 toolchain used by the Elecrow CrowPanel Arduino
library.
You describe the UI in natural language → the MCP builds screens, widgets,
styles and screen-to-screen navigation → it writes a .spj you open in
SquareLine Studio, tweak visually, and export to your Arduino sketch.
The
.spjwriter was reverse-engineered from genuine SquareLine 1.4.2 / 1.5.x, LVGL 8.3.11 exports. Object/screen/style/event structure and the widget-specific properties for the verified widgets (label, panel, button, image, imagebutton, slider, switch, bar, arc, roller, spinbox, keyboard, tabview, textarea, chart) match those exports property-for-property. The remaining widgets reuse the same verified scaffolding; open + re-save once in SquareLine to normalise before exporting.
SquareLine Studio has no scripting API, but its project files are JSON. This MCP
generates that JSON directly, so you get AI-assisted layout while keeping
SquareLine in the loop for visual fine-tuning and its battle-tested LVGL code
export. See docs/SETUP.md for matching SquareLine + Arduino
versions to your CrowPanel.
pip install -e . # from the repo root (installs the `mcp` dependency)Requires Python ≥ 3.10.
Claude Code / Claude Desktop — add to your MCP config
(claude_desktop_config.json or .mcp.json):
{
"mcpServers": {
"squareline": {
"command": "python",
"args": ["-m", "squareline_mcp"]
}
}
}(Or use the installed console script squareline-mcp as the command.)
See examples/mcp-config.json.
| Tool | What it does |
|---|---|
create_project(name, preset, width, height) |
Start a project. Presets: crowpanel-5 (default), crowpanel-7, crowpanel-4.3, crowpanel-2.8. |
load_project(path) |
Open an existing .spj for editing (lossless — see below). |
add_screen(name) |
Add a screen (first one is the start screen). |
rename_widget / delete_widget / move_widget |
Rename, remove, or re-parent/reorder a widget. |
rename_screen(screen, new_name) / delete_screen(screen) |
Manage screens. |
add_widget(screen, type, name, x, y, width, height, value, parent, align) |
Add a widget, optionally nested in a container. |
set_property(widget, x, y, width, height, value, align, hidden, clickable, checkable, disabled) |
Update geometry / value / core flags. |
configure_widget(widget, property, value) |
Set any widget-specific config (e.g. Range=[0,255], Mode, Options). |
set_style(widget, part, state, bg_color, text_color, radius, …, props_json) |
Full styling on any part and state; props_json reaches every style key (shadow, gradient, outline, pad…). |
set_flag(widget, flag, value) |
Set any OBJECT flag (scrollable, floating, hidden, scrollbar_mode…). |
set_layout(widget, type, flow, wrap, *_align) |
Give a container a Flex or Grid layout. |
add_event(widget, action, trigger, target, value, params_json) |
Attach any event action (see below). |
add_navigation(widget, target_screen, trigger, fade, speed) |
Convenience Change Screen event. |
add_image(source, name) / set_image(widget, source, slot) |
Register/apply image assets (copied to assets/ on export). |
list_project() / list_widget_types() / list_actions() / list_styles() / list_fonts() / list_assets() |
Introspection. |
export_project(path) |
Write <Name>.spj and copy image assets into assets/. |
get_lv_conf_requirements() / get_board_info() / get_setup_guide() |
Reference helpers. |
SquareLine keeps no asset list inside the .spj — fonts are referenced by name
and images by relative assets/<file> path. So:
- Fonts — set
text_fontto a built-inmontserrat_8…montserrat_48.list_fonts()shows them;get_lv_conf_requirements()prints the#define LV_FONT_MONTSERRAT_NN 1lines you need inlv_conf.h. Custom fonts can be referenced but must be added once in SquareLine's Font Manager (their definition isn't stored in the.spj). - Images —
add_image(path)/set_image(widget, path, slot)register a file; onexport_projectit's copied into the project'sassets/folder and stored asassets/<file>(SquareLine's convention;-means none). Works for the image widget, imagebutton slots (released/pressed/…), andslot='bg'backgrounds.
panel, button, label, image, imagebutton, slider, switch, bar,
arc, checkbox, dropdown, roller, textarea, spinbox, keyboard,
tabview, tabpage, tileview, window, list, messagebox, chart,
table, calendar, meter, led, line, spinner, colorwheel, canvas,
animimg, buttonmatrix — plus aliases (btn, img, text, toggle,
gauge, progress, input, combobox, …). Containers (panel, button,
tabview, tabpage, tileview, window, list, messagebox) hold child widgets.
CHANGE SCREEN, DELETE SCREEN, BASIC_PROPERTY, LABEL_PROPERTY,
SLIDER_PROPERTY, BAR_PROPERTY, ROLLER_PROPERTY, SET OPACITY,
MODIFY FLAG, MODIFY STATE, INCREMENT ARC/BAR/SLIDER, STEP SPINBOX,
MOVE CURSOR, KEYBOARD SET TARGET, SET TEXT VALUE FROM ARC/SLIDER,
PLAY ANIMATION, SWITCH THEME, CALL FUNCTION. Each carries SquareLine's
exact C call templates, so exported ui_events code is correct. All triggers
supported (CLICKED, VALUE_CHANGED, LONG_PRESSED, SCREEN_LOADED, …).
Every style key — bg_color, bg_grad_color, bg_grad_dir, radius,
border_color/width/side, outline_*, shadow_color/offset/params,
line_color, image_recolor, text_color/font/align, opacity, pad — on
any part (main, indicator, knob, selected, scrollbar, items,
cursor, ticks, placeholder) and any state (DEFAULT, PRESSED,
CHECKED, DISABLED, FOCUSED, combinable with |).
load_project(path) reads an existing .spj back into the model so you can
create and edit. It's lossless: loaded screens/widgets keep their original
node and edits patch it in place, so anything this tool doesn't model —
including widget types outside the catalogue (e.g. ELOANIMATION,
PROPERTYANIMATION) and any exotic property — is written back untouched.
Verified on real exports: loading and re-saving ref.spj (34 nodes),
actions.spj (13), and a 1.3 MB 133-node project reproduces the widget tree
identically. Typical flow:
load_project("~/SquareLine/Projects/Thermostat/Thermostat.spj")
add_screen("Settings")
rename_widget("Label1", "TempReadout")
set_style("TempReadout", text_font="montserrat_28", part="main", state="PRESSED")
move_widget("BackButton", parent="HeaderPanel")
export_project("~/SquareLine/Projects/Thermostat/Thermostat.spj") # save back
Create a CrowPanel project called "Thermostat". Add a HomeScreen with a title label "Living Room", a big temperature label, a slider for the setpoint, and a Settings button that navigates to a SettingsScreen. Export it.
python examples/demo_dashboard.py # writes CrowDemo.spj
python tests/test_spj.py # run the schema testssrc/squareline_mcp/
server.py MCP server + 28 tools (FastMCP)
project.py in-memory UI model + .spj assembler (+ raw-node editing)
loader.py parse an existing .spj back into the model (round-trip)
spj.py .spj property serialization (reverse-engineered schema)
widgets.py widget catalogue (config props + style parts)
styles.py full style-property catalogue, parts, states
events.py event/action builder (loads data/actions.json)
assets.py fonts (+lv_conf) and image-asset management
board.py CrowPanel presets + the project `info` block
guide.py the CrowPanel/SquareLine/Arduino setup guide
data/actions.json verbatim action templates from real exports
docs/SETUP.md the same setup guide, rendered
examples/ demo script + MCP config
tests/ schema tests
MIT