mcp server
easel
Easel — an HTML design canvas for AI agents. Claude Code / Codex write screens, you see and edit them live (MCP)
Set up
Runs on your computer. Pick your agent and operating system.
Before you start
- Node.js 20 or newer
{
"mcpServers": {
"nemodleo-easel": {
"command": "npx",
"args": [
"-y",
"easel-canvas"
]
}
}
}same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12
Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs
README
synced Oct 26, 2026Easel
An infinite canvas for HTML screens. Your AI agent writes the designs; you see and tweak them live.
A real Claude Code run on the bundled demo (the agent's thinking time is sped up 4×).
Easel turns a folder of plain HTML files into a design canvas. Claude Code or Codex writes a screen, and it shows up on the canvas a moment later. Drag, resize, retype or restyle anything on the canvas, and only that part of the source file changes. There is no export step and no proprietary file format: your designs stay in your repo as HTML and merge with git like any other code.
Try it in 30 seconds
npx easel-canvas --demo
That copies the "Tidy" habit tracker demo to ./easel-demo and opens it at http://localhost:4317. Open the AI tab (⌘J) and ask Claude Code or Codex for a change, or double-click a screen and start editing.
Already have HTML screens? Run it in your project:
npx easel-canvas # opens ./docs/design (or the current folder if it has screens/)
npx easel-canvas --init # starts a new ./docs/design with one screen
npx easel-canvas --dir ./designs # any folder of screens/*.html
Why Easel
- HTML is the source of truth. Each
screens/*.htmlfile is one board. What you see is exactly what ships to the browser, and there's nothing to export or keep in sync. - Built for AI agents. An MCP server with 20 tools lets agents see the boards, read what you selected, leave comments, check tokens and arrange the canvas. An in-app chat runs
claude -porcodex execwith your selection as context. - Surgical edits. Changes are patched into the exact source range (parse5 locations), so formatting, comments and the rest of the file stay untouched. If an agent edits the same element first, your edit is rejected instead of overwriting it.
- Git-friendly by design. Layout, notes and comments live in small JSON files written one entry per line, so two branches moving different boards merge without conflicts.
- Local-first. It runs on
127.0.0.1and works directly on the files in your repo. No account, no cloud, no lock-in.
Features
Connect your AI agent
Keep the canvas running (npx easel-canvas). Agents write HTML files directly; the MCP server lets them see (list_boards, read_board, screenshot, list_errors), know what you mean (get_selection with the selected element, visible boards and the current page), talk (comments and replies) and arrange (place_board, create_board, notes, pages, check_tokens, save_snapshot).
Claude Code: add .mcp.json to your project root:
{ "mcpServers": { "easel": { "type": "stdio", "command": "npx", "args": ["-y", "-p", "easel-canvas", "easel-mcp", "--port", "4317"] } } }
Codex: add to ~/.codex/config.toml:
[mcp_servers.easel]
command = "npx"
args = ["-y", "-p", "easel-canvas", "easel-mcp", "--port", "4317"]
default_tools_approval_mode = "approve" # the tools only view, comment and arrange
Using Easel as a submodule (below)? Point args at tools/easel/bin/mcp.mjs with "command": "node" instead.
Then try:
"Fix what I selected on the canvas: make the button text shorter." "Make three variants of the Stats screen." "Create an onboarding screen at 390×844 and put it next to Today."
New files with <meta name="board-size" content="390x844"> are placed on the canvas automatically at that size.
Pin it in your project
npx easel-canvas is all most projects need. To pin an exact version for your team, add it as a dev dependency (npm i -D easel-canvas, then "design": "easel-canvas" in your scripts) or as a git submodule. With your designs in <project>/docs/design/screens/:
git submodule add https://github.com/nemodleo/easel.git tools/easel
{
"scripts": {
"design:install": "git submodule update --init tools/easel && npm --prefix tools/easel install",
"design": "npm --prefix tools/easel start"
}
}
npm run design:install # once: fetch the submodule and install
npm run design # http://localhost:4317
- The default design folder is
../../docs/designrelative to the tool, sotools/easelopens yourdocs/designwith no flags. Anywhere else:npm --prefix tools/easel start -- --dir <design folder> --port <port>. - It installs separately from your app, so it never touches your app's dependencies, build or deploy, and CI that skips submodules (the
actions/checkoutdefault) is unaffected. - To update: push to this repo, then run
git submodule update --remote tools/easelin your project and commit the new pointer. Teammates runnpm run design:installaftergit pull.
| To do this | How |
|---|---|
| Pan and zoom | Drag empty space, Space, or Hand (H); trackpad scroll; ⌘+wheel or pinch (4–400%). ⇧1 Fit all, ⇧2 selection, ⌘0 Zoom to 100%, Minimap (M) |
| Find | Left "Boards" tab: ⌘K search and list; pages (drag to reorder; right-click to rename, delete, or set the launch page) |
| Boards | Draw with the board tool (B), use a "New board" template (Blank · Mobile screen · Desktop screen · Document), or drop an HTML file on the canvas. Duplicate ⌘D, rename F2 (renaming the file offers to update references), Delete (confirm, trash, undo), Bring to front / Send to back ⌘] ⌘[ |
| Multiple items | Drag on empty space to marquee-select or use Shift; move together (snap guides, Alt to disable); arrow keys 1, Shift 10, Option 100 |
| Board options | Right "Properties": position, size, Radius, No frame, Interactive (Play), Stretch to window, Open as launch screen, Layout guides (Columns · Rows · Grid, up to 6, ⌃G), Adjustable values |
| View | Focus view (F, ←→), Play in place (P or ▶, Esc), Play full screen (right-click) |
| Notes and shapes | Title (T), sticky note (N, 8 colors, size, bold, italic), Rectangle (R), Oval (O), Line (L), Arrow (A), Pen (P when no board is selected), image (Assets or paste) |
| Edit mode | Double-click or E. Click or Shift, Esc for parent, Enter for child, ↑↓ for siblings, Option+↑↓ to reorder, ⌘A for all siblings, ⌘G Wrap in flex |
| Direct editing | Move with handles (Alt drops inside a box) and resize, 6 align options, double-click text (Shift+Enter for a line break), Properties panel (token chips, ⚠, contrast ratio), Replace image, ⌘C/⌘V to paste into another board |
| Undo | ⌘Z / ⌘⇧Z. Canvas actions, HTML edits and board create/delete share one history (100 entries) |
| Design system | Register tokens (tokens.json) and themes in the left "Tokens" tab, Theme menu in the toolbar, Token check and Accessibility lists on the right |
| Collaboration | Comments (Reply, Resolve, author), Snapshot menu (git-commits only the design folder) |
| AI | Right "AI" tab (⌘J): Claude Code or Codex; selection, boards, visible boards, comments and references go along as context; N variants; Ask AI for a review |
| Screen | F6 to move between regions, Tab to move between boards, ⌘\ to hide panels, Appearance (System · Light · Dark) |
How it works
your-project/docs/design/
├── screens/*.html ← one board per file; agents and you both edit these
├── board.json ← positions, titles, pages, notes, shapes (one line per entry)
├── comments.json ← comment threads pinned to elements
└── tokens.json ← optional design tokens and themes
- A small Node server watches the folder (chokidar) and tells the browser which board changed, so only that board reloads, keeping scroll and selection.
- Element paths and hashes are computed from the source with parse5. Every edit is a minimal string patch into that range, written atomically.
- The canvas is a Vite + React app. Each board is an iframe of the real HTML, so scripts, links and CSS behave exactly as in a browser.
Development
npm test # unit tests (source patching, board format, chat parsing, tokens…)
npm run test:e2e # Playwright end-to-end tests against test/fixtures/design
npm run typecheck
npm run demo # run the repo's demo from source
test/fixtures/design/intentionally keeps Korean screen content and board titles: it mimics real user content and is needed to test Korean text input (IME) and rendering. SetDESIGN_CANVAS_E2E_DIR=<folder>to run the e2e tests against another design folder.examples/demo/is the "Tidy" demo used for the screenshots above.
| Folder | Contents |
|---|---|
bin/ | server.mjs (app server), mcp.mjs (MCP stdio server) |
server/ | API and routes, board.json, file watching, element paths and hashes, source patching, history, chat runner, tokens |
app/ | Canvas UI (Vite + React + TypeScript); all UI text lives in app/src/strings.ts |
test/ | node --test unit tests and Playwright e2e tests |
Background reading: the original plan is in PLAN.md, a feature-by-feature comparison with Claude Design is in PARITY.md, and the work log is in tasks.md.
Contributions are welcome: open an issue or a pull request.
License
MIT © Hyun Park