Excalidraw MCP Diagramming
Create diagrams on a live Excalidraw canvas that renders in the browser and updates in real time. You are not generating a static file β you are painting onto a shared whiteboard through MCP tools. The canvas persists between calls, so what you put on it in one call is visible in the next screenshot.
When to Use
- The user asks to "create an excalidraw", "draw me a diagram", "make a flowchart", "visualise the system", or "diagram this architecture".
- The user wants an Excalidraw canvas output (not Draw.io / diagrams.net β use
drawio-mcp-diagrammingfor that). - The user wants to visualise an architecture, workflow, data flow, system design, mind map, or sequence diagram.
- The user asks to export a diagram to PNG, SVG,
.excalidrawfile, or a shareable URL. - The user asks to update, change, or fix an existing Excalidraw diagram.
Required MCP Server
The excalidraw MCP server must be present in .vscode/mcp.json:
{
"servers": {
"excalidraw": {
"type": "http",
"url": "https://mcp.excalidraw.com"
}
}
}If the MCP tools are not available, tell the user to add the server and reload VS Code, then stop.
Available Tools
Essential β use on every diagram:
| Tool | Purpose | When |
|---|---|---|
read_diagram_guide |
Returns server-side colour palette and sizing rules | First call β before any elements |
batch_create_elements |
Creates multiple shapes and arrows atomically | Main workhorse |
get_canvas_screenshot |
Returns a photo of the current canvas | After every change β verify before continuing |
clear_canvas |
Wipes all content | Start of every new diagram |
set_viewport |
Scrolls and zooms to fit content | After creating elements |
Secondary β use when needed:
| Tool | When |
|---|---|
create_from_mermaid |
Quick drafts β 3β8 node sequential flows |
update_element |
Small corrections (position, colour, text) |
export_to_image |
User requests PNG or SVG file |
export_scene |
User requests editable .excalidraw file |
export_to_excalidraw_url |
User wants a shareable link |
describe_scene |
Audit what is currently on the canvas |
Workflow
Step 1 β Choose the Creation Path
Mermaid path β use for simple sequential flows (3β8 nodes, no zones):
create_from_mermaid(
mermaidDiagram="graph TD; A[Frontend] -->|REST| B[API]; B -->|SQL| C[DB]"
)Then jump to Step 6 (screenshot). Skip Steps 2β5.
Batch path β use for everything else: layered architectures, data flows, hub-and-spoke, any diagram needing zones or colour-coded roles. Continue below.
Step 2 β Read the Design Guide
read_diagram_guide()Retrieve the server's current colour palette and sizing rules. This call is mandatory β the values may differ from the defaults in this skill. The server guide takes precedence.
Step 3 β Clear and Confirm Empty
clear_canvas()
get_canvas_screenshot() // must verify the canvas is truly emptyPrevious diagrams leave ghost data even after clear_canvas. If any element
is visible in the screenshot, call clear_canvas() again before proceeding.
Do not skip this confirmation β ghost elements silently break arrow bindings
on new diagrams.
Step 4 β Plan the Layout
Before writing any JSON, decide:
- Which pattern? β See references/canvas-patterns.md for coordinate templates. Load it now if you are unsure which pattern fits.
- Column spacing β Labeled arrows need β₯150px clear gap between boxes. Budget 440px column pitch (230px box + 210px gap) for labeled arrows.
- Row pitch β Allow ~350px per row (160px box height + 190px gap for arrows, labels, and breathing room).
- Zone positions β Calculate zone backgrounds before placing shapes.
Zone background:
y = row_y - 50,height = box_height + 100.
Sketch coordinates to paper or comments before writing the batch payload.
Step 5 β Create in One Batch
Call batch_create_elements with all elements in one payload. Arrow
binding resolves at batch time β if the target shape and arrow are not in the
same call, the arrow will not connect.
Element order within the elements array matters for render layering:
- Zone backgrounds (large dashed rectangles, low opacity 25β40)
- Shapes (rectangles, ellipses, diamonds) β assign unique
idto each - Arrows β reference shapes by
startElementId/endElementId - Standalone text (titles, zone labels, side annotations)
Step 6 β Screenshot and Verify
get_canvas_screenshot()Inspect the image:
- Are all labels readable and unclipped by their containers?
- Do arrows land on the correct shapes?
- Are zone backgrounds behind their contents, not on top?
- Is there β₯150px of clear space around labeled arrows?
- Are connections logically correct?
Step 7 β Iterate and Refine
Load references/iterative-refinement.md if the user asks to change, fix, or update an existing diagram. Quick reference:
Audit first:
describe_scene() // returns all element ids, types, labels, positionsUse this before any edit β you need the exact element id to target.
Small corrections (label text, colour, position):
update_element({ id: "box-1", text: "New Label" })
update_element({ id: "box-1", backgroundColor: "#b2f2bb" })Then get_canvas_screenshot() to verify.
Shape replacement (size, shape type, structural change):
batch_create_elements([{ "type": "delete", "ids": "old-id" }, ...newShapes, ...newArrows])Arrows bound to the deleted shape must also be deleted and recreated in the same batch β binding cannot be re-attached after the fact.
Decision rule:
| Change | Method |
|---|---|
| Text / colour / opacity / position | update_element |
| Shape size, shape type | delete + redraw in one batch |
| Moving an arrow endpoint | delete + redraw arrow in one batch with target shape |
| Restructuring a zone (adding/removing shapes) | delete zone bg + all children + redraw all in one batch |
Step 8 β Zoom to Fit
set_viewport({ scrollToContent: true })Step 9 β Export (if requested)
// PNG or SVG
export_to_image({ format: "png", filePath: "/path/to/output.png" })
// Editable JSON file
export_scene({ filePath: "/path/to/output.excalidraw" })
// Shareable link β no file needed
export_to_excalidraw_url()Typography Rules
Minimum font sizes β never go smaller. Labels that look correct in JSON are frequently unreadable in screenshots at display scale.
| Context | Minimum fontSize |
Notes |
|---|---|---|
| Shape labels / body text | 16 |
Default for all labeled boxes |
| Diagram title | 24 |
Standalone text above the diagram |
| Zone / section heading | 16 |
Inside or above zone background |
| Secondary annotations | 14 |
Data form notes, layer labels only β use sparingly |
| Absolute minimum | 14 |
Never use below 14 under any circumstance |
Camera scale warning: At XXL (1600Γ1200) the canvas renders at roughly
40% of original size in the chat panel. A fontSize: 14 label renders at
~5px β invisible. Use fontSize: 20+ for any label that must be readable
without the user zooming in. When in doubt, go larger.
Shape Syntax
{
"type": "rectangle",
"id": "api-server",
"x": 440, "y": 200,
"width": 230, "height": 160,
"backgroundColor": "#d0bfff",
"strokeColor": "#7048e8",
"roughness": 0,
"text": "API Server\nExpress.js"
}roughness: 0β crisp professional edges.roughness: 1β hand-drawn feel.\nintextcreates multi-line labels.- Types:
rectangle,ellipse,diamond,text(standalone). - Zone backgrounds: same as rectangle but add
strokeStyle: "dashed"andopacity: 30.
Arrow Syntax
{
"type": "arrow",
"x": 0, "y": 0,
"startElementId": "api-server",
"endElementId": "database",
"strokeColor": "#2f9e44",
"text": "SQL"
}x, yare approximate β binding tostartElementId/endElementIdoverrides position. The server auto-routes to the nearest edges.startArrowhead/endArrowhead:"arrow","dot","bar", ornull.strokeStyle: "dashed"β async or optional flows.strokeStyle: "dotted"β weak dependency.
Colour Quick Reference
Read references/color-palette.md for the full semantic colour table β load it when choosing colours for a new diagram type.
| Role | Fill | Stroke |
|---|---|---|
| Frontend / UI | #a5d8ff |
#1971c2 |
| Backend / API | #d0bfff |
#7048e8 |
| Database | #b2f2bb |
#2f9e44 |
| Queue / Events | #fff3bf |
#fab005 |
| External / Third-party | #ffc9c9 |
#e03131 |
| Zone background | #e9ecef + opacity:30 |
#868e96 dashed |
Rule: Same architectural role β same colour pair. Limit diagrams to 3β4 fills. More colours add noise, not clarity.
Validation Checklist
Run through this before calling set_viewport. Fix any failures before
finishing β they compound and are harder to fix after zooming out.
Arrow bindings
- Every arrow has both
startElementIdandendElementIdreferencing elements that exist in the same batch - No arrow was created in a call before its target shapes
- Deleted shapes had their bound arrows deleted and redrawn in the same batch
Typography
- All shape labels use
fontSize β₯ 16 - Diagram title uses
fontSize β₯ 24 - No font smaller than
14anywhere - Multi-line labels (
\n) have enough box height (β₯ 60px per line)
Colour discipline
- No more than 4 distinct fill colours in the diagram
- Same architectural role uses the same fill/stroke pair throughout
- Zone backgrounds have
opacity: 25β40andstrokeStyle: "dashed"
Layout and spacing
- Column gap between adjacent labeled boxes is β₯ 150px
- Zone backgrounds were placed in the batch before the shapes they contain
- Zone backgrounds do not hug inner boxes β β₯ 50px padding on all sides
- Title is horizontally centred over the diagram content
Viewport
-
set_viewport({ scrollToContent: true })called as the final step - Camera size is one of the approved 4:3 ratios (400Γ300, 600Γ450, 800Γ600, 1200Γ900, 1600Γ1200)
Anti-Patterns (Quick Reference)
| Anti-pattern | Why it causes problems |
|---|---|
Skip screenshot after clear_canvas |
Ghost elements silently corrupt new arrow bindings |
| Place arrows before their target shapes in a batch | Arrow IDs reference non-existent shapes β no binding |
| Column gap under 100px | Arrow labels bleed into adjacent shapes |
Arrows without startElementId/endElementId |
Arrow floats, doesn't update when shapes move |
Skip set_viewport after creation |
Diagram is off-screen and user sees a blank canvas |
| Zone backgrounds in a separate later batch | Zones render on top of shapes already on the canvas |
| More than 4 fill colours | Diagram becomes unreadable without a legend |
fontSize below 16 on shape labels |
Unreadable at XL/XXL camera scale |
Editing a bound arrow with update_element |
Binding is preserved but endpoints may drift β delete and redraw |
Examples
"Draw me a 3-tier web architecture"
read_diagram_guide()β get coloursclear_canvas()βget_canvas_screenshot()β confirm empty- Plan: 3 zones (Frontend y=0, Backend y=360, Data y=720), 2 columns
batch_create_elements([zones..., shapes..., arrows..., titles...])get_canvas_screenshot()β verifyset_viewport({ scrollToContent: true })
"Quick flowchart of user login"
create_from_mermaid("graph TD; A[Enter email] --> B[Verify] --> C[Dashboard]")get_canvas_screenshot()β verify