Excalidraw MCP Server

A local Excalidraw canvas that assistants can draw on, inspect, screenshot and export to files

By yctimlin (individual) TypeScript Local · stdio Design

79 popularity score ★ 2.6k ↓ 3.2k/wk

Connect your client

Local (npm)

Add in one click

Opens the app, which asks before it adds the server.

Or add it by command or config

Claude CodeTerminal
claude mcp add excalidraw -- npx -y mcp-excalidraw-server
Claude Desktopclaude_desktop_config.json
{
  "mcpServers": {
    "excalidraw": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-excalidraw-server"
      ]
    }
  }
}
Cursor~/.cursor/mcp.json
{
  "mcpServers": {
    "excalidraw": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-excalidraw-server"
      ]
    }
  }
}
VS Code.vscode/mcp.json
{
  "servers": {
    "excalidraw": {
      "type": "stdio",
      "command": "npx",
      "args": [
        "-y",
        "mcp-excalidraw-server"
      ]
    }
  }
}
Codex~/.codex/config.toml
[mcp_servers.excalidraw]
command = "npx"
args = ["-y", "mcp-excalidraw-server"]
Gemini CLITerminal
gemini mcp add --scope user excalidraw npx -- -y mcp-excalidraw-server
Local (container)

Add in one click

Opens the app, which asks before it adds the server.

Or add it by command or config

FirstTerminal
docker run -d -p 3000:3000 --name mcp-excalidraw-canvas ghcr.io/yctimlin/mcp_excalidraw-canvas:latest
Claude CodeTerminal
claude mcp add excalidraw -- docker run -i --rm -e EXPRESS_SERVER_URL=http://host.docker.internal:3000 -e ENABLE_CANVAS_SYNC=true ghcr.io/yctimlin/mcp_excalidraw:latest
Claude Desktopclaude_desktop_config.json
{
  "mcpServers": {
    "excalidraw": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e",
        "EXPRESS_SERVER_URL=http://host.docker.internal:3000",
        "-e",
        "ENABLE_CANVAS_SYNC=true",
        "ghcr.io/yctimlin/mcp_excalidraw:latest"
      ]
    }
  }
}
Cursor~/.cursor/mcp.json
{
  "mcpServers": {
    "excalidraw": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e",
        "EXPRESS_SERVER_URL=http://host.docker.internal:3000",
        "-e",
        "ENABLE_CANVAS_SYNC=true",
        "ghcr.io/yctimlin/mcp_excalidraw:latest"
      ]
    }
  }
}
VS Code.vscode/mcp.json
{
  "servers": {
    "excalidraw": {
      "type": "stdio",
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e",
        "EXPRESS_SERVER_URL=http://host.docker.internal:3000",
        "-e",
        "ENABLE_CANVAS_SYNC=true",
        "ghcr.io/yctimlin/mcp_excalidraw:latest"
      ]
    }
  }
}
Codex~/.codex/config.toml
[mcp_servers.excalidraw]
command = "docker"
args = ["run", "-i", "--rm", "-e", "EXPRESS_SERVER_URL=http://host.docker.internal:3000", "-e", "ENABLE_CANVAS_SYNC=true", "ghcr.io/yctimlin/mcp_excalidraw:latest"]
Gemini CLITerminal
gemini mcp add --scope user excalidraw docker -- run -i --rm -e EXPRESS_SERVER_URL=http://host.docker.internal:3000 -e ENABLE_CANVAS_SYNC=true ghcr.io/yctimlin/mcp_excalidraw:latest
Local (from source)

Add in one click

Opens the app, which asks before it adds the server. Then replace each <NAME> in its settings with your value.

Or add it by command or config

FirstTerminal
git clone https://github.com/yctimlin/mcp_excalidraw.git
cd mcp_excalidraw
npm ci
npm run build
Claude CodeTerminal
claude mcp add excalidraw -- node '<ABSOLUTE_PATH_TO_CLONE>/dist/index.js'
Claude Desktopclaude_desktop_config.json
{
  "mcpServers": {
    "excalidraw": {
      "command": "node",
      "args": [
        "<ABSOLUTE_PATH_TO_CLONE>/dist/index.js"
      ]
    }
  }
}
Cursor~/.cursor/mcp.json
{
  "mcpServers": {
    "excalidraw": {
      "command": "node",
      "args": [
        "<ABSOLUTE_PATH_TO_CLONE>/dist/index.js"
      ]
    }
  }
}
VS Code.vscode/mcp.json
{
  "servers": {
    "excalidraw": {
      "type": "stdio",
      "command": "node",
      "args": [
        "<ABSOLUTE_PATH_TO_CLONE>/dist/index.js"
      ]
    }
  }
}
Codex~/.codex/config.toml
[mcp_servers.excalidraw]
command = "node"
args = ["<ABSOLUTE_PATH_TO_CLONE>/dist/index.js"]
Gemini CLITerminal
gemini mcp add --scope user excalidraw node -- '<ABSOLUTE_PATH_TO_CLONE>/dist/index.js'

Settings 9

EXPRESS_SERVER_URL Optional: Address of the canvas server the MCP server drives. Default http://127.0.0.1:3000.
ENABLE_CANVAS_SYNC Optional: Set to false to turn off syncing to the canvas. Default true.
EXCALIDRAW_NO_AUTOSTART Optional: Set to 1 to stop the MCP server starting the canvas itself (the Docker MCP image sets it).
EXCALIDRAW_EXPORT_DIR Optional: Folder that file paths for exports and imports must resolve inside. Default the working directory. For export_scene, import_scene, export_to_image.
EXCALIDRAW_RENDER_MAX_DIM Optional: Largest side of a headless PNG in pixels. Default 8192. For get_canvas_screenshot, export_to_image.
LOG_LEVEL Optional: Log detail: error, warn, info or debug. Default info.
LOG_FILE_PATH Optional: Where the log file is written. Default ~/.local/state/excalidraw-mcp/excalidraw.log on Linux, ~/Library/Logs/excalidraw-mcp.log on macOS.
Set together: canvas server
PORT Optional: Port the canvas server listens on. Default 3000.
HOST Optional: Address the canvas server binds to; 0.0.0.0 exposes the unauthenticated API. Default 127.0.0.1.

In a container, a setting goes in with -e NAME=value after docker run.

What it does

mcp-excalidraw-server runs an Excalidraw canvas on your own machine and gives an assistant element-level control over it. The assistant can add, read, change and delete single shapes, arrows and text, line them up, group or lock them, and then check its own work. It can ask for a text summary of the scene or a PNG screenshot and use these to fix labels that are cut off or shapes that overlap.

The package has two parts. A canvas server (web editor, REST API and WebSocket sync, on 127.0.0.1:3000 by default) holds the scene. The MCP server talks stdio to your client and drives that canvas, and it starts the canvas itself if nothing is listening. The same package also ships a command-line tool and an agent skill, and the project recommends those for coding agents. The MCP server is aimed at chat clients such as Claude Desktop. Version 2.1.2 registers 26 tools and no MCP resources or prompts.

Finished diagrams can be saved as .excalidraw files to commit next to code, or as .excalidraw.md files for the Obsidian Excalidraw plugin. They can also be exported as PNG or SVG, built from Mermaid text, or uploaded to excalidraw.com as an encrypted share link. Named snapshots let the assistant roll back within a session. Screenshots and image exports render without a browser since version 2.1.

The current README also documents durable canvas state (EXCALIDRAW_DATA_DIR) and a configurable share target for self-hosted Excalidraw (EXCALIDRAW_SHARE_UPLOAD_URL, EXCALIDRAW_SHARE_BASE_URL). Both were added on the main branch after the 2.1.2 release of 2026-10-04 and are not in the published npm package: at the v2.1.2 tag the share upload address is fixed in src/core/share-url.ts.

This is a community project, not the Excalidraw team's own MCP server. Its README contrasts the two: the official server streams one diagram into a chat, while this one keeps a lasting canvas that the assistant edits element by element.

What it needs

RuntimeNode.js 20+
Runs onmacOS, Windows, Linux
Also
  • A browser tab open on the canvas, for Mermaid conversion and set_viewport only

Node.js 20 or later for the npx route; no account or API key. Docker is optional: the MCP image does not start a canvas, so run the separate canvas image (ghcr.io/yctimlin/mcp_excalidraw-canvas) first. Mermaid conversion and viewport control need the canvas page open at http://127.0.0.1:3000.

Limits

  • Limit In 2.1.2 the canvas is held in memory: a restart clears elements and named snapshots unless you exported them to a file
  • Not covered Durable state (EXCALIDRAW_DATA_DIR) and a self-hosted share target are on the main branch only, not in npm release 2.1.2
  • Not covered export_to_excalidraw_url in 2.1.2 always uploads to json.excalidraw.com and returns an excalidraw.com link
  • Limit File tools only take paths inside EXCALIDRAW_EXPORT_DIR. The README's symlink check and .excalidraw/.json/.md/.png/.svg filter are not in 2.1.2
  • Limit create_from_mermaid and set_viewport fail without the canvas open in a browser tab
  • Size cap Headless PNGs are capped at 8192 px on the largest side by default (EXCALIDRAW_RENDER_MAX_DIM)
  • Limit Headless renders bundle Latin fonts only; CJK and emoji depend on fonts installed on the machine
  • Limit The canvas page loads its hand-drawn fonts from the esm.sh CDN and falls back to system fonts offline
  • Limit The canvas REST API has no authentication of its own

By default the canvas lives in memory, so a restart clears it unless EXCALIDRAW_DATA_DIR is set. Durable state supports one writer on a local file system, not network shares. Mermaid conversion and set_viewport do not work without an open browser tab. Headless renders only bundle fonts for Latin scripts, so CJK and emoji depend on fonts installed on the machine. The canvas page loads its hand-drawn fonts from the esm.sh CDN and falls back to system fonts when offline. The canvas API has no authentication of its own.

Who it suits

Suits developers who want an assistant to produce editable architecture diagrams or flowcharts, refine them over several steps and keep them in a repository. It runs locally with no key. For a terminal coding agent, its README points to the bundled CLI and skill rather than MCP setup. Teams that need share links on their own Excalidraw instance, or a canvas that survives restarts, have to build from main until a release carries those features. If you only want a quick one-off diagram shown inside a chat, the official Excalidraw MCP server (github.com/excalidraw/excalidraw-mcp) is the lighter choice: it streams a diagram from one prompt but has no element-level editing or file export.

Tools 26

7 only read, 16 change things, 3 can delete or overwrite. A ? marks an optional parameter.

create_element changes
Add one shape, arrow or text element; arrows can bind to shapes by idtype: enumx: numbery: numberwidth?: numberheight?: numberid?: stringtext?: stringbackgroundColor?: stringstrokeColor?: stringstrokeWidth?: numberstrokeStyle?: stringroughness?: numberopacity?: numberfontSize?: numberfontFamily?: anystartElementId?: stringendElementId?: stringstartArrowhead?: stringendArrowhead?: string
get_element
Fetch a single element by its idid: string
update_element changes
Change the properties of an existing elementid: stringtype?: enumx?: numbery?: numberwidth?: numberheight?: numbertext?: stringbackgroundColor?: stringstrokeColor?: stringstrokeWidth?: numberstrokeStyle?: stringroughness?: numberopacity?: numberfontSize?: numberfontFamily?: any
delete_element deletes
Remove an element from the canvasid: string
query_elements
Find elements by type, property filter or bounding boxtype?: enumfilter?: objectbbox?: object
batch_create_elements changes
Add many elements, including bound arrows, in one callelements: array
duplicate_elements changes
Copy elements with an offsetelementIds: arrayoffsetX?: numberoffsetY?: number
align_elements changes
Line up elements along an edge or centre lineelementIds: arrayalignment: enum
distribute_elements changes
Space elements evenly in one directionelementIds: arraydirection: enum
group_elements changes
Join elements into a groupelementIds: array
ungroup_elements changes
Break a group back into separate elementsgroupId: string
lock_elements changes
Stop elements from being moved or editedelementIds: array
unlock_elements changes
Make locked elements editable againelementIds: array
describe_scene
Return a text summary of elements, positions, labels and links
get_canvas_screenshot
Render the canvas to an image the assistant can look atbackground?: booleanrenderer?: enumdark?: booleanscale?: numberpadding?: numberelementIds?: arrayframeId?: string
export_scene changes
Return the scene as .excalidraw JSON, or write it to a .excalidraw or .excalidraw.md filefilePath?: string
import_scene changes
Load a .excalidraw file, an Obsidian .excalidraw.md file or raw JSON onto the canvasmode: enumfilePath?: stringdata?: string
export_to_image changes
Export the scene or part of it as PNG or SVGformat: enumfilePath?: stringbackground?: booleanrenderer?: enumdark?: booleanscale?: numberpadding?: numberelementIds?: arrayframeId?: string
export_to_excalidraw_url changes
Encrypt the scene, upload it to excalidraw.com and return a share link
create_from_mermaid changes
Turn a Mermaid diagram into Excalidraw elements (needs an open canvas tab)mermaidDiagram: stringconfig?: object
clear_canvas deletes
Remove every element from the canvas
snapshot_scene changes
Save a named snapshot of the current scenename: string
restore_snapshot deletes
Return the canvas to a named snapshotname: string
set_viewport
Zoom or scroll the open canvas tab to content or elementsscrollToContent?: booleanscrollToElementIds?: arrayscrollToElementId?: stringviewportZoomFactor?: numberzoom?: numberoffsetX?: numberoffsetY?: number
read_diagram_guide
Get the project's guidance on laying out clear diagrams
get_resource
Read the scene, library, theme or element listresource: enum

Similar servers