This repository has no description

README.md

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's boundElements back-reference. Excalidraw's own convertToExcalidrawElements can't run outside a DOM, so that expansion is reimplemented here.
  • cameraUpdate and restoreCheckpoint pseudo-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: #212529 for a white frame, #1864ab for a light blue fill, #b45309 for a bright orange.
  • Only arrows default to rounded. A multi-point line given roundness turns a triangle into an arc; a two-point line is straight either way.
  • Standalone text elements need an explicit width (≈ 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 cameraUpdate idea. This server shares no code with it.

license #

MIT.