excalidraw-mcp
Use when creating hand-drawn style Excalidraw diagrams via the Excalidraw MCP at https://mcp.excalidraw.com/mcp. Use for flow diagrams, architecture diagrams, slide visuals, and any time a sketchy/hand-drawn diagram is needed as a PNG file.
Best use case
excalidraw-mcp is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Use when creating hand-drawn style Excalidraw diagrams via the Excalidraw MCP at https://mcp.excalidraw.com/mcp. Use for flow diagrams, architecture diagrams, slide visuals, and any time a sketchy/hand-drawn diagram is needed as a PNG file.
Teams using excalidraw-mcp should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/excalidraw/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How excalidraw-mcp Compares
| Feature / Agent | excalidraw-mcp | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Use when creating hand-drawn style Excalidraw diagrams via the Excalidraw MCP at https://mcp.excalidraw.com/mcp. Use for flow diagrams, architecture diagrams, slide visuals, and any time a sketchy/hand-drawn diagram is needed as a PNG file.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Excalidraw MCP
Streams hand-drawn Excalidraw diagrams via MCP. Remote endpoint: `https://mcp.excalidraw.com/mcp`
## Connecting via mcporter
**Critical:** The `--server` flag auto-corrects to configured server names. Always use `--http-url` for the Excalidraw endpoint:
```bash
# List tools
mcporter list --http-url https://mcp.excalidraw.com/mcp
# Call a tool
mcporter call 'https://mcp.excalidraw.com/mcp.create_view' --args '{"elements": "[...]"}'
```
## Tools
| Tool | Purpose |
|------|---------|
| `read_me` | Returns element format reference. Call once per conversation. |
| `create_view` | Renders diagram in OpenClaw canvas panel (visible to user in chat). |
| `export_to_excalidraw` | Uploads to excalidraw.com, returns shareable URL you can screenshot. |
| `save_checkpoint` | Save user-edited state by ID. |
| `read_checkpoint` | Read checkpoint state for restore. |
## Getting a PNG File
`create_view` renders into a canvas panel — not a file. To get an embeddable image:
1. Use `export_to_excalidraw` → get shareable URL (e.g. `https://excalidraw.com/#json=...`)
2. Open URL in browser (`browser action=open profile=clawd`)
3. **Dismiss the "Load from link" dialog** — it always appears. Take a snapshot to get the button ref, then click "Replace my content"
4. Wait ~1.5s for diagram to render
5. Screenshot
6. Crop with ffmpeg: `ffmpeg -y -i input.jpg -vf 'crop=in_w:HEIGHT:0:0' -update 1 -frames:v 1 out.png`
```python
# Step 3 automation — snapshot then click
snapshot = browser(action="snapshot", targetId=tid)
# Find button "Replace my content" in snapshot, note its ref (e.g. e218)
browser(action="act", request={"kind":"click","ref":"e218"}, targetId=tid)
browser(action="act", request={"kind":"wait","timeMs":1500}, targetId=tid)
```
## Element Format
Elements are passed as a **JSON string** (double-encoded), not an array:
```python
import json, subprocess
elements = [...] # list of element dicts
args = {"elements": json.dumps(elements)} # elements MUST be a string
result = subprocess.run([
"mcporter", "call",
"https://mcp.excalidraw.com/mcp.create_view",
"--args", json.dumps(args),
"--output", "raw"
], capture_output=True, text=True)
```
## Element Reference
### Rectangle / Box + Bound Text (CORRECT for export_to_excalidraw)
The `label` shorthand only works in `create_view`. For `export_to_excalidraw`, use a separate bound text element with `containerId`:
```json
[
{
"type": "rectangle",
"id": "r1",
"x": 40, "y": 60, "width": 160, "height": 80,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#e8f5e9",
"fillStyle": "solid",
"strokeWidth": 2, "strokeStyle": "solid",
"roughness": 1, "opacity": 100,
"roundness": {"type": 3}
},
{
"type": "text",
"id": "t1",
"x": 40, "y": 60, "width": 160, "height": 80,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1, "strokeStyle": "solid",
"roughness": 1, "opacity": 100,
"text": "My Label",
"fontSize": 22,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "r1",
"originalText": "My Label"
}
]
```
**fontFamily values:** 1=Virgil (hand-drawn) ← use this for the Excalidraw look, 2=Helvetica, 3=Cascadia (monospace)
Text element x/y/width/height must match the containing rectangle exactly.
### Arrow
```json
{
"type": "arrow",
"id": "a1",
"x": 182, "y": 105,
"width": 60, "height": 0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"points": [[0, 0], [60, 0]],
"startArrowhead": null,
"endArrowhead": "arrow"
}
```
### Text
```json
{
"type": "text",
"id": "t1",
"x": 50, "y": 80,
"width": 120, "height": 25,
"text": "Hello",
"fontSize": 20,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "middle",
"strokeColor": "#1e1e1e"
}
```
### Export JSON wrapper
```python
excalidraw_json = json.dumps({
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": elements,
"appState": {"viewBackgroundColor": "#fffce8", "gridSize": None}
})
args = {"json": excalidraw_json}
```
## Color Palette (from read_me)
| Name | Hex | Use |
|------|-----|-----|
| Blue | `#4a9eed` | Primary actions |
| Amber | `#f59e0b` | Warnings, highlights |
| Green | `#22c55e` | Success |
| Red | `#ef4444` | Errors |
| Purple | `#8b5cf6` | Accents |
Background fills: `#e8f5e9` (green pastel), `#fff3e0` (orange pastel), `#ffd6d6` (red pastel), `#fffce8` (cream/default canvas)
## Layouts
### Left-to-right flow (4 boxes)
Spacing: box width ~155px, gap ~60px for arrows. Start x=20, y=60.
- Box 1: x=20
- Arrow: x=177 (box1.x + width + 2)
- Box 2: x=237 (arrow.x + 60)
- Arrow: x=394
- Box 3: x=454
- etc.
### Fan-in (3 sources → 1 center)
Use SVG `<path>` curves for the converging lines — Excalidraw arrows can't curve natively in the JSON format. Use `export_to_excalidraw` then screenshot.
## Common Mistakes
- **Passing elements as array** — `create_view` and `export_to_excalidraw` expect `elements` as a JSON *string*, not an array. Double-encode: `json.dumps(elements)`
- **Using `--server` flag** — it auto-corrects to a configured server. Use `--http-url https://mcp.excalidraw.com/mcp` instead
- **Expecting `create_view` to return an image** — it renders in the canvas panel only. Use `export_to_excalidraw` + screenshot for PNG files
- **Inline SVG marker ID conflicts** — when embedding multiple SVGs in one HTML doc, each `<marker id="...">` must be unique (ah-d1, ah-d2, ah-d3)Related Skills
writer
Write content in Eric's voice — articles, blog posts, tweets, social media posts, marketing copy, newsletter drafts. Loads WRITING-STYLE.md and enforces kill phrases.
positioning-angles
Use when defining product positioning, choosing strategic angles, crafting value propositions, competitive positioning, product messaging, differentiation strategy, or go-to-market angles. Also use for 'how should I position my app', 'what angle should I use', 'painkiller vs vitamin', or 'market positioning'.
outline-generator
Use when generating outlines, article structures, content outlines, blog outlines, planning article sections, structuring posts, breaking down topics into sections, or organizing ideas for long-form content. Also use for 'outline this', 'structure this article', or 'plan the sections'.
last30days-open
Use only when the user explicitly asks for the open variant of last30days, including watchlists, briefings, and history queries. Sources: Reddit, X, YouTube, web.
last30days
Use when researching what happened in the last 30 days on a topic. Also triggered by 'last30'. Sources: Reddit, X, YouTube, web. Produces expert-level summary with copy-paste-ready prompts.
hooks
Use when generating hooks, headlines, titles, and scroll-stopping openers for content. Also use when analyzing viral posts, Reels, TikToks, YouTube Shorts, or successful social examples to extract reusable hook patterns and improve hook guidance.
evaluate-content
Use when judging content quality OR editing/improving existing copy: shareability, readability, voice, cuttability, angle, copy sweeps.
editor-in-chief
Use when a first draft is complete and all Phase 1 gates are done: topic selected (seo-research), title approved (hooks), outline approved (outline-generator), draft written (writer). Runs autonomous diagnosis-prescribe-rewrite loop before Substack.
copywriting
Write or improve marketing copy for any surface: pages, ads, app stores, landing pages, TikTok/Meta scripts, push notifications, UGC. Combines page copy frameworks with direct response principles.
content-strategy
Use when building content strategy: hooks, angles, and ideas from what's trending now. Covers organic and paid creative across TikTok, X, YouTube, Meta, LinkedIn.
content-pipeline
Orchestrator for the 3-article content pipeline — runs research phase, spawns parallel article sub-agents, creates Typefully drafts. Use when running the full content pipeline (usually via cron at 3am).
yt-dlp
Download audio/video from YouTube and other sites using yt-dlp. Use when the user asks to download music, songs, albums, podcasts, or video from YouTube or similar platforms. Triggers on 'download song', 'get mp3', 'yt-dlp', 'youtube download', 'rip audio'.