SuperAPI AI Directory

excalidraw-mcp

Agents & models

View repository

Updated 10-15 19:57

About this skill

Excalidraw via MCP

Saying "use Excalidraw MCP" should be enough to get a complicated diagram, drawn live and saved where it belongs. This skill is what makes that true.

The one thing that breaks everything

create_view's element format is a skeleton, not the on-disk schema. A shape's label is skeleton-only sugar. Excalidraw's canvas renderer has no text branch for rectangle / ellipse / diamond — container text is drawn only via boundElements → containerId. So pasting create_view JSON into a .excalidraw file produces empty boxes, silently. That single fact is why scripts/excalidraw_build.py exists: author once in the skeleton, get both the live render and a correct file.

Second fact: hand-authored diagrams do not degrade gradually with size — they fail on irregularity. Measured on a real generated scene, elements produced by a repeating layout formula had a 0.00 defect rate; bespoke one-off elements had 0.71. So compute positions with a formula, and let the linter check the result.

Preflight on a new machine

Installing this skill does not install the MCP server — mcp-deps is a declaration that vskill check verifies, not an installer. If the mcp__excalidraw__* tools are not available, run this first — it detects and registers the server, on any OS:

python3 scripts/ensure_mcp.py --install

It reports where the server was found, or runs claude mcp add --transport http --scope user excalidraw https://mcp.excalidraw.com and re-verifies. Exit 0 configured, 1 missing, 2 could not register (no Claude CLI on PATH — it prints the command to run by hand). Claude Code needs a restart afterwards to pick up a newly added server.

Scopes: --scope user (default) covers every project on the machine via ~/.claude.json; --scope project writes ./.mcp.json so teammates get it on clone; --scope local is this project only. A server registered under a different project counts as missing — Claude Code will not load it here, and the script says so.

Without the MCP everything except the live inline render still works. excalidraw_build.py and excalidraw_lint.py are stdlib-only Python 3 — no packages, no network. Build files, lint them, ship them. excalidraw_render.py additionally needs pip install playwright && playwright install chromium.

Windows

Use py -3 and backslashes; python3 on Windows is a Microsoft Store stub that opens the Store instead of running anything.

py -3 scripts\ensure_mcp.py --install
py -3 scripts\excalidraw_build.py scene.json -o out.excalidraw

Everything else is portable: paths go through pathlib, output is written with explicit UTF-8 and \n newlines so a Windows run does not bake CRLF into the scene JSON, and the preflight resolves claude.cmd / claude.exe as well as claude. If vskill i warns that symlinks are unavailable, it falls back to copying — enable Developer Mode to get symlinks back.

Workflow

  1. Preflight (new machine only): if the mcp__excalidraw__* tools are missing, python3 scripts/ensure_mcp.py --install (py -3 on Windows).
  2. Author the skeleton. One JSON array, the same one create_view takes. Positions come from a layout formula (see references/layout-recipes.md), never from eyeballing.
  3. Lint it — offline, no dependencies, catches overflow/collision/geometry:
    python3 scripts/excalidraw_lint.py scene.json --camera
    
    --camera prints a correctly framed 4:3 cameraUpdate to paste at the top.
  4. Render it live with mcp__excalidraw__create_view, elements streamed in z-order with camera moves (see references/mcp-workflow.md). Keep the returned checkpointId.
  5. Save the file when the user wants one on disk:
    python3 scripts/excalidraw_build.py scene.json -o out.excalidraw
    python3 scripts/excalidraw_build.py scene.json -o out --obsidian   # → out.excalidraw.md
    python3 scripts/excalidraw_build.py scene.json -o out.excalidraw --dark
    
  6. Look at it before declaring success on anything non-trivial. This renders through Excalidraw's own exportToSvg, so text is measured with real font metrics:
    python3 scripts/excalidraw_render.py out.excalidraw -o /tmp/preview.png
    
    Then Read the PNG. Fix what you see; re-lint; re-render.

Steps 2 and 5 are the quality gate. Skipping them is how labels end up outside their boxes and arrows end up pointing at nothing.

Skeleton cheat sheet

{"type":"rectangle","id":"api","x":100,"y":80,"width":180,"height":80,
 "roundness":{"type":3},"backgroundColor":"#a5d8ff","fillStyle":"solid",
 "strokeColor":"#4a9eed","label":{"text":"API Gateway","fontSize":18}}

{"type":"arrow","id":"e1","from":"api","to":"db","label":{"text":"SQL"}}   // auto-bound
{"type":"arrow","id":"e2","from":"api","to":"db","route":"ortho"}          // L-shaped
{"type":"arrow","id":"e3","x":300,"y":150,"points":[[0,0],[120,0]]}        // manual

{"type":"text","id":"ttl","center":400,"y":20,"text":"Title","fontSize":24} // auto-centred
{"type":"rectangle","id":"zone","x":40,"y":40,"width":700,"height":420,
 "opacity":30,"zoneLabel":"Data layer"}                                     // caption, not bound label

Builder-only extensions: from/to (perimeter anchors + two-way binding), route:"ortho", zoneLabel, center on text, fixedWidth on a shape (wrap instead of grow). Everything else is passed through unchanged, so the same array still works verbatim with create_view.

Never put a bound label on a background zone rectangle — it centres in the middle of the zone and cannot be grabbed. Use zoneLabel.

Choosing the output

Destination Command Notes
Show the user now create_view animated, camera-guided; returns checkpointId
File in a repo excalidraw_build.py -o x.excalidraw opens at excalidraw.com or in the VS Code extension
Obsidian vault ... -o x --obsidian .excalidraw.md, plugin-parsed, git-diffable
Shareable link mcp__excalidraw__export_to_excalidraw uploads to excalidraw.com — ask first

The MCP has no local-file tool. export_to_excalidraw is a public upload, so treat it as publishing: confirm with the user before calling it.

Iterating

create_view returns a checkpointId. To continue from it — including user edits made in fullscreen — start the next array with {"type":"restoreCheckpoint","id":"<checkpointId>"} and append only what is new. Use {"type":"delete","ids":"a,b"} to remove elements; never reuse a deleted id. Keep the skeleton file on disk in sync, since that file is what builds and lints.

References

  • references/file-format.md — the on-disk schema, verified against upstream: font codes, bound text, arrow geometry, bindings, what Excalidraw does and does not recompute on open. Read before hand-editing any .excalidraw file.
  • references/mcp-workflow.md — camera choreography, streaming order, checkpoints, dark mode, and the MCP's own limits.
  • references/layout-recipes.md — formulas for flow, layered, swimlane, sequence, grid, radial and matrix layouts, plus how to keep a 60-element diagram legible.
  • references/obsidian.md — the .excalidraw.md wrapper, block-ref rules, and the compressed-scene gotcha.
  • scripts/ensure_mcp.py — detect and register the Excalidraw MCP server on any OS.
  • scripts/split_excalidraw_library.py — split an .excalidrawlib (AWS/GCP/K8s icon packs from libraries.excalidraw.com) into per-icon JSON plus a lookup table, so icon data never enters context.

Limits worth stating out loud

  • Width estimates are calibrated per-character, not measured from the font binary; the linter warns inside 10% of overflow. When it warns on something important, render it.
  • excalidraw_render.py needs playwright plus network access to esm.sh. Without them, lint is still fully offline.
  • The linter cannot judge whether a diagram is good, only whether it is correct. Composition is still your job.

Changelog

  • 1.2.0 — scripts/ensure_mcp.py: cross-platform preflight that detects the MCP server (including ~/.claude.json's per-project map) and registers it when missing. Windows support: documented py -3, claude.cmd resolution, explicit UTF-8 and \n newlines on every write.
  • 1.1.1 — linter no longer applies the box-fit rules (R1/R2) to arrow containers; arrow labels are laid along the path and are covered by R10.
  • 1.1.0 — moved into the vskill monorepo at skills/excalidraw-mcp/, matching remotion-best-practices and the other in-repo skills. The standalone anton-abyzov/excalidraw-mcp-skill repo is deprecated.
  • 1.0.1 — document MCP setup: installing the skill does not install the server; added the claude mcp add one-liner and what still works without it.
  • 1.0.0 — first release. Replaces the file-only excalidraw-diagram-generator skill, whose templates emitted inline text on shapes and therefore opened as empty boxes.