- TypeScript 97%
- JavaScript 3%
lexidraw-mcp #
An MCP server that reads and writes Lexidraw scenes as atproto records, so an agent can draw straight into your own repo.
Lexidraw is an Excalidraw fork by
@pds.dad where drawings live in your own
place rather than the app's database. Each drawing is an app.lexidraw.scene
record holding a gzipped Excalidraw scene as a blob. This server speaks that
format directly:
- reading needs no auth at all — anyone's public scenes, straight from their PDS
- writing goes to your own repo, and the result opens at
https://lexidraw.app/#atproto=<did>,<rkey>
The record shape isn't guessed. Bailey publishes the lexicon on the network, and
this server follows it:
at://did:plc:rnpkyqnmsw4ipey6eotbdnnf/com.atproto.lexicon.schema/app.lexidraw.scene
install #
git clone <this repo> lexidraw-mcp && cd lexidraw-mcp
npm install && npm run build
Then register it with your MCP client. For Claude Code, run this from the project you want it available in:
claude mcp add lexidraw \
-e LEXIDRAW_HANDLE=you.example.com \
-e LEXIDRAW_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx \
-- node /absolute/path/to/lexidraw-mcp/dist/index.js
Use an app password, not your account password. Note it grants full repo
write access — scoped OAuth is the correct long-term shape and isn't implemented
here yet. Reading works with no credentials at all, so you can leave the env
vars off entirely if you only want lexidraw_list and lexidraw_open.
tools #
| tool | auth | what it does |
|---|---|---|
lexidraw_list |
none | list a handle's scenes — rkey, name, updatedAt |
lexidraw_open |
none | fetch a scene and return its Excalidraw elements |
lexidraw_save |
app password | write a scene to your repo; returns the at:// uri and a shareable link |
lexidraw_save takes rkey to update a scene in place, or omits it to create a
new one. Pair it with lexidraw_open to pick up hand edits you made in the app
instead of clobbering them.
element format #
lexidraw_save accepts the same shorthand the
excalidraw-mcp draw tool uses, so
the two compose:
label: { text, fontSize }on a shape expands to a bound text child with the container'sboundElementsback-reference. Excalidraw's ownconvertToExcalidrawElementscan't run outside a DOM, so that expansion is reimplemented here.cameraUpdateandrestoreCheckpointpseudo-elements are stripped.{ type: "delete", ids: "a,b" }removes those elements and their bound labels.- Missing
seed,versionNonce,points, arrowheads and friends get filled in.
Output is serializeAsJSON-shaped and gzipped at level 6, per the lexicon.
gotchas worth knowing #
- The Lexidraw viewer renders dark, inverting luminance while keeping hue.
Specify the opposite lightness of what you want:
#212529for a white frame,#1864abfor a light blue fill,#b45309for a bright orange. - Only arrows default to rounded. A multi-point
linegiven roundness turns a triangle into an arc; a two-point line is straight either way. - Standalone
textelements need an explicitwidth(≈chars × fontSize × 0.62) or long lines clip.
A companion excalidraw skill covering this in more depth lives in
skills/excalidraw/ — copy it into
~/.claude/skills/ to use it.
prior art / thanks #
- @pds.dad for Lexidraw and for publishing the lexicon on the network, which is why none of this required reverse engineering.
- excalidraw/excalidraw-mcp for
the element shorthand and the
cameraUpdateidea. This server shares no code with it.
license #
MIT.