A canvas your coding agent paints on.
Open-source visual canvas for AI coding agents. Designs render as real HTML on a 2D canvas — no shape-to-code translation step. An MCP server hands the canvas (and your project's tokens and components) to Cursor or any MCP-capable agent, so the agent can compose UI visually, read what the user has selected, and ship idiomatic React using your existing design system.
Self-hosted, runs locally, no telemetry. Built for environments where SaaS design tools aren't an option.
+----------------+ WebSocket +-----------------+ stdio +-------+
| Easel canvas | <------------- | Easel MCP | <---------- | IDE |
| (web app) | state + | server | tools | agent|
| | selection | | | |
+----------------+ <-------------> +-----------------+ +-------+
|
v
+-----------------+
| ds-scanner |
| reads project's |
| tailwind config,|
| components/ui, |
| tokens |
+-----------------+
- The canvas (
pnpm canvas) runs athttp://localhost:5173. - Your IDE agent spawns the MCP server, which binds a local WebSocket on
127.0.0.1:7777(falling back through7778–7786if taken — the canvas probes the same range) and holds canonical state. Browser connections must come from a localhost origin. - You ask the agent to build something. It paints HTML on the canvas using your real tokens. You watch live.
- Selection syncs both ways. Click on the canvas → agent's
get_selectionreturns it. Agent callsset_selection→ indigo ring jumps on your screen. - When you're happy, ask the agent to ship it. It reads the tree (
get_jsx,get_computed_styles,get_screenshot) and writes idiomatic JSX into your repo using your<Button>, your tokens, your conventions.
- Node.js 20+ (
node --version) - pnpm 8+ (
pnpm --version) —npm install -g pnpm - A Chromium-based browser for the canvas (Chrome, Edge, Arc, Brave, etc.)
git clone git@github.com:mraza007/easel.git
cd easel
pnpm setup # installs deps, builds ds-scanner + mcp-server
pnpm canvas # starts only the web canvas at http://localhost:5173pnpm canvas deliberately does not start the MCP server — your IDE agent will spawn its own copy via stdio (next section). That keeps WS port 7777 free for whichever agent is in charge.
Open http://localhost:5173 once the canvas is up. Status pill in the top-right will say OFFLINE until you wire an agent.
Easel works with any MCP-capable agent. The fastest path is the bundled installer.
pnpm install:cursor # use the easel repo as the design-system source
pnpm install:cursor -- --project-root ~/code/my-app # or point at your real React projectThe script:
- Writes
~/.cursor/mcp.json(creates it if missing, preserves any servers already there). - Sets
EASEL_PROJECT_ROOTto the path you pass — that's the directoryds-scannerreads at startup to detect Tailwind config, components, and CSS variables. Point it at the React project you want the agent to translate designs into. - Idempotent — re-run any time you want to change the project root.
After running it:
- Start the canvas (
pnpm canvas). - Restart Cursor (or toggle
easelunder Settings → Tools & MCP). - In Cursor chat: "use the easel MCP — what's on the canvas?"
If you'd rather hand-edit, cursor.example.json at the repo root has the exact JSON shape. Copy it into ~/.cursor/mcp.json and replace the absolute paths.
For a project-scoped install, put the same JSON at .cursor/mcp.json inside that project. Project config wins over global if both define easel.
The MCP server is a plain stdio binary at mcp-server/dist/index.js (run pnpm setup first). Any tool that can spawn an MCP stdio process works — point it at that path and set EASEL_PROJECT_ROOT to enable design-system introspection.
After wiring an agent, try:
Use the easel MCP to tell me what's on the canvas.
Calls get_basic_info. The agent summarizes the seed artboard.
Use easel to add a "Pricing" artboard with three cards side-by-side.
Watch the artboard appear and fill in live as the agent calls create_artboard + write_html.
I've selected a button on the canvas. Use easel to read what's selected and
make it bigger and indigo.
Calls get_selection, then update_styles. No node-id pasting.
Read the Pricing artboard via get_jsx + get_computed_styles, then write it as
a React component to src/Pricing.tsx using semantic Tailwind classes.
The full agent → real-code loop. The screenshot tool gives the agent visual grounding when computed styles aren't enough.
Read: get_basic_info, get_design_system, get_tree, get_node_info, get_jsx, get_computed_styles, get_screenshot, get_selection
Write: create_artboard, write_html, update_styles, apply_token, set_text, set_layer_name, set_document_name, delete_node, duplicate_artboard, move_artboard, reset_canvas, set_selection, undo, redo
Design workflow: design_to_code (one-call translation brief), diff_artboards (structured tree/style diff), create_variant / get_variants (linked responsive variants), insert_component (render a real project component in a sandboxed iframe), add_comment / get_comments / resolve_comment (notes pinned to nodes — the agent explains choices, you reply on the canvas)
All HTML written to the canvas is sanitized (tag allowlist, no event handlers, no javascript: URLs). insert_component bundles the component from your project's own source and node_modules with esbuild; CSS imports are stubbed in v1, so Tailwind-styled components render unstyled for now.
| Action | How |
|---|---|
| Pan / zoom | Drag empty canvas / cmd+scroll |
| Fit view | Shift+1 all artboards, Shift+2 selection |
| Select | Click on canvas or layer tree; Shift+click for multi-select |
| Deselect | Click empty canvas, or Esc |
| Move artboard | Drag its surface or label — edges snap to other artboards; arrow keys nudge (Shift = 10px) |
| Resize | Drag the handles on the selection's east/south/corner edges |
| Edit styles | Style inspector on the right — edits apply to every selected node |
| Edit text | Double-click any text → type → Enter (or blur); Esc cancels |
| Insert elements | Press I — text, heading, button, image, stack, row, card |
| Reorder layers | Hover a row in the layer tree → ↑/↓ |
| Undo / redo | ⌘Z / ⌘⇧Z — includes agent edits, and agents can undo too |
| Delete | Select, then Delete or Backspace |
| Comments | Click a pin on the canvas, read/reply/resolve in the inspector |
| Tokens | Design-system panel in the sidebar — click a token to copy var(--name) |
| New artboard | Click + next to "ARTBOARDS" in the sidebar |
| Duplicate artboard | Select an artboard, press ⌘D |
| Rename document | Click the file name in the topbar |
| Rename artboard | Double-click its name in the layer tree |
| Server menu | Click the connection pill — Reload from disk, Reconnect, Reset |
State (artboards, document name, comments) autosaves to ~/.easel/state.json (override with EASEL_STATE_FILE). 500ms debounce, atomic write via .tmp + rename. Loads on boot. Delete the file to wipe back to the seed.
If the canvas and disk ever drift (e.g. a stale agent process left in-memory state behind), use Reload from disk in the server menu — recovers in one click.
Status pill says OFFLINE forever.
No MCP server is listening on 127.0.0.1:7777–7786. Check that your IDE has spawned it — in Cursor, look under Settings → Tools & MCP and confirm easel is enabled. To see what's holding ports in the range:
lsof -nP -iTCP:7777-7786 -sTCP:LISTENMultiple agents can run their own servers side by side (each takes the next free port); the canvas connects to the first one it finds.
Canvas shows different content than ~/.easel/state.json.
A stale mcp-server is running in parallel and serving stale in-memory state. Open the server menu (click the connection pill) and use Reload from disk.
The agent says it can't find a node id.
You probably restarted the server while the canvas had stale ids in its store. Hard-refresh the browser (⌘+shift+R) so it adopts the server's current state.
app/— Vite + React canvas. Real HTML/CSS, pannable + zoomable, layer tree, selection, autosave indicator, server-management menu.mcp-server/— Node MCP server (stdio). Holds canonical tree state (with undo history), bridges to the canvas via WebSocket, bundles project components with esbuild. 30 tools today.ds-scanner/— Project introspection. Auto-detects Tailwind config (v3 + v4@theme), component library with props and cva variants, design tokens, CSS custom properties from a project root.
Alpha. The agent-driven canvas is functional end-to-end: editing (undo/redo, drag, resize, multi-select, style inspector, insert palette), design-system awareness (tokens rendered and applied, real components sandboxed on the canvas), and agent collaboration (comments, diffs, responsive variants). Next up: per-project state, npx distribution, and compiling project Tailwind CSS into component sandboxes.
MIT.