loading…
Search for a command to run...
loading…
MCP server that enables AI coding agents to read and write to a local-first HTML/CSS design canvas, bridging visual design and code generation.
MCP server that enables AI coding agents to read and write to a local-first HTML/CSS design canvas, bridging visual design and code generation.
An open-source MCP design canvas that gives AI coding agents eyes.
DesignJS is a local-first, HTML/CSS-native visual canvas that AI coding agents (Claude Code, Cursor, Codex) read and write through the Model Context Protocol. Design and code converge into a single artifact — no translation gap between visual intent and production output.
Status: v0.1 foundations + v0.3 Chrome-extension capture working end-to-end. The agent ↔ canvas loop is stable, and a separate side channel lets you drop any live web page or DOM subtree onto the canvas as editable HTML via the extension. v0.2 polish (paste-import, transform handles, component instances) is in flight per the roadmap.
AI coding agents currently generate frontend UI blind. They produce React components from text prompts alone, with no ability to see a visual design, iterate spatially, or maintain layout relationships. Design engineers spend 2–4 hours a day in prompt → preview → re-prompt cycles getting agents to match their visual intent.
Two proprietary tools are closing this gap from different angles. Paper.design ships an HTML/CSS-native canvas — the design is the production code — but as a hosted SaaS product tied to its own backend. Pencil.dev ships a local-first, git-native canvas that any agent can drive over MCP — but the design file is a vector format in a closed renderer, not the HTML/CSS that ships to production. DesignJS combines both bets: HTML/CSS-native like Paper, local-first and git-native like Pencil, MIT-licensed, and built on open-source foundations (GrapesJS, the MCP TypeScript SDK, html-to-image).
┌───────────────────┐ stdio ┌─────────────────┐
│ Claude Code / │──────────────▶│ MCP Server │
│ Cursor / Codex │ (JSON-RPC) │ (Node.js) │
└───────────────────┘ └───────┬─────────┘
│ WebSocket (127.0.0.1:29170)
▼
┌─────────────────────────────────────────────┐
│ Browser (Vite + React SPA) │
│ ┌────────────┐ ┌───────────────────────┐ │
│ │ Editor UI │ │ GrapesJS Canvas │ │
│ │ (panels, │ │ (iframe with │ │
│ │ blocks, │ │ real HTML/CSS │ │
│ │ styles) │ │ + Tailwind v4 CDN) │ │
│ └────────────┘ └───────────────────────┘ │
└─────────────────────────────────────────────┘
│
┌───────┴───────┐
│ .designjs │
│ .json │
└───────────────┘
Agent ↔ Canvas communication flows stdio → MCP server → WebSocket bridge → browser → GrapesJS API, with responses travelling back the same path. Both human edits and agent edits converge on the same GrapesJS component model — one source of truth.
Requires Node.js 20+.
The canvas app runs locally (it's not hosted). Two ways:
From source (recommended while v0.2 is in development — get all the latest):
git clone https://github.com/rubychilds/DesignJS.git
cd DesignJS
pnpm install
pnpm dev
Opens at http://localhost:3000. The WebSocket bridge listens on 127.0.0.1:29170. Connection status is visible top-right in the editor shell.
npm create designjs@latest my-app
cd my-app
Drops a .mcp.json, CLAUDE.md, and README.md pointing at the published MCP server. If you already have a project directory and just want the MCP config, skip this step and use npx @designjs/cli init instead (writes .mcp.json / .cursor/mcp.json / .vscode/mcp.json based on which IDE config dirs it detects).
With the canvas running and your project scaffolded:
claude # Claude Code — reads .mcp.json automatically
# or
cursor .
# or
code .
On the first tool call, the agent runs npx -y @designjs/mcp-server and connects. The bridge dot in the canvas Topbar flips to green. Prompt:
"Create a Desktop artboard, then add a pricing section with three tier cards."
If you'd rather write the MCP config yourself, add this to your project's .mcp.json:
{
"mcpServers": {
"designjs": {
"command": "npx",
"args": ["-y", "@designjs/mcp-server"]
}
}
}
The DesignJS Chrome extension lets you grab any element or full web page and drop it onto the canvas as editable HTML. The capture pipeline walks the DOM, serializes computed styles + author CSS, and routes everything into a fresh artboard on your running canvas — no copy-paste, no screenshot tracing.
The extension isn't on the Chrome Web Store yet (planned for v0.3 public). For now, load it locally from this repo:
pnpm install # if you haven't already
pnpm --filter @designjs/chrome-extension build # builds packages/chrome-extension/dist/
Then in Chrome:
chrome://extensionspackages/chrome-extension/dist/When you next change extension source, rebuild and click the circular reload arrow on the DesignJS card in chrome://extensions.
With pnpm dev running (canvas reachable at localhost:3000):
↑ ↓ ← → to traverse up/down the DOM tree, Enter to commit, Esc to cancel. The selection plus its descendants and computed styles land in the canvas as a new component.<html> tree, scrolls + settles lazy-mounted sections, takes a full-page screenshot for an opacity-0.15 backplate, creates a fresh artboard sized to the source page, and ships everything to the canvas. Progress phases (Capturing → Sending → Rendering) display in the overlay during long captures.Captured content lands as real, editable HTML/CSS — every element shows up in the layer tree, every property is inspectable + editable, classes survive intact for Tailwind round-trips, and the result saves to your .designjs.json like any other canvas state.
<iframe> content (inlined as srcdoc)editor.Css.addRules so dedup + author rules land in the canvas's CSS Manager rather than being stripped by GrapesJS' HTML parser — see ADR-0011 2026-05-24 addendum)<link> tags hoisted into the capture (Google Fonts / Bunny / TypeKit / fonts loaded via <link>)_djhN classes to keep payload boundedDOM.getDocument traversal<iframe> with absolute src, content not inlinedfont-family value preserved, but the font file doesn't follow (Phase 1 of font preservation is in docs/font-preservation-plan.md)add_components is the bottleneck; chunked dispatch is tracked as Q1 in epic-8-followups.md §9The overlay shows a progress phase + an inline error if anything fails. For more detail:
[designjs] page captured: N nodes, MKB, serialized in Tms plus a [designjs] extracted N <style> block(s) line confirming the CSS extraction step.chrome://extensions → DesignJS card → blue "service worker" link) — the background logs [designjs] dispatching add_css_rules: NKB then the canvas response.localhost:3000) — the bridge logs [designjs:bridge] add_css_rules: NKB → N chunks → M rules parsed confirming rules landed in the CSS Manager.If [designjs:bridge] add_css_rules doesn't appear after a capture, the canvas isn't running an add_css_rules-aware build — restart pnpm dev from scratch (not just HMR).
.designjs.json per canvas session, not per projectThe Vite dev server writes all canvas state to a single .designjs.json at the root of the cloned DesignJS repo, regardless of which user project an agent is operating in. A user who scaffolds /tmp/my-app via create-designjs, points Claude Code there, and starts designing will find the resulting .designjs.json inside the DesignJS clone — not inside /tmp/my-app.
For v0.1 this is a known limitation. The practical workaround if you're designing for multiple projects:
pnpm dev session per project. Before switching projects, save (Cmd+S), copy .designjs.json out of the DesignJS clone into your project's directory, then git restore .designjs.json to reset the canvas.A proper fix — per-project .designjs.json discovery via a get_project_context MCP handshake plus a Paper-style file switcher in the Topbar — is tracked as a v0.2 feature. It's the single biggest UX gap in v0.1.
If you have other design MCPs configured globally in ~/.claude.json (pencil, paper, figma, etc.), Claude Code can pick any of them for a "design this" prompt. The CLAUDE.md that create-designjs drops tells agents to prefer DesignJS, but isn't foolproof — reinforce in-prompt ("use designjs, not pencil") or remove competing entries from your user config to isolate the test.
opencanvas entry in Claude Code configPre-rebrand users may have an opencanvas MCP server sitting in ~/.claude.json's mcpServers block pointing at @opencanvas/mcp-server (which doesn't exist on npm anymore). Claude Code retries the connection on every launch and reports opencanvas · ✘ failed. Open the file and delete that key — restart Claude Code and the error stops.
| Package | npm name | Purpose |
|---|---|---|
| packages/app | (not published) | Vite + React SPA hosting the GrapesJS canvas. Embeds a WebSocket hub (port 29170) that relays messages between the MCP server and the browser. |
| packages/mcp-server | @designjs/mcp-server |
Standalone stdio MCP server. Registers all tools, forwards calls over WebSocket to the canvas. This is what npx -y @designjs/mcp-server runs. |
| packages/bridge | @designjs/bridge |
Shared Zod schemas for the WS wire protocol and MCP tool I/O. Consumed by both halves. |
| packages/cli | @designjs/cli |
designjs init — detects the installed IDE(s) and writes the right MCP config. |
| packages/create-designjs | create-designjs |
npm create designjs@latest <dir> scaffolder. Drops .mcp.json + CLAUDE.md into a fresh project. |
| packages/chrome-extension | (not published — load unpacked) | Chrome MV3 extension that captures any web page or DOM subtree and drops it onto the running canvas. Loadable via chrome://extensions → Load unpacked → packages/chrome-extension/dist/. |
Twenty-one bidirectional tools, grouped by area. Full input/output schemas in packages/bridge/src/tools.ts.
Inspect
| Tool | Purpose |
|---|---|
ping |
Health check — returns { pong: true, at: <timestamp> }. |
get_tree |
Recursive JSON component tree (optional depth). |
get_html |
Clean HTML (optional componentId scope). |
get_css |
CSS stylesheet (optional componentId scope). |
get_jsx |
Convert canvas HTML to JSX. mode="tailwind" (default) preserves classNames; mode="inline" emits style objects. |
get_screenshot |
PNG/JPEG base64 screenshot (scale=2 for high fidelity). |
get_selection |
Component IDs currently selected in the editor. |
get_variables |
Read CSS custom properties on the canvas :root. |
Mutate
| Tool | Purpose |
|---|---|
add_components |
Insert raw HTML (Tailwind supported). artboardId lands content in a specific frame. |
add_css_rules |
Register raw CSS rules directly with the canvas's CSS Manager. Bypasses add_components' HTML parsing (which strips <style> elements) so author + dedup CSS from the Chrome extension actually lands. Chunked at rule boundaries for large payloads. |
update_styles |
Set CSS properties on a component by id. |
add_classes / remove_classes |
Tailwind class helpers without re-emitting full HTML. |
set_text |
Replace the text content of a text-bearing component. |
set_variables |
Write CSS custom properties (persisted to .designjs.json). |
delete_nodes |
Remove components and their children. |
select / deselect |
Drive the editor's selection from the agent side. |
Artboards (multi-frame canvas)
| Tool | Purpose |
|---|---|
create_artboard |
Add a new artboard frame at given size + position. Replaces the empty scratch frame on a fresh canvas so create_artboard({ name: "Desktop" }) yields one Desktop, not two. |
list_artboards |
Enumerate frames with { id, name, x, y, width, height }. |
find_placement |
Suggest a non-overlapping (x, y) for a new artboard of given size. |
fit_artboard |
Shrink a frame's height to match content. Useful after add_components drops content into a fixed-preset artboard (e.g. Desktop 1440×900) and you want the artboard to hug instead of leaving blank space. |
| Tool | Canvas format | MCP | License |
|---|---|---|---|
| Paper.design | HTML/CSS + GPU shaders | Bidirectional (21 tools) | Proprietary |
| Pencil.dev | Vector (native) | Bidirectional (6 tools) | Proprietary |
| Figma | WASM vector engine | Read-only Dev Mode | Proprietary |
| Penpot | SVG | Community only | MPL-2.0 |
| GrapesJS | HTML/CSS iframe | None | BSD-3 |
| Onlook | Live React DOM | Own agent | Apache-2.0 |
| Webstudio | DOM (real CSS) | None | AGPL-3.0 |
| DesignJS | HTML/CSS iframe (GrapesJS) | Open bidirectional | MIT |
Foundations
.designjs.json — Cmd+S, 30s autosave, reload-restore, git-diffable127.0.0.1:29170, multi-peer routingdesignjs init CLI — auto-detects Claude Code / Cursor / VS Code and writes the right MCP configMulti-frame spatial canvas (originally v0.2 — landed early)
Primitive vocabulary (ADR-0005)
Rectangle 1, Rectangle 2, …)Semantic inspector (panel reshape from sectioned style manager)
MCP tools (21 tools, full list above — all verified via Playwright specs + unit tests)
ping, get_tree, get_html, get_css, get_jsx, get_screenshot, get_selection, get_variablesadd_components, add_css_rules, update_styles, add_classes, remove_classes, set_text, set_variables, delete_nodes, select, deselectcreate_artboard, list_artboards, find_placement, fit_artboardRemaining for v0.1
paste-import.ts exists for image paste)npm create designjs@latest scaffolder (shipped — packages/create-designjs)Chrome extension for site capture (substantially shipped — load-unpacked today; Web Store submission pending)
↑↓←→ traverses, Enter commits, Esc exits<html> capture with <body> → <div> swap, fresh artboard sized to sourcesrcdoc)@font-face, @keyframes, ::before / ::after rules_djhN classes; capped at 100 to keep GrapesJS' CSS Manager surface boundedadd_css_rules bridge tool routes captured CSS (~2,400 rules on Wikipedia-class pages) directly into the canvas's CSS Manager, bypassing parseHtml stripping<link> tagsscripts/capture-diff.mjs scores source vs captured per-element drift with ε-tolerance and walk-alignment invariantsadd_components — eliminates 180s timeout race on Wikipedia-class captures (Q1 in epic-8-followups.md §9)Other v0.3 items
.designjs.jsonDetailed stories and acceptance criteria live in docs/epic-8-followups.md and the relevant ADRs.
See CONTRIBUTING.md.
Maintainer workflow for publishing releases: see RELEASING.md.
MIT — see LICENSE.
Run in your terminal:
claude mcp add designjs-mcp-server -- npx Security
Low riskAutomated heuristic from public metadata — not a security guarantee.