Excalidraw MCP Diagram Agent

Excalidraw MCP Diagram Agent

Generates editable Excalidraw diagrams from natural language system descriptions, with properly bound arrows and labels. Includes tools for diagram creation, validation, and backend status.

Category
访问服务器

README

Excalidraw MCP Diagram Agent

An MCP server that turns a sentence about a system into a real, editable Excalidraw file.

Not a picture of a diagram — a .excalidraw document you can open at excalidraw.com, drag a box in, and watch the arrows follow. Arrows are genuinely bound to the shapes they connect, labels are genuinely bound to their containers, and shapes never overlap.

An eight-box architecture diagram generated from one English sentence

That picture was generated from this, with no model involved:

A React storefront calls the API gateway. The API gateway authenticates with Auth0
and forwards requests to the orders service. The orders service queries the Postgres
database, caches in Redis and publishes to Kafka. A billing worker consumes from
Kafka, calls Stripe and writes to the Postgres database.

The scene it produced is in docs/example-checkout.excalidraw — open it and move something.

The thing that is actually hard

Emitting JSON that Excalidraw accepts is easy. Excalidraw's restore() runs on every import and quietly patches whatever you left out, so a half-correct file still opens and looks fine. You find out it was wrong later, when someone drags a box and every arrow stays behind.

Three failures hide behind a valid-looking file:

  1. One-sided bindings. An arrow names its shapes in startBinding/endBinding, but the shapes must name the arrow back in boundElements. Miss the back-reference and the file opens perfectly and stops behaving like a diagram the moment you move anything.
  2. Floating arrows. Excalidraw decides an arrow is bound from the binding records, but it draws the shaft from the element's own points. Both have to agree, and the points have to land on the real outline of the shape — which is a different equation for a rectangle, an ellipse and a diamond.
  3. Boxes on top of boxes. Schema-valid JSON that renders as an unreadable pile is still a failed diagram.

So the validator here checks three separate things, and only the third catches the failure that matters most: bindings that are perfect on paper attached to arrows that visibly float.

How it works

description ──► graph (nodes + edges) ──► layout ──► scene ──► validate
                  ▲             ▲
            rule parser    language model

Everything upstream of the graph is interchangeable; everything downstream is deterministic Python. The model — when there is one — never sees Excalidraw's schema and never produces a coordinate. It emits a tiny JSON graph and nothing else. That split is the whole design: the part that must be exactly right is kept out of the hands of the part that is only usually right, and a model failure degrades to the rule parser instead of to a broken file.

The layout is a small Sugiyama-style layered pass: break cycles, assign layers by longest path, insert dummy nodes so a long edge reserves a corridor instead of ploughing through the boxes between its ends, order within layers by barycentre sweeps to cut crossings, then assign coordinates. Non-overlap is structural rather than checked afterwards — layers occupy disjoint bands and siblings are stacked with a fixed gap — and the validator asserts it anyway.

Install

Python 3.10+.

git clone https://github.com/dev48v/excalidraw-mcp-agent
cd excalidraw-mcp-agent
python -m venv .venv && . .venv/bin/activate     # Windows: .venv\Scripts\activate
pip install -e ".[dev]"

Quickstart

The CLI runs the same pipeline the MCP server does:

excalidraw-diagram --offline -t "Checkout" -o checkout.excalidraw \
  "A React storefront calls the API gateway, which queries Postgres and publishes to Kafka."
[rules] 4 nodes, 3 edges, 15 elements, 6 bindings -> checkout.excalidraw

Open checkout.excalidraw at excalidraw.com. Useful flags:

Flag Effect
--offline never call a model; rule parser only, fully deterministic
--require-model fail rather than silently downgrading to the rule parser
-d down flow top-to-bottom instead of left-to-right
--stdin read the description from a pipe

Arrow syntax works too, and mixes freely with prose:

excalidraw-diagram --offline "Browser -> CDN -> Load Balancer -> App Server: HTTPS"

Optional: the model backend

The rule parser is a first-class path, not a fallback — the renderer cannot tell which front end produced the graph it is drawing. But a model handles phrasing the verb table does not.

cp .env.example .env
# NVIDIA_API_KEY=nvapi-...   free tier at https://build.nvidia.com

Any OpenAI-compatible endpoint works — Ollama, vLLM, OpenRouter — via EXCALIDRAW_MCP_BASE_URL and EXCALIDRAW_MCP_MODEL. With no key set, everything still runs; it just uses the rules.

Wire it into an MCP client

The server speaks stdio.

Claude Code

claude mcp add excalidraw -- /abs/path/to/.venv/bin/excalidraw-mcp-agent

Claude Desktop — add to claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/, Windows: %APPDATA%\Claude\):

{
  "mcpServers": {
    "excalidraw": {
      "command": "/abs/path/to/.venv/bin/excalidraw-mcp-agent",
      "env": { "NVIDIA_API_KEY": "" }
    }
  }
}

On Windows the command is C:\\path\\to\\.venv\\Scripts\\excalidraw-mcp-agent.exe. Use an absolute path: MCP clients do not inherit your shell's PATH.

Then ask for a diagram in plain English:

Draw me the checkout flow: a React storefront calls the API gateway, which authenticates with Auth0 and forwards to the orders service; the orders service queries Postgres, caches in Redis and publishes to Kafka. Save it to ~/checkout.excalidraw

Tools

Tool Does
create_diagram description → complete scene. Optional title, direction, save_path, and use_model (auto / never / always).
validate_diagram checks a scene you already have — JSON string or a path — for schema errors, one-sided bindings, floating arrows and overlaps.
diagram_backend_status says whether a model is configured or the run will be offline, so a downgrade is distinguishable from a deliberate offline run.

create_diagram returns the scene and a summary, so the client can see what it got:

{
  "title": "Order Checkout Flow",
  "backend": "rules",
  "nodes": 9, "edges": 9,
  "elements": 37, "shapes": 9, "arrows": 9, "labels": 19,
  "bindings": 18,
  "valid": true, "errors": [], "warnings": [], "notes": []
}

Nine arrows, eighteen bindings — every arrow attached at both ends.

Node kinds

The kind decides the shape and the colour, and is inferred from the label when no model supplies one.

Kind Shape Inferred from
client violet rectangle browser, frontend, mobile, SPA, dashboard
service blue rectangle api, server, worker, lambda, gateway (the default)
database green ellipse postgres, mysql, mongo, dynamo, rds
store green rectangle redis, cache, s3, bucket, elasticsearch
queue dashed orange rectangle kafka, sqs, topic, stream, pubsub
external dashed grey rectangle stripe, twilio, third-party, webhook
decision orange diamond router, switch, branch, condition

Verifying it for yourself

pytest covers the graph, the parser, the layout, the geometry, the scene builder and the validator:

pytest
# 114 passed

Those are Python assertions about Python objects, which is exactly the trap this project is about — so there is a second harness that renders the output with Excalidraw's own engine. It needs Node and Chrome and a 14 MB bundle, so it is not part of the package or the test suite:

cd tools/render
npm install
npx esbuild entry.mjs --bundle --outfile=bundle.js --format=iife \
  --define:process.env.NODE_ENV=\"production\" --loader:.woff2=dataurl
cd ../..
python tools/render/verify.py docs/example-checkout.excalidraw
[PASS] example-checkout.excalidraw
        svg        : 2260 x 953.7184460878227
        drawn nodes: 129 (20 text)
        restore()  : 0 element(s) changed
        screenshot : tools/render/out/example-checkout.png

It does two things a schema check cannot. It draws the scene with the real exportToSvg, so a malformed element shows up as a broken or empty picture rather than a passing assertion. And it runs the file through Excalidraw's own restore() and diffs the result against what we generated — restore() being the function that silently repairs imported files. Zero elements changed means the file needed no repair at all.

It also writes a PNG, cropped to the drawing, which is the only way to catch the failure no assertion describes: a diagram that is correct and unreadable.

Honest limits

  • The rule parser is a verb table, not language understanding. It knows about fifty verb phrases and the A -> B form. Sentences it cannot parse are dropped rather than guessed at, so odd phrasing costs you an edge, never a wrong diagram — but it does cost you the edge.
  • The model summarises. Asked to draw a worker that "fetches the file from S3 and writes a thumbnail back to S3", an 8B model returned one edge, not two. --require-model gets you the model's reading of your description, not a transcription of it.
  • Crossings are reduced, not eliminated. Barycentre ordering is a heuristic. On the example above, billing worker → Kafka still crosses orders service → Postgres. Non-overlap of boxes is guaranteed; non-crossing of arrows is not, and cannot be in general.
  • Text is measured by estimate. Excalidraw measures with real font metrics in a browser; this has neither the fonts nor a canvas, so widths are a deliberate slight over-estimate. Boxes are a few pixels wider than they need to be, which beats a box that clips its own label.
  • Arrow labels may overhang their arrows. Given the choice between a label wider than its edge and hard-splitting authenticates into authenticate + s with, this picks the overhang.
  • Self-loops are dropped. An edge from a node to itself needs routing this layout does not do, so it is skipped rather than drawn as junk.
  • Layered layout suits pipelines and architectures. A dense mesh with no clear direction will lay out legally and read poorly.
  • Two directions, seven kinds, one style. No groups, frames, images, freedraw or nested containers. Node labels are capped at five words.
  • The MCP server is built against mcp 2.x, where the ergonomic class is mcp.server.mcpserver.MCPServer and FastMCP is gone. Most tutorials you will find describe the 1.x API.

Licence

MIT © Devanshu Biswas. See LICENSE.

Project #1 of the Weekend Builds series.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选