Control a live Excalidraw canvas from AI agents using a plain CLI and a loadable agent skill.
Two things to get running: the canvas server (the thing you draw on) and the CLI (the thing that controls it).
With Docker (no build step, works anywhere):
docker run -d -p 3000:3000 --name excalidraw ghcr.io/opencoredev/excalidraw-cli-canvas:latestDon't have Docker? Install Docker Desktop — free for personal use on Mac, Windows, and Linux.
Without Docker (requires Bun, runs directly):
bunx --bun github:opencoredev/excalidraw-cli serveDon't have Bun?
curl -fsSL https://bun.sh/install | bashOpen a new terminal after installing, then re-run.
bun add -g github:opencoredev/excalidraw-cliexcalidraw status
# → {"status":"healthy","elements_count":0}Open http://localhost:3000 in your browser to see the canvas.
excalidraw create --type rectangle --x 100 --y 100 --width 160 --height 60 --text "Hello"
excalidraw viewport --fit
excalidraw screenshot --out hello.png# Stop
docker stop excalidraw
# Restart after a reboot
docker start excalidraw
# View logs
docker logs -f excalidraw
# Remove
docker rm -f excalidrawPoint the CLI at a non-default server:
export EXCALIDRAW_URL=http://myserver:3000
excalidraw status| Skill | What it's for |
|---|---|
excalidraw |
CLI commands, how to use the canvas |
excalidraw-design-guide |
Colors, sizing, layout, anti-patterns |
excalidraw-workflow |
Planning, element-by-element drawing, review |
Install all three:
bunx skills add opencoredev/excalidraw-cliOr install individually:
bunx skills add opencoredev/excalidraw-cli --skill excalidraw
bunx skills add opencoredev/excalidraw-cli --skill excalidraw-design-guide
bunx skills add opencoredev/excalidraw-cli --skill excalidraw-workflowTo update, just re-run — it overwrites in place:
bunx skills updateSkills are plain markdown files in skills/ — loaded by your agent on demand, zero overhead when not in use.
This project started as an MCP server (yctimlin/mcp_excalidraw). We rewrote it as a CLI + skill for three reasons:
| MCP | CLI + Skill | |
|---|---|---|
| Context when idle | All tool schemas always loaded | Zero — skill is loaded on demand |
| Process management | Daemon, ports, crashes to manage | Stateless CLI, nothing to babysit |
| Agent compatibility | Depends on client MCP support | Any agent with bash access |
"Adding just the popular GitHub MCP defines 93 additional tools and swallows another 55,000 of those valuable tokens. Existing CLI tools gain all of that functionality for a token cost close to zero — because every frontier LLM knows how to use them already." — Simon Willison, simonwillison.net, Aug 2025
The numbers back it up: the Playwright MCP server alone eats 22.2% of Claude's 200K context window just to list its tools (demiliani.com). Three MCP servers together consume over 20% before any work starts (EclipseSource). The problem is acknowledged at the protocol level — Huawei engineers filed a formal spec proposal to fix it in the MCP repo itself (#1576).
The canvas server still runs as a simple Express app. The difference is the agent interface — instead of tool schemas loaded into context on every request, it's bash commands the agent runs when needed, guided by a skill file it loads on demand.
All commands output JSON. Set EXCALIDRAW_URL env var or pass --url to target a different canvas.
excalidraw serve [--port 3000] [--host localhost]
excalidraw statusexcalidraw create --type rectangle --x 100 --y 100 --width 160 --height 60 --text "Box"
excalidraw create --type arrow --x 0 --y 0 --start <id> --end <id>
excalidraw update <id> --text "New Label" --stroke-color "#e03131"
excalidraw delete <id>
excalidraw get <id>
excalidraw query [--type rectangle]
excalidraw clearexcalidraw batch diagram.json
cat diagram.json | excalidraw batchBatch JSON format:
{
"elements": [
{"id":"svc-a","type":"rectangle","x":100,"y":100,"width":160,"height":60,"label":{"text":"Service A"},"strokeColor":"#1971c2","backgroundColor":"#a5d8ff"},
{"id":"svc-b","type":"rectangle","x":100,"y":240,"width":160,"height":60,"label":{"text":"Service B"},"strokeColor":"#2f9e44","backgroundColor":"#b2f2bb"},
{"type":"arrow","x":0,"y":0,"start":{"id":"svc-a"},"end":{"id":"svc-b"},"label":{"text":"HTTP"}}
]
}excalidraw export [--out scene.excalidraw]
excalidraw import scene.excalidraw [--mode replace|merge]
excalidraw snapshot v1
excalidraw restore v1
excalidraw describeexcalidraw viewport --fit
excalidraw viewport --element <id>
excalidraw viewport --zoom 1.5
excalidraw screenshot [--format png|svg] [--out file.png]excalidraw align id1 id2 id3 --alignment left|right|center|top|bottom|middle
excalidraw distribute id1 id2 id3 --direction horizontal|vertical
excalidraw group id1 id2 id3
excalidraw ungroup <groupId>
excalidraw duplicate id1 id2 [--dx 20] [--dy 20]
excalidraw lock id1 id2
excalidraw unlock id1 id2excalidraw url # Shareable excalidraw.com link (encrypted)
excalidraw mermaid diagram.mmd # Convert Mermaid diagram to Excalidraw
echo "graph TD; A-->B" | excalidraw mermaid
excalidraw guide # Print design guide as JSON| Variable | Default | Description |
|---|---|---|
EXCALIDRAW_URL |
http://localhost:3000 |
Canvas server URL |
For a persistent, auto-restart setup (canvas survives reboots):
docker compose up -d # start in background
docker compose down # stop
docker compose logs -f # stream logsdocker-compose.yml is included in the repo and pulls the prebuilt image from GHCR — no build step required.
bun install
bun run src/cli.ts --help # Run CLI directly (no build step)
bun src/server.ts # Start canvas server directly
bun run type-check # TypeScript check
bun run build:frontend # Build React frontend- In-memory state — restarting the server clears the canvas. Use
excalidraw exportto persist. - Screenshot needs a browser —
excalidraw screenshotrequires the canvas open in a browser tab.
MIT