Framesmith 1.7 – a quality gate that tells an AI agent when a UI is done Framesmith 1.7, an open-source MCP server, provides AI coding agents with a visual canvas for UI design, enabling sketching, review, and approval before code generation. The tool includes a quality inspector that scores designs and highlights issues, and integrates with AI assistants via standard MCP protocol. An open-source MCP server that gives your AI coding agent a visual canvas. Sketch the UI, review it in a browser, agree on the design — before any framework code gets written. Contents: Viewer viewer · Installation installation · Tools tools · Usage Example usage-example · Workflow workflow · Development development Above: the framesmith viewer. Workspaces and projects in the sidebar, canvases as live thumbnails on the right. AI agents create canvases via MCP tools; you browse them like Figma files. MCP Client → stdio → framesmith server ↓ Scene Graph in-memory JSON tree ↓ HTML/CSS Renderer inline styles ↓ Puppeteer headless Chromium → PNG Run npx -p framesmith framesmith-viewer to start the standalone browser viewer default port 3001 . Open any canvas to review it at multiple breakpoints, compare them side-by-side, inspect the underlying JSON, or archive / delete. Above: a single canvas in the detail view. The toolbar across the top exposes the breakpoint preview modes, Compare for side-by-side rendering, Fit for max-width, JSON for the raw scene graph, and lifecycle actions. Quality panel. The canvas detail view shows a read-only quality inspector on the right: the heuristic canvas evaluate score 0–100 , per-category bars, and the issue list — each cliché tell with its category · tell badge, severity, and suggestion. Issues that canvas autofix can resolve carry an auto-fixable tag, and clicking any issue highlights its node in the live preview. Every gallery card also shows a color-coded score badge so weak canvases stand out at a glance. The score matches what your agent sees over MCP same fast-mode evaluation, genre-relaxed by the canvas's preset — it's computed for display only and never written back. Design-system panel. A second inspector tab shows the canvas's effective design tokens — color swatches, type scale, spacing, and radius — resolved through the full workspace ▸ project ▸ canvas inheritance chain. Each section notes its dominant source layer, and any token that overrides it is tagged canvas / project so you can see at a glance what a given canvas customized versus inherited. The viewer is purely read-only — every canvas is authored through MCP tool calls from your AI assistant. Files persist to ~/.framesmith/canvases/ so the viewer keeps showing them across sessions. No clone or build needed — register framesmith with your MCP client via npx requires Node 20+ . claude mcp add framesmith -- npx -y framesmith Add to ~/.codex/config.toml : mcp servers.framesmith command = "npx" args = "-y", "framesmith" Add to ~/.cursor/mcp.json or per-project .cursor/mcp.json : { "mcpServers": { "framesmith": { "command": "npx", "args": "-y", "framesmith" } } } Add to ~/.codeium/windsurf/mcp config.json : { "mcpServers": { "framesmith": { "command": "npx", "args": "-y", "framesmith" } } } Add to .vscode/mcp.json project-scoped or your global MCP settings: { "servers": { "framesmith": { "command": "npx", "args": "-y", "framesmith" } } } framesmith speaks standard stdio MCP. Point your client at npx -y framesmith using whatever config shape your client expects. Optional:set FRAMESMITH VIEWER URL=http://localhost:3001 in the MCP server env to pin it to a long-lived standalone viewer process — see Running the viewer . git clone https://github.com/vicmaster/framesmith.git cd framesmith npm install npm run build then point your client at: node /path/to/framesmith/dist/index.js One-call onboarding — the recommended first call each session , and safe to run repeatedly idempotent . Binds the current repo if it isn't already canvases become checked-in JSON under .framesmith/ , ensures the convention projects exist, and returns the live state you need to start working. | Param | Type | Description | |---|---|---| dir | string? | Directory to bind / detect. Defaults to the nearest git repo root above the server working directory. | workspaceName | string? | Name for the workspace when binding fresh. Defaults to the repo folder name. | projects | string ? | Projects to ensure exist default: "Foundations", "UI" . Existing projects are never removed, so it's safe for adding feature/area projects like Onboarding . | Returns the bound workspace + project IDs binding re-keys IDs to repo- — use the ones init returns , the on-disk layout, the workspace-layer token count, a workflow cheatsheet, the current gotchas, the framesmith://guidelines URI, and the viewer URL. It does not seed design tokens — set those at the workspace layer with workspace set design system . The default Foundations project is just a canvas that visualizes the workspace tokens which is where the design system actually lives . Create a new canvas. If projectId is omitted, it lands in the built-in Untitled project of the Personal workspace. | Param | Type | Description | |---|---|---| name | string? | Canvas name | projectId | string? | Target project. Defaults to the built-in Untitled project. See project list . | The response also carries a diversification signal for the target project: the recently-built structures newest first and a hint to differ on at least one taxonomy axis, so successive canvases don't converge on the same layout. It's advisory — never blocking. List canvases. Excludes archived canvases by default. | Param | Type | Description | |---|---|---| projectId | string? | Scope to one project | includeArchived | bool? | Include archived canvases default false | Returns { id, name, createdAt, lastModified, projectId, archived } . Canvas lifecycle. canvas move reassigns a canvas to a different project. canvas archive sets a soft-delete flag canvas stays on disk, hidden from default canvas list ; canvas unarchive clears it. canvas delete removes the canvas and its file permanently — irreversible. Get the URL of the live viewer plus per-canvas URLs. Share these with the user so they can open the design in their browser. No params. { "url": "http://localhost:3001", "gallery": "http://localhost:3001", "canvases": { "name": "Login", "viewer": "http://localhost:3001/canvas/abc123" } } canvas create already returns the per-canvas viewer URL in its response; reach for viewer url when you want the gallery URL or to enumerate every existing canvas's URL in one call. Top-level container CRUD. The built-in Personal workspace cannot be deleted, and workspace delete refuses if the workspace still contains projects move or delete them first . Mid-level container CRUD inside a workspace. The built-in Untitled project cannot be deleted. project delete refuses if the project still contains any canvases archived ones still count — move or delete them first . Bind a workspace to the current project directory so its canvases live in the repo as open JSON — a .framesmith/ directory checked in alongside the code, instead of the global ~/.framesmith store. Run it once per repo. | Param | Type | Description | |---|---|---| workspaceId | string? | Workspace whose projects + canvases migrate into the repo. Defaults to the built-in Personal workspace. | dir | string? | Directory to bind. Defaults to the nearest git repo root above the server's working directory. | It creates .framesmith/workspace.json the binding plus the design system, so a fresh clone resolves tokens identically and one subdirectory per project holding one slug-named file per canvas: .framesmith/ workspace.json workspace + projects + design system design-system/ design-tokens.json ui/ bloom-landing.json login-form.json It migrates the workspace's projects + canvases in and makes the repo the source of truth for the rest of the session. A canvas is either repo-bound or global, never both. Afterwards the server auto-detects .framesmith/ on startup walking up from its working directory . Commit .framesmith/ so designs travel with the code and diff cleanly in review. The bind also records the repo in ~/.framesmith/registry.json , so the standalone viewer shows bound repos alongside your global workspaces in one gallery it rebuilds that read-only mirror on launch and whenever the registry changes . Execute operations on the scene graph. Operations are line-separated strings: Insert a frame into the document root header=I "document", { type: "frame", layout: "horizontal", fill: " 1a1a2e", padding: 24, gap: 16, width: 1440, height: 80 } Insert text into the header I header, { type: "text", content: "My App", fontSize: 24, fontWeight: 700, color: " ffffff" } Update a node U "nodeId", { fill: " e94560" } Delete a node D "nodeId" Copy a node to a new parent copy=C "sourceId", "parentId", { fill: " 0f3460" } Move a node M "nodeId", "newParentId", 0 Replace a node entirely R "nodeId", { type: "text", content: "Replaced" } Returns { ok, nodeIds, results } . nodeIds maps each bound variable to the node ID it created — e.g. { "header": "n a1b2" } — so you can target those nodes in later calls bindings only live within a single call . results lists each op's outcome in order. Node types: frame , text , rectangle , ellipse , image , icon , path , component , instance , toggle , checkbox , radio , select Properties: fill , gradient , stroke , strokeWidth , cornerRadius , width , height , layout "horizontal" | "vertical" , gap , padding , alignItems , justifyContent , fontSize , fontFamily , fontWeight , color , content , textAlign , lineHeight , letterSpacing px , textDecoration , textTransform , fontVariationSettings , src , objectFit , opacity , shadow , shadows , blur , backdropBlur , backdropFilter , overflow , wrap , position , x , y , icon , iconSize , iconColor , iconStyle , checked , disabled , value , d , viewBox , strokeLinecap , strokeLinejoin , animation , transition , componentId , overrides Use textTransform: "uppercase" for uppercase labels don't bake casing into content , letterSpacing for tracking, and fontVariationSettings e.g. '"wght" 650' for variable-font axes. Render canvas to PNG returned as base64 image . | Param | Type | Description | |---|---|---| canvasId | string | Canvas ID | nodeId | string? | Specific node to capture | width | number? | Viewport width default 1440 | height | number? | Viewport height default 900 | scale | number? | Device scale default 2 | Read node data from the scene graph. | Param | Type | Description | |---|---|---| canvasId | string | Canvas ID | nodeIds | string ? | Node IDs to read default: root | maxDepth | number? | Max traversal depth default 5 | Get computed bounding boxes via browser rendering. | Param | Type | Description | |---|---|---| canvasId | string | Canvas ID | nodeId | string? | Root node to start from | maxDepth | number? | Max depth default 10 | Read and write design tokens colors, spacing, radius, typography . Use $tokenName in node properties to reference variables. { "colors": { "primary": " e94560", "bg": " 1a1a2e" }, "spacing": { "sm": 8, "md": 16, "lg": 24 }, "radius": { "sm": 4, "md": 8 } } Then use in nodes: { fill: "$primary", padding: "$md", cornerRadius: "$sm" } Set tokens at the workspace level — every project + canvas under the workspace inherits them. Resolution order at render is canvas.variables override → project.designSystem → workspace.designSystem → built-in defaults, with the rightmost layer winning. Per-category merge: setting only colors doesn't reset spacing . workspace set design system { workspaceId: "...", variables: { colors: { primary: " f59e0b", bg: " 0a0a0a" }, spacing: { sm: 8, md: 16, lg: 24 } } } workspace apply preset { workspaceId, preset } is a shortcut that copies a named preset "dark" , "light" , "material" , "minimal" into the workspace. Same shape, but at the project layer between workspace and canvas. Use for sub-brand overrides e.g., a Marketing project that overrides one color while inheriting everything else from the workspace . Fonts load by name automatically — naming a fontFamily in a typography token or on a node resolves it from Google Fonts at token-write time, with a render-time backstop catching anything else. Binaries are cached under ~/.framesmith/fonts/ , so renders are offline and deterministic after the first resolve; typography.body.fontFamily becomes the document default. An unresolvable family renders in the fallback stack and adds a Font warnings item to the screenshot/export result. set fonts covers explicit registration. Three forms, combinable: { "families": "Inter", "JetBrains Mono" , "fonts": { "family": "Inter", "url": "https://fonts.googleapis.com/css2?family=Inter:wght@400;700" }, { "family": "Brand Face", "url": "https://example.com/brand.woff2", "weight": 400 } } families — resolve by name from Google Fonts and merge into the existing declarations. fonts with a Google Fonts CSS URL fonts.googleapis.com/css2?... — faces are extracted from the stylesheet automatically. fonts with a direct binary URL .woff2 / .woff / .ttf / .otf or a data: URI — for non-Google sources. fonts replaces declarations wholesale pass to clear ; families merges. The renderer emits @font-face blocks plus