Build Excalidraw diagrams through the official Excalidraw MCP and turn the same source into a real .excalidraw file on disk or an Obsidian .excalidraw.md drawing. Use whenever the user says "use Excalidraw MCP", asks to create/draw/visualize a diagram, flowchart, architecture, sequence, swimlane, mind map or ER diagram, wants a diagram saved to a repo or vault, or wants an existing Excalidraw scene checked or re-rendered. Covers the MCP skeleton format, the on-disk schema it is NOT, a geometr...
Scanned 9/3/2026
Install to Claude Code
npx -y skills add anton-abyzov/vskill --skill excalidraw-mcp --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Excalidraw Mcp?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anton-abyzov-excalidraw-mcp)More formats (shields.io, HTML) on the badges page.
---
name: excalidraw-mcp
description: Build Excalidraw diagrams through the official Excalidraw MCP and turn the same source into a real .excalidraw file on disk or an Obsidian .excalidraw.md drawing. Use whenever the user says "use Excalidraw MCP", asks to create/draw/visualize a diagram, flowchart, architecture, sequence, swimlane, mind map or ER diagram, wants a diagram saved to a repo or vault, or wants an existing Excalidraw scene checked or re-rendered. Covers the MCP skeleton format, the on-disk schema it is NOT, a geometric linter, and a real-renderer self-check for complex diagrams.
version: 1.2.0
license: MIT
repository: anton-abyzov/vskill
mcp-deps: [excalidraw]
allowed-tools: Bash, Read, Write, Edit, mcp__excalidraw__read_me, mcp__excalidraw__create_view, mcp__excalidraw__export_to_excalidraw, mcp__excalidraw__save_checkpoint, mcp__excalidraw__read_checkpoint
---
# 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:
```bash
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.
```powershell
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
0. **Preflight** (new machine only): if the `mcp__excalidraw__*` tools are missing,
`python3 scripts/ensure_mcp.py --install` (`py -3` on Windows).
1. **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.
2. **Lint it** — offline, no dependencies, catches overflow/collision/geometry:
```bash
python3 scripts/excalidraw_lint.py scene.json --camera
```
`--camera` prints a correctly framed 4:3 `cameraUpdate` to paste at the top.
3. **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`.
4. **Save the file** when the user wants one on disk:
```bash
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
```
5. **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:
```bash
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
```jsonc
{"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.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!