- Use fs.realpathSync for entry point detection to fix npx symlink failures - Add --help and --version CLI flags with explicit process.exit(0) - Add TTY detection in setup wizard to prevent non-interactive hangs - Wrap JSON.parse in mergeJsonConfig with try/catch for malformed configs - Update engines.node to >=20.0.0 to match better-sqlite3 requirements - Update Dockerfiles from node:18-slim to node:20-slim - Remove error-swallowing || true from postinstall script - Replace deprecated windows-build-tools with VS Build Tools link
19 KiB
MCP Excalidraw Local
A fully local, self-hosted Excalidraw MCP server with SQLite persistence, multi-tenancy, and auto-sync — designed to run entirely on your machine without depending on excalidraw.com.
Run a live Excalidraw canvas and control it from any AI agent. This repo provides:
- MCP Server: 32 tools over stdio — works with any MCP-compatible client
- Agent Skill: Portable skill with workflow playbooks, cheatsheets, and helper scripts
- Live Canvas: Real-time Excalidraw UI synced via WebSocket
- SQLite Persistence: Elements survive restarts, with versioning and search
- Multi-Tenancy: Isolated canvases per workspace, auto-detected
Fork notice: This project is forked from yctimlin/mcp_excalidraw and extends it with persistence, multi-workspace support, and numerous UX improvements. Full credit to the original author for the excellent foundation. See What Changed From Upstream for details.
Keywords: Excalidraw MCP server, AI diagramming, local Excalidraw, self-hosted, SQLite persistence, multi-tenant, Mermaid to Excalidraw.
Screenshots
Canvas UI
The live Excalidraw canvas with toolbar, connection status, sync controls, and workspace badge:
Workspace Switcher
Click the workspace badge to switch between isolated canvases — each workspace has its own set of diagrams:
For a demo of the upstream project (before persistence/multi-tenancy), see the original video by @yctimlin.
Table of Contents
- Screenshots
- Prerequisites
- Quick Start
- Configuration
- Verify Installation
- How We Differ from the Official Excalidraw MCP
- What Changed From Upstream
- Architecture
- Environment Variables
- Multi-Tenancy (Workspaces)
- Agent Skill (Optional)
- MCP Tools (32 Total)
- Testing
- Troubleshooting
- Known Issues / TODO
- Development
- Credits
Prerequisites
| Requirement | Why | Check |
|---|---|---|
| Node.js >= 20 (LTS 20 or 22 recommended) | Runtime | node --version |
| C++ build tools | better-sqlite3 compiles native bindings |
See below |
| npm (bundled with Node.js) | Package manager | npm --version |
C++ build tools by platform
macOS:
xcode-select --install
Ubuntu / Debian:
sudo apt install build-essential python3
Windows: Install "Desktop development with C++" from Visual Studio Build Tools.
better-sqlite3ships prebuilt binaries for most Node LTS versions. The build tools are only needed when a prebuilt binary isn't available for your platform/Node combination.
Quick Start
Path A: Interactive Setup (recommended for first-time users)
The setup wizard checks your environment, optionally installs the agent skill, and configures MCP clients — all interactively. Every step is skippable.
npx @sanjibdevnath/mcp-excalidraw-local setup
Example session
$ npx @sanjibdevnath/mcp-excalidraw-local setup
Excalidraw MCP — Setup
[1/3] Environment
✔ Node.js v22.12.0 .................. OK
✔ better-sqlite3 bindings ........... OK
✔ Frontend build .................... OK
[2/3] Agent Skill
Install the Excalidraw agent skill? [Y/n]: Y
Detected agents:
[1] Cursor (~/.cursor)
[2] Claude Code (~/.claude)
Which agents? (comma-separated, 'all', or 'skip'): all
Cursor — scope? [G]lobal / [l]ocal: G
✔ Installed to ~/.cursor/skills/excalidraw-skill/
Claude Code — scope? [G]lobal / [l]ocal: G
✔ Installed to ~/.claude/skills/excalidraw-skill/
[3/3] MCP Configuration
Add MCP server to agent configs automatically? [Y/n]: Y
Cursor — add to ~/.cursor/mcp.json? [Y/n]: Y
✔ Added 'excalidraw-canvas' to ~/.cursor/mcp.json
Claude Code — register via CLI? [Y/n]: Y
✔ Registered 'excalidraw-canvas' via Claude Code CLI
Done! Open http://localhost:3000 to verify the canvas.
The setup is fully optional. If you prefer to configure everything manually, skip to Path B or C below.
Path B: From Source
git clone https://github.com/sanjibdevnathlabs/mcp-excalidraw-local.git
cd mcp-excalidraw-local
npm install
npm run build
Then configure your MCP client — see Configuration.
To run manually (outside an MCP client):
node dist/index.js
Open http://localhost:3000 in your browser.
Path C: Docker
Canvas server:
docker run -d -p 3000:3000 --name mcp-excalidraw-canvas sanjibdevnath/mcp-excalidraw-local-canvas:latest
MCP server (stdio) is typically launched by your MCP client:
{
"mcpServers": {
"excalidraw-canvas": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "CANVAS_PORT=3000",
"sanjibdevnath/mcp-excalidraw-local:latest"
]
}
}
}
Note: For Docker on Linux, add
--add-host=host.docker.internal:host-gateway.
Configuration
This is a standard MCP server communicating over stdio. It works with any MCP-compatible client.
Cursor
Add to ~/.cursor/mcp.json (global) or .cursor/mcp.json (per-project):
{
"mcpServers": {
"excalidraw-canvas": {
"command": "npx",
"args": ["-y", "@sanjibdevnath/mcp-excalidraw-local"],
"env": {
"CANVAS_PORT": "3000"
}
}
}
}
Or, if installed from source:
{
"mcpServers": {
"excalidraw-canvas": {
"command": "node",
"args": ["/absolute/path/to/mcp-excalidraw-local/dist/index.js"],
"env": {
"CANVAS_PORT": "3000"
}
}
}
}
Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"excalidraw-canvas": {
"command": "npx",
"args": ["-y", "@sanjibdevnath/mcp-excalidraw-local"],
"env": {
"CANVAS_PORT": "3000"
}
}
}
}
Claude Code
claude mcp add excalidraw-canvas --scope user \
-e CANVAS_PORT=3000 \
-- npx -y @sanjibdevnath/mcp-excalidraw-local
Codex CLI
Add to ~/.codex/mcp.json:
{
"mcpServers": {
"excalidraw-canvas": {
"command": "npx",
"args": ["-y", "@sanjibdevnath/mcp-excalidraw-local"],
"env": {
"CANVAS_PORT": "3000"
}
}
}
}
Key points
- Single process — The canvas server is embedded. No separate terminal or process needed.
- Browser required for screenshots —
export_to_imageandget_canvas_screenshotrely on the frontend. Openhttp://localhost:3000in a browser.
Verify Installation
After configuring your MCP client, verify everything works:
# 1. Check the canvas server is running
curl http://localhost:3000/health
# 2. Open the canvas in your browser
open http://localhost:3000
# 3. In your AI agent, ask it to:
# "Create a blue rectangle labeled 'Hello World' on the Excalidraw canvas"
If the health check fails, see Troubleshooting.
How We Differ from the Official Excalidraw MCP
Excalidraw now has an official MCP — it's great for quick, prompt-to-diagram generation rendered inline in chat. We solve a different problem.
| Official Excalidraw MCP | This Project | |
|---|---|---|
| Approach | Prompt in, diagram out (one-shot) | Programmatic element-level control (32 tools) |
| State | Stateless — each call is independent | Persistent live canvas with real-time sync |
| Storage | None | SQLite with WAL mode, versioning, element history |
| Multi-tenancy | No | Workspace-based isolation, auto-detected |
| Element CRUD | No | Full create / read / update / delete per element |
| AI sees the canvas | No | describe_scene (structured text) + get_canvas_screenshot (image) |
| Iterative refinement | No — regenerate the whole diagram | Draw → look → adjust → look again, element by element |
| Layout tools | No | align_elements, distribute_elements, group / ungroup |
| File I/O | No | export_scene / import_scene (.excalidraw JSON) |
| Snapshot & rollback | No | snapshot_scene / restore_snapshot |
| Mermaid conversion | No | create_from_mermaid |
| Search | No | search_elements — full-text search across labels |
| Design guide | read_me cheat sheet |
read_diagram_guide (colors, sizing, layout, anti-patterns) |
| Viewport control | Camera animations | set_viewport (zoom-to-fit, center on element, manual zoom) |
| Live canvas UI | Rendered inline in chat | Standalone Excalidraw app synced via WebSocket |
| Multi-agent | Single user | Multiple agents can draw on the same canvas concurrently |
| Works without MCP | No | Yes — REST API fallback via agent skill |
What Changed From Upstream
This fork extends yctimlin/mcp_excalidraw with the following enhancements:
| Area | Upstream | This Fork |
|---|---|---|
| Storage | In-memory (lost on restart) | SQLite with WAL mode, versioning, element history |
| Multi-tenancy | None | Workspace-based tenant isolation (auto-detected via server.listRoots()) |
| Canvas lifecycle | Separate process (2 terminals) | Embedded in MCP process (single node dist/index.js) |
| Auto-sync | Manual "Sync to Backend" button | Debounced auto-sync (3s idle) with manual override |
| Canvas port | Hardcoded 3000 | Configurable via CANVAS_PORT env var |
| MCP tools | 26 | 32 (added search, history, tenants, projects) |
| Workspace switcher | None | Dropdown with search in canvas UI |
| Sync normalization | Bound text breaks on reload | Elements normalized to MCP format before storage |
Architecture
- Single process: The MCP server embeds the canvas server. Starting the MCP starts both; stopping it stops both.
- SQLite: Stored at
~/.excalidraw-mcp/excalidraw.dbby default. WAL mode +busy_timeoutfor multi-process safety. - Multi-tenancy: Each workspace gets an isolated tenant (SHA-256 hash of workspace path). The UI shows a workspace switcher dropdown with search.
Environment Variables
| Variable | Description | Default |
|---|---|---|
CANVAS_PORT |
Port for the embedded canvas server | 3000 |
EXCALIDRAW_DB_PATH |
Path to the SQLite database file | ~/.excalidraw-mcp/excalidraw.db |
EXCALIDRAW_EXPORT_DIR |
Allowed directory for file exports | process.cwd() |
EXPRESS_SERVER_URL |
Canvas server URL (only if running canvas separately) | http://localhost:3000 |
Multi-Tenancy (Workspaces)
Each workspace (codebase) gets an isolated canvas. The tenant is identified by a SHA-256 hash of the workspace path.
How it works
- Auto-detection: When the MCP starts, it calls
server.listRoots()to get the actual workspace path from the MCP client. This is hashed to create a unique tenant ID. - Per-request scoping: Every HTTP request includes an
X-Tenant-Idheader. The canvas server uses this to scope all CRUD operations to the correct tenant. - UI switcher: The canvas UI shows a "Workspace: <name>" badge. Click it to open a dropdown with all known workspaces, complete with search.
- Multi-instance safe: SQLite WAL mode with
busy_timeout = 5000mshandles concurrent access from multiple client instances.
Projects within a tenant
Each tenant can have multiple projects (collections of elements). Use the list_projects and switch_project MCP tools, or manage via the REST API.
Agent Skill (Optional)
This repo includes a skill at skills/excalidraw-skill/ that provides:
- Workflow playbook (
SKILL.md): step-by-step guidance for drawing, refining, and exporting diagrams — including an iterative write-check-review cycle, sizing rules, color palettes, and anti-patterns - Cheatsheet (
references/cheatsheet.md): MCP tool and REST API reference for all 32 tools - Helper scripts (
scripts/*.cjs): export, import, clear, healthcheck, CRUD operations
Install via Setup Wizard
The easiest way to install the skill:
npx @sanjibdevnath/mcp-excalidraw-local setup
The wizard detects your installed agents and lets you choose which ones get the skill.
Install Manually
Copy the skill folder to your agent's skill directory:
# Cursor
cp -R skills/excalidraw-skill ~/.cursor/skills/excalidraw-skill
# Claude Code
cp -R skills/excalidraw-skill ~/.claude/skills/excalidraw-skill
# Codex CLI
cp -R skills/excalidraw-skill ~/.codex/skills/excalidraw-skill
MCP Tools (32 Total)
| Category | Tools |
|---|---|
| Element CRUD | create_element, get_element, update_element, delete_element, query_elements, batch_create_elements, duplicate_elements |
| Layout | align_elements, distribute_elements, group_elements, ungroup_elements, lock_elements, unlock_elements |
| Scene Awareness | describe_scene, get_canvas_screenshot |
| File I/O | export_scene, import_scene, export_to_image, export_to_excalidraw_url, create_from_mermaid |
| State Management | clear_canvas, snapshot_scene, restore_snapshot |
| Viewport | set_viewport |
| Design Guide | read_diagram_guide |
| Resources | get_resource |
| Search & History | search_elements, element_history |
| Multi-Tenancy | list_tenants, switch_tenant |
| Projects | list_projects, switch_project |
Full schemas are discoverable via tools/list or in skills/excalidraw-skill/references/cheatsheet.md.
Testing
Health check
curl http://localhost:3000/health
MCP Inspector
List tools:
npx @modelcontextprotocol/inspector --cli \
-e CANVAS_PORT=3000 -- \
node dist/index.js --method tools/list
Create a rectangle:
npx @modelcontextprotocol/inspector --cli \
-e CANVAS_PORT=3000 -- \
node dist/index.js --method tools/call --tool-name create_element \
--tool-arg type=rectangle --tool-arg x=100 --tool-arg y=100 \
--tool-arg width=300 --tool-arg height=200
Troubleshooting
better-sqlite3 compilation failure
This is the most common installation issue. better-sqlite3 is a native Node.js module that requires C++ build tools.
Symptoms:
npm installfails withgyp ERR!orprebuild-installerrorsnpxcommand fails during installation- Error:
Cannot find module 'better-sqlite3'at runtime
Fix:
- Install build tools for your platform (see Prerequisites)
- Rebuild the module:
npm rebuild better-sqlite3 - Or run the setup wizard which handles this automatically:
npx @sanjibdevnath/mcp-excalidraw-local setup
EADDRINUSE (port already in use)
Symptom: Error listen EADDRINUSE: address already in use :::3000
Fix:
# Find what's using the port
lsof -i :3000
# Either kill the process or use a different port
CANVAS_PORT=3001 node dist/index.js
The MCP server automatically detects and reuses an existing healthy canvas server on the same port, so this error is rare.
Canvas not loading / "Frontend not found"
Symptom: Browser shows "Frontend not found" or blank page at http://localhost:3000
Fix:
npm run build # builds both frontend and server
node dist/index.js # restart
NVM / path issues with npx
Symptom: npx @sanjibdevnath/mcp-excalidraw-local hangs or uses the wrong Node version.
Fix:
# Ensure you're using a supported Node version
nvm use 20 # or 22
# Clear npm cache if npx is stale
npm cache clean --force
# Try with explicit node path in your MCP config
which node # copy this path
Then use the full path in your MCP config:
{
"mcpServers": {
"excalidraw-canvas": {
"command": "/Users/you/.nvm/versions/node/v22.12.0/bin/npx",
"args": ["-y", "@sanjibdevnath/mcp-excalidraw-local"],
"env": { "CANVAS_PORT": "3000" }
}
}
}
Canvas not updating / elements not syncing
Fix:
- Confirm the MCP process is running and the browser is connected (check the green status dot in the header)
- Click the "Sync" button in the canvas header for a manual sync
Wrong workspace shown
The MCP uses server.listRoots() to detect the workspace. Restart your MCP client if the workspace changed.
Known Issues / TODO
- Image export requires a browser:
export_to_imageandget_canvas_screenshotrely on the frontend rendering. The canvas UI must be open in a browser. export_to_excalidraw_urlblocked: Organizations that blockexcalidraw.comcannot use shareable URL export. Useexport_scenefor local.excalidrawfiles instead.
Contributions welcome!
Development
# Type check
npm run type-check
# Full build (frontend + server)
npm run build
# Dev mode (watch)
npm run dev
Database
SQLite database: ~/.excalidraw-mcp/excalidraw.db
Override with EXCALIDRAW_DB_PATH environment variable.
REST API
The canvas server exposes a REST API alongside the WebSocket interface:
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Health check |
| GET | /api/elements |
List all elements |
| POST | /api/elements |
Create an element |
| PUT | /api/elements/:id |
Update an element |
| DELETE | /api/elements/:id |
Delete an element |
| DELETE | /api/elements/clear |
Clear all elements |
| POST | /api/elements/sync |
Sync all elements (bulk upsert) |
| GET | /api/tenants |
List all tenants |
| GET | /api/tenant/active |
Get the active tenant |
| PUT | /api/tenant/active |
Set the active tenant |
| GET | /api/settings/:key |
Read a setting |
| PUT | /api/settings/:key |
Write a setting |
All endpoints accept an X-Tenant-Id header for per-request tenant scoping.
Credits
This project is forked from yctimlin/mcp_excalidraw — an excellent Excalidraw MCP server with a live canvas, 26 tools, real-time WebSocket sync, Mermaid conversion, and a comprehensive agent skill. Full credit to @yctimlin for the original design and implementation.
This fork adds SQLite persistence, multi-tenancy, auto-sync, embedded canvas lifecycle, and workspace management on top of that foundation.
Licensed under MIT.


