Skip to main content

Excalidraw MCP Server

A high-performance, backend only Model Context Protocol (MCP) server for programmatically generating, editing, and managing Excalidraw v2 diagrams with obstacle aware arrow routing, font accurate text containment, and automated layout resolution.

Python FastMCP MCP Spec License: MIT Built with Hatchling


Overview

Excalidraw MCP empowers LLM agents (Claude, Cursor, OpenCode, Codex, and custom AI agents) to create, inspect, manipulate, and export rich Excalidraw diagrams directly from chat and automated workflows.

Built on FastMCP using Hatchling as the build backend, it runs entirely local via standard I/O (stdio). It requires no browser, no headless Chromium, and no third party cloud hosting, rendering deterministic, production grade .excalidraw (schema v2) documents safely and atomically.

Key Capabilities

  • Obstacle Aware Orthogonal Arrow Routing: Manhattan A* corridor pathfinding routes connectors cleanly around intermediate boxes, frames, and text with configurable clearance ($15\text{px}$). Arrows attach perpendicularly to cardinal faces (top, bottom, left, right) on rectangles, diamonds, and ellipses, preventing clipped arrowheads and wire collisions.
  • Multi Edge Parallel Lanes: Automatically routes multiple connectors between the same components into distinct, parallel tracks ($20\text{px}$ lane spacing) so arrows and labels never superimpose.
  • Collision Free Label Positioning: Ranks corridor segments to position connector labels along the longest clear segment away from arrowheads and obstacle boxes.
  • Font Aware Text Containment: Accurate FreeType/Pillow font measurement (supporting Excalifont, Helvetica, Cascadia, and Comic Shanns) automatically wraps text and expands shapes outward from center. Rhombus and ellipse bounding geometry ensures labels never escape container borders.
  • Two Pass Architecture Layout Engine: Content first sizing calculates text dimensions before positioning containers, automatically aligning multi tier architectures (Main Stack, Side Columns, Banners) with zero overlaps.
  • Dynamic 8K Canvas Scaling: Content aware export bounds calculate margins from all visible elements, generating crystal clear high resolution PNGs up to 8192px without downscaling or text truncation.
  • Automated Geometry Diagnostics: check_diagram_geometry automatically detects shape to shape overlaps, gap violations, frame containment escapes, and connector collisions.
  • Mermaid & Architecture Shorthand: Directly parse Mermaid flowcharts (draw_mermaid) or shorthand prompts (draw_architecture) into structured, fully editable Excalidraw scenes.
  • Syntax Highlighted Code Blocks: add_code_block renders syntax highlighted code with line numbers, dark/light themes, and window controls.
  • Interactive Live Canvas & WebSocket Sync: Built in HTTP and WebSocket server (start_live_server) synchronizes canvas changes with browser clients in real time.
  • End-to-End Encrypted Sharing: Client side 128 bit AES-GCM encryption (export_to_excalidraw_url) publishes shareable links directly to excalidraw.com.
  • Atomic Operations & In-Memory Undo: Non blocking transactions with process local undo history (undo) and memory budgets.
  • Automatic Agent Discovery: Ships with a root mcp.json and uvx support for zero config onboarding across modern AI coding environments.

Architecture & Workflow

flowchart TD
    subgraph Agent["AI Agent / Host Client"]
        Client["Claude / Cursor / OpenCode / Codex"]
    end

    subgraph Server["Excalidraw MCP Server (stdio)"]
        FastMCP["FastMCP Framework"]
        Service["Scene Service & Transaction Guard"]
        State[("In-Memory SceneState")]
        History[("Undo History Stack")]
        
        subgraph LayoutEngines["Layout & Routing Engines"]
            Router["Orthogonal Router (A* Manhattan)"]
            Containment["Text Containment & Font Metrics"]
            AutoLayout["Two-Pass Architecture Layout"]
            Diagnostics["Geometry Diagnostics"]
        end
        
        Renderer["PNG Renderer (8K Dynamic Scaling)"]
        Storage["Atomic Storage Manager"]
    end

    subgraph Output["Artifacts & UI"]
        Disk[("Local Storage (.excalidraw)")]
        PNG["Rendered High-Res PNG"]
        WebShare["excalidraw.com (AES-GCM)"]
        LiveCanvas["Browser Live Canvas (WebSocket)"]
    end

    Client <-->|"MCP Protocol (JSON-RPC over stdio)"| FastMCP
    FastMCP --> Service
    Service <--> State
    Service <--> History
    Service --> LayoutEngines
    Service --> Renderer
    Service --> Storage
    Storage --> Disk
    Renderer --> PNG
    Service --> WebShare
    Service --> LiveCanvas

Agent Workflow

  1. Creation: Agent invokes draw_graph, draw_architecture, draw_mermaid, or primitive shape tools (add_rectangle, add_circle, connect).
  2. Layout & Routing: Layout engine positions components into balanced tiers and routes connectors through obstacle free corridors with parallel lane separation.
  3. Inspection & Revision: Agent verifies element placement using get_scene, get_element, or check_geometry. If adjustments are needed, it calls move, resize, update_element, reroute_connectors, or undo.
  4. Persistence or Export: The scene is saved locally with save(name="arch-v1"), exported as a high resolution image via export_png / preview, or published to excalidraw.com via export_to_excalidraw_url.

Quick Start

Run instantly without cloning the repository or managing virtual environments using uvx:

uvx --from git+https://github.com/Dharshan2208/excalidraw-mcp.git excalidraw-mcp

Or install it globally to your FastMCP environment:

fastmcp install mcp.json

Client Configuration

Add excalidraw-mcp to your favorite MCP client configuration file:

Claude Desktop

Add this to claude_desktop_config.json:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "excalidraw": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/Dharshan2208/excalidraw-mcp.git",
        "excalidraw-mcp"
      ]
    }
  }
}

Cursor

Add to .cursor/mcp.json in your project root or user configuration:

{
  "mcpServers": {
    "excalidraw": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/Dharshan2208/excalidraw-mcp.git",
        "excalidraw-mcp"
      ]
    }
  }
}

OpenCode

Add to opencode.json (or the MCP configuration section of your OpenCode settings):

{
  "mcpServers": {
    "excalidraw": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/Dharshan2208/excalidraw-mcp.git",
        "excalidraw-mcp"
      ]
    }
  }
}

Codex / Generic Agent Configuration

For CLI agents and runners accepting standard mcpServers JSON:

{
  "mcpServers": {
    "excalidraw": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/Dharshan2208/excalidraw-mcp.git",
        "excalidraw-mcp"
      ],
      "env": {
        "EXCALIDRAW_HISTORY_LIMIT": "25"
      }
    }
  }
}

Automatic Agent Discovery

This repository comes pre-packaged with a root mcp.json descriptor:

{
  "mcpServers": {
    "excalidraw": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/Dharshan2208/excalidraw-mcp.git",
        "excalidraw-mcp"
      ]
    }
  }
}

When an MCP compliant agent tool (e.g. fastmcp list mcp.json, Cursor, or Zed) scans your workspace, it will automatically detect the server and register all 46 tools with zero manual configuration.


MCP Tools Reference

The server exports 46 typed tools designed with strict JSON schema bounds and explicit safety hints (readOnly, destructive, idempotent).

1. High Level Generation & Design Guidance

Tool Inputs Outputs Description
draw_graph nodes: list[GraphNode]
edges: list[GraphEdge]
clusters: list[GraphCluster] = None
layout: str = "layered" (layered, horizontal, vertical)
direction: str = "horizontal" (horizontal, vertical)
elbowed: bool = False
`roundness: str
None = None` GraphResult
{ revision, element_count, node_map, edge_map, cluster_map }
draw_mermaid mermaid_code: str
`direction: str
None = None<br>layout: str = "layered"<br>elbowed: bool = False<br>roundness: str None = None`
draw_architecture prompt: str (e.g., "box: Frontend, db: Postgres")
wrap_threshold: int = 4
Optional styles (strokeColor, backgroundColor, etc.)
ArchitectureResult
{ revision, element_count, components }
Rapid diagram generator parsing comma/newline-separated shorthand syntax (box, circle, diamond) with grid-wrapping and automatic obstacle-avoiding connectors.
read_diagram_guide `topic: str None = None (layout, colors, typography, grouping, arrows, spacing` ) DiagramGuideResult
{ topic, guide, available_topics }

2. Primitive Shapes, Text, Code Blocks & Frames

Tool Inputs Outputs Description
add_rectangle x: float, y: float, w: float, h: float
label: str = "" + styles + roundness: str = None, frame_id: str = None
CreateResult
{ rectangle_id, label_id }
Adds a rectangle to the active scene with an optional centered bound label, adaptive/round corner roundness, frame membership, and automatic outward expansion.
add_circle x: float, y: float, size: float
label: str = "" + styles + frame_id: str = None
CreateResult
{ circle_id, label_id }
Adds a circle (ellipse) with an optional centered bound label, ellipse inscribed boundary containment, and frame membership.
add_diamond x: float, y: float, w: float, h: float
label: str = "" + styles + roundness: str = None, frame_id: str = None
CreateResult
{ diamond_id, label_id }
Adds a decision diamond with rhombus inscribed boundary containment, adaptive roundness, and frame membership.
add_arrow x: float, y: float, x2: float, y2: float
label: str = "" + styles + elbowed: bool = False
CreateResult
{ arrow_id, label_id }
Adds a directional arrow (straight or elbowed) with an optional corridor positioned label.
add_text x: float, y: float, content: str
font_size: int = 20 + styles + frame_id: str = None
CreateResult
{ text_id }
Creates a standalone text element with accurate FreeType font metrics and optional frame membership.
add_code_block code: str
language: str = "python"
x: float = 0, y: float = 0
title: str = None
theme: str = "dark"
font_size: int = 14
line_numbers: bool = True
w: float = None, h: float = None
frame_id: str = None
CodeBlockResult
{ status, container_id, text_id, width, height, revision }
Adds a syntax highlighted code block with window controls, configurable themes, line numbers, and monospace typography.
connect source_id: str, target_id: str
label: str = "" + styles + elbowed: bool = False
ConnectResult
{ arrow_id, label_id }
Binds two existing shapes with obstacle aware orthogonal routing, cardinal face boundary docking, and multi edge lane separation.
add_frame name: str = "", x: float, y: float, w: float, h: float
element_ids: list[str] = None
CreateResult
{ frame_id }
Creates an Excalidraw frame container and optionally parents specified elements inside it.

3. Scene Editing & Manipulation

Tool Inputs Outputs Description
duplicate_elements element_ids: list[str]
offset_x: float = 20.0
offset_y: float = 20.0
DuplicateResult
{ status, original_ids, duplicated_ids, id_mapping, revision }
Duplicates multiple elements with spatial offsets, relationship remapping, arrow endpoint retargeting, bound label cloning, and group isolation.
move element_id: str, x: float, y: float MoveResult
{ status, element_id, x, y }
Repositions an element. Moving a frame moves all contained elements; moving grouped elements moves group members together; rejects locked elements.
resize element_id: str, width: float, height: float ResizeResult
{ status, element_id, width, height }
Resizes a shape or frame, recalculates label centering, and reroutes connected arrows; rejects locked elements.
update_element element_id: str
`label: str
None = None+ styles +roundness, elbowed, frame_id` UpdateResult
{ status, element_id, label_id }
delete element_id: str DeleteResult
{ status, deleted_ids }
Deletes an element and its bound label; unparents frame children if a frame is deleted; cleans up references in connected arrows; rejects locked elements.
group_elements element_ids: list[str] GroupResult
{ status, group_id, element_ids, revision }
Groups multiple elements together so that movements and transformations apply cohesively.
ungroup_elements element_ids: list[str] = None
group_id: str = None
UngroupResult
{ status, affected_elements, removed_group_ids, revision }
Disbands an element group by group_id or removes outermost groups from specified elements.
lock_elements element_ids: list[str] LockResult
{ status, element_ids, revision }
Locks elements and bound labels to protect them from unintended moves, resizes, updates, or deletions.
unlock_elements element_ids: list[str] UnlockResult
{ status, element_ids, revision }
Unlocks previously locked elements and bound labels for subsequent editing.
batch_mutate operations: list[dict] BatchResult
{ status, operations_executed, ref_map, results, revision }
Atomically executes multiple creation, connection, movement, resize, lock, group, duplicate, or update actions in a single revision step with dynamic client_id resolution.
clear None DeleteResult
{ status }
Wipes the entire scene, active elements, and embedded state.
undo None UndoResult
{ status, revision }
Rolls back the scene to the state immediately preceding the latest mutation.

4. Inspection, Viewport, Query, Export & Sharing

Tool Inputs Outputs Description
describe_scene detailed: bool = True SceneDescriptionResult
{ summary, element_count, layout_type, elements, connections, bounds, revision }
Produces an AI-readable summary of elements, positions, labels, flow connections, topological layout, and bounds.
get_scene_summary None SceneSummaryResult
{ element_count, type_counts, frames, groups, connectors, code_blocks, standalone_elements, bounds, description, revision }
Provides a structured semantic overview and topological understanding of the entire diagram for AI agent reasoning.
get_resource `uri: str None = None<br>resource_type: str None = None<br>identifier: str
import_scene data: str
mode: str = "replace" (replace, merge)
offset_x: float = 0.0
offset_y: float = 0.0
ImportResult
{ status, mode, elements_imported, total_elements, id_mapping, revision }
Imports Excalidraw scenes from raw JSON, .excalidraw files, Obsidian .excalidraw.md files, or embedded PNGs with reference preservation and collision remapping.
export_to_excalidraw_url `name: str None = None` ShareUrlResult
{ status, url, id, revision }
get_viewport None ViewportResult
{ scroll_x, scroll_y, zoom, view_background_color, revision }
Inspects current canvas camera scroll coordinates, zoom factor, and background color.
set_viewport scroll_x: float = None
scroll_y: float = None
zoom: float = None
background_color: str = None
ViewportResult
{ scroll_x, scroll_y, zoom, view_background_color, revision }
Updates canvas camera viewport scroll offsets, zoom level, or background canvas color.
focus_elements element_ids: list[str] = None
padding: int = 50
FocusResult
{ status, scroll_x, scroll_y, zoom, bounding_box, focused_element_ids, revision }
Automatically calculates bounding box, centers camera, and sets fit zoom to frame specified elements or the entire diagram.
find_elements `query: str None<br>type: str None<br>frame_id: str
list_fonts `category: str None (sans-serif, monospace, serif, handwriting`) FontListResult
{ fonts, total, defaults }
get_scene offset: int = 0, limit: int = 50 ScenePage
{ elements, total, offset, limit, has_more, revision }
Paginated inspection of all elements currently on the canvas.
get_element element_id: str ElementResult
{ element, revision }
Fetches details and properties of a single element by its ID.
list_saved_scenes None SavedScenesResult
{ scenes: list[str] }
Returns a list of all scene files stored in the persistent directory.
save name: str, overwrite: bool = False SaveResult
{ status, name, path }
Atomically writes the scene to server disk in .excalidraw JSON format.
load name: str LoadResult
{ status, name, elements }
Atomically loads a saved .excalidraw file and sets it as the active scene.
export_scene None EmbeddedResource
(application/vnd.excalidraw+json)
Emits the entire .excalidraw schema v2 document directly into the MCP message.
preview scale: float = 1.0
padding: int = 40
background: bool = True
ImageContent
(image/png)
Renders an inline PNG image preview of the active Excalidraw canvas directly into the chat conversation with dynamic canvas scaling up to 8K.
get_canvas_screenshot scale: float = 1.0
padding: int = 40
background: bool = True
ImageContent
(image/png)
Alias for preview. Renders the current canvas into an inline PNG screenshot containing embedded application/vnd.excalidraw+json metadata.
export_png name: str
`directory: str
None = None<br>scale: float = 1.0<br>padding: int = 40<br>background: bool = True<br>overwrite: bool = False` PngExportResult
{ status, name, path, bytes, width, height }

5. Live Canvas Server & Browser Synchronization

Tool Inputs Outputs Description
start_live_server host: str = "127.0.0.1"
port: int = 8080
LiveServerResult
{ status, url, host, port }
Launches the interactive browser connected live canvas server with real-time WebSocket synchronization.
stop_live_server None LiveServerResult
{ status }
Gracefully shuts down the background live canvas server and closes active client connections.
get_live_server_status None LiveServerResult
{ is_running, url, host, port, active_clients }
Reports operational status, connection URL, and connected client count of the live canvas server.

MCP Resources

In addition to tools, the server exposes saved scenes via standard MCP resource URIs:

  • URI Template: excalidraw://scenes/{name}.excalidraw
  • MIME Type: application/vnd.excalidraw+json

Clients can directly read saved scenes into their context window without altering the active working canvas.


Environment Variables & Configuration

The server operates with secure defaults and zero required environment variables. Fine tune performance and resource limits via the environment:

Variable Default Allowed Values Description
EXCALIDRAW_SCENE_DIR Platform User Data Absolute path Directory where scenes are persisted. Defaults to $XDG_DATA_HOME/excalidraw-mcp/scenes on Linux, ~/Library/Application Support/excalidraw-mcp/scenes on macOS, and %APPDATA%\excalidraw-mcp\scenes on Windows.
EXCALIDRAW_EXPORT_DIR $EXCALIDRAW_SCENE_DIR/exports Absolute path Configurable directory where PNG diagram exports are saved by default.
EXCALIDRAW_DEFAULT_FONT Platform Sans Font Font family name Preferred system font family for regular diagram text and labels.
EXCALIDRAW_MONO_FONT Platform Monospace Font Font family name Preferred monospace font family for code blocks and code annotations.
EXCALIDRAW_FONT_DIR None Directory path Additional custom directory scanned for TrueType (.ttf) and OpenType (.otf) fonts.
EXCALIDRAW_HISTORY_LIMIT 20 1–100 Maximum number of in-memory undo snapshots retained per session.
EXCALIDRAW_MAX_HISTORY_BYTES 104857600 (100 MiB) 1 KiB–500 MiB Shared memory ceiling allocated for serialized undo states.
EXCALIDRAW_MAX_SCENE_BYTES 26214400 (25 MiB) 1 KiB–100 MiB Maximum file size permitted when saving or loading scene files.
EXCALIDRAW_MAX_EXPORT_BYTES 26214400 (25 MiB) 1 KiB–100 MiB Maximum payload size permitted for export_scene and resource reads.
EXCALIDRAW_MAX_ELEMENTS 10000 1–100,000 Hard cap on the total number of elements allowed per scene.

Local Development & Debugging

Prerequisites

  • Python >= 3.12
  • uv (recommended) or pip

1. Clone & Set Up Environment

git clone https://github.com/Dharshan2208/excalidraw-mcp.git
cd excalidraw-mcp

# Synchronize dependencies with uv
uv sync

# Or with pip & virtualenv
python3 -m venv .venv
source .venv/bin/activate
pip install -e .

2. Run the Test Suite

The test suite contains 353 comprehensive unit and contract tests verifying Excalidraw serialization, obstacle aware arrow routing, text containment and font metrics, two pass architecture layout, 8K dynamic scaling, storage safety, PNG export, code blocks, live server synchronization, sharing, and MCP schemas:

uv run --locked python -B -m unittest discover -s tests -v

3. Interactive Debugging with FastMCP Inspector

Inspect schemas, invoke tools interactively, and test stdio communications using the built-in FastMCP web inspector:

uv run fastmcp inspect mcp.json

Or list discovered tools:

uv run fastmcp list mcp.json

4. Packaging Verification

Test source distribution and wheel generation with Hatchling:

uv build

Contributing

Contributions are warmly welcomed! Please follow these guidelines:

  1. Fork the Repository: Create a descriptive feature branch (git checkout -b feat/custom-styling).
  2. Adhere to Code Standards: Ensure clean type annotations, preserve existing docstrings, and maintain standard exception hierarchies.
  3. Verify Tests: Run uv run --locked python -B -m unittest discover -s tests -v and ensure all 353 tests pass.
  4. Compile Check: Confirm no byte compilation warnings via:
    uv run python -B -m compileall -q -f main.py excalidraw_mcp tests
    
  5. Submit a Pull Request: Provide a concise summary of the rationale and changes.

Standards & License

Metadata

Release files for exca-mcp 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for exca-mcp 0.1.0
File Size Uploaded Provenance
exca_mcp-0.1.0.tar.gz 310.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for exca-mcp 0.1.0
File Interpreter ABI Platform
exca_mcp-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 502.3 kB

Release files / exca_mcp-0.1.0.tar.gz

Download URL exca_mcp-0.1.0.tar.gz
Size 310.6 kB
Tags Source
SHA-256 checksum
How to use checksums
064b869d43c204a7b9118eab60c1cd72a60df19255d08920579276c9620726f6
BLAKE2b-256 checksum
How to use checksums
a4a54465428aa888f11ca46afad4a3b96359269b813af6b05c1992ad140b2285
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.11

Release files / exca_mcp-0.1.0-py3-none-any.whl

Download URL exca_mcp-0.1.0-py3-none-any.whl
Size 191.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bc1e4259ca79bc6966968b294ca40cf6a1eeb4b57288fae4995808b3b64a2899
BLAKE2b-256 checksum
How to use checksums
6bc540d53b6fe810c4d4066c9af057e78105985299928fe1b7058ff49b2b221c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.11

Security for exca-mcp 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page