Shared Whiteboard
Enables Claude to create, open, and edit persistent tldraw whiteboards in real-time, with live collaboration between human and AI.
README
tags:
- whiteboard
- tldraw
- mcp
- tool
- plugin
Shared Whiteboard
Named, persistent draw.io-style whiteboards in the browser that you and Claude edit at the same time. Boards live on a small sync server; the browser renders them with tldraw; an MCP server lets Claude open a board by name and mutate it. Your edits and Claude's edits appear on the same canvas.
Install (Claude Code plugin)
The quickest way. Installs the MCP server and the sync backend + web UI in
one step — no clone, no npm install (the plugin ships a self-contained bundle).
Run these in Claude Code:
/plugin marketplace add camfung/shared-whiteboard
/plugin install shared-whiteboard@camfung-plugins
- Restart Claude Code when prompted so it loads the MCP server.
- On first use the plugin boots a local backend + web UI at http://127.0.0.1:5858 — open that in your browser to watch Claude draw live.
- Nothing else to configure: the plugin auto-spawns the backend for you. The manual Run and Claude / MCP sections below are only for running from source (development).
Update later: re-pull the marketplace, then reinstall:
/plugin marketplace update camfung-plugins
/plugin install shared-whiteboard@camfung-plugins
The workflow it's built for
- Open the web UI, pick a board from the dropdown (or
+ new). The dropdown name is what you tell Claude. - Tell Claude: "open the Auth Redesign board and add a node for the session store".
- Claude calls
open_board("Auth Redesign")then edits — you watch it happen live. - Either side can create boards; new boards show up in the other's list.
Skills
The plugin ships two skills that give Claude a scripted first step: boot the backend if it's down, then open the board in your browser — so you never end up with Claude drawing to a window you can't see.
- create — triggered when you ask Claude to draw, diagram, or add anything to
the whiteboard. Opens the board, then guides Claude through
create_node/create_uml/create_note/create_text/connect. - edit — triggered when you ask Claude to move, restyle, connect, rename, re-layout, or delete existing shapes. Opens the board, then reads the current shapes before mutating them.
Each skill's open-board.sh is a thin wrapper over wb server open (pure Node —
no curl or node_modules needed), so it works the same from a plugin install
or a source clone.
Architecture
One authoritative document per board
- Each board is a
TLSocketRoom(@tldraw/sync-core), keyed by a url-safe id. - Two kinds of client mutate the same room: browsers (WebSocket,
@tldraw/sync) and Claude (HTTP, via the MCP server). - Every mutation runs through
room.updateStore(...), which broadcasts to all connected browsers — so Claude's edits show up live, andget_boardreflects edits you just made. - Persistence: any change (browser or Claude) debounce-writes the board's
snapshot to
data/snapshots/<id>.json; it's reloaded on next access. Board names live indata/boards.json. Restarting the server keeps every board.
browser (tldraw useSync) ──WS──┐
├── server.js (TLSocketRoom per board) ── data/*.json
Claude (mcp-server.js) ──HTTP──┘
Pieces
server.js— sync backend. Browser WS at/connect/:boardId; semantic HTTP API; also serves the built web UI fromweb/distwhen present (one port for everything).boards.js— named + persisted room registry (create/rename/delete/load/save).shapes.js— builders → valid tldraw v5.2.5 records (captured from a live editor).uml-schema.js— the customumlshape's props (backend half; see caveat below).templates.js— reusable block store + stamp/clone (id remap, re-base to a point).mcp-server.js— stdio MCP. Holds a per-session "current board"; each tool = one HTTP call.web/— Vite + React + tldraw client. Board picker,+UML, save/stamp templates.web/src/uml.tsx— theUmlShapeUtil(browser half of the custom shape + double-click editing).
Custom UML shape — the one gotcha
A custom tldraw shape must be registered in three places, all agreeing on the same props (no migrations, so the synced schemas match):
web/src/uml.tsx—UmlShapeUtil.props(browser render + validation).uml-schema.js→boards.jscreateTLSchema({ shapes: { uml: {props} } })(server validation).shapes.jsbuildUml(the record the MCP server writes). Change the props in one, change them in all three, or sync breaks.
Reuse model
No Figma-style linked masters (tldraw has none). A template is a captured bundle
of records; stamping clones them with new ids at a target point. Copies are
independent. Edit data/templates.json to inspect/prune.
Run (from source / development)
Skip this if you installed the plugin above — it bundles and boots everything. These steps are for hacking on the code or running a checkout by hand.
Dev (hot reload)
./run.sh # backend :5858 + Vite dev UI :5173 → open http://127.0.0.1:5173
Always-on (one process serves UI + API on :5858)
cd web && npm run build # build the UI once (rerun after web/ changes)
# then install the user service:
cp whiteboard.service ~/.config/systemd/user/whiteboard.service
systemctl --user daemon-reload
systemctl --user enable --now whiteboard.service
# UI + API now live at http://127.0.0.1:5858
The bundled whiteboard.service binds WB_HOST=0.0.0.0, so the board is
reachable from other devices on the LAN (see On an iPad below).
On an iPad (installs as a full-screen app)
The UI is a PWA — added to the Home Screen it launches chromeless (no address bar, no tabs), locks out browser page-zoom so pinch drives the canvas, and kills the rubber-band bounce. It feels like a native app.
- Serve on the LAN. The always-on service already binds
0.0.0.0. If you run it by hand instead, pass the bind explicitly:cd web && npm run build WB_HOST=0.0.0.0 node server.js # UI + API on every interface, port 5858No auth — anyone on the LAN can read/edit. To scope it to your Tailscale net, use that interface's IP instead of
0.0.0.0. - Open it on the iPad. In Safari go to
http://<this-machine-ip>:5858(e.g.http://10.0.2.52:5858).hostname -Iprints the machine's IPs. - Install. Share button → Add to Home Screen → Add. Launch it from the new "Whiteboard" icon — it opens full-screen.
Keep the iPad and host on the same network. The page and its WebSocket both talk
to <the-host-you-opened>:5858, so LAN over plain http/ws just works.
Claude / MCP (from source)
Plugin users can skip this —
/plugin installregisters the MCP server via the bundled.mcp.json. This is the manual registration for a source checkout.
Registered at user scope (works in every project), pointing at the backend:
claude mcp add --scope user whiteboard --env WB_URL=http://127.0.0.1:5858 \
-- node "/home/camer/ClaudeChats/Shared Whiteboard/mcp-server.js"
The MCP server just proxies HTTP, so the backend must be running (that's why always-on is recommended). Restart Claude Code to load the server after adding it.
Tools
Board management:
list_boards— every board (name, id, shape count, updated).open_board {name}— open by name/id, make it active, return its contents.create_board {name}— create + open + make active.rename_board {name, id?}/delete_board {name}.
Reading the active board (call open_board or create_board first):
get_board {since?, type?, color?, text?, ids?}— the whole board (shapes with id, type, x/y, w/h, color, text; uml shapes also give name/fields/methods) + arrow links + the boardclock. Filters narrow it;since=<clock>returns only what changed. See Reading big boards efficiently below.list_shapes {type?, color?, text?}— compact index: one{id, type, label}line per shape (+ arrow links). A cheap map of a large board.get_shapes {ids?, type?, color?, text?}— full detail for specific shapes (by id, or by filter).get_neighbors {ids, hops?}— a shape plus everything arrow-linked to it, out tohopslinks (default 1), with the connecting arrows.check_overlap— layout-quality metrics (overlap ratio, worst offenders); decide/verify a re-layout.
Editing the active board:
create_node {text,x,y,w?,h?,shape?,color?,fill?}→ id.create_text {text,x,y,color?,size?}→ id.create_note {text,x,y,color?}→ id.create_uml {name,x,y,fields?,methods?,color?}→ id. A UML class block (title + fields + methods compartments).update_uml {id,name?,fields?,methods?,color?,x?,y?,w?}— replace name/fields/methods.add_field {id,field}/add_method {id,method}— append one row (auto-grows).connect {fromId,toId,text?,color?,dashed?}→ arrow that follows the shapes.update_node {id,text?,x?,y?,w?,h?,color?,fill?}.delete_shapes {ids}— also removes bound arrows.clear_board— wipe shapes/arrows (keeps the board).
Reusable templates (save a block once, stamp copies anywhere):
list_templates— saved templates (name + shape count).save_template {name, ids}— capture the given shapes (+ arrows between them) from the active board as a template.stamp_template {name, x, y}— drop a fresh independent copy onto the active board.delete_template {name}.
Enums
- colors: black, grey, light-violet, violet, blue, light-blue, yellow, orange, green, light-green, light-red, red
- shapes (geo): rectangle, ellipse, diamond, triangle, hexagon, cloud, star, oval, pentagon, octagon, rhombus, trapezoid, x-box, check-box, heart
- fills: none, semi, solid, pattern
- Invalid enum → error (it would otherwise crash the browser's validator).
Reading big boards efficiently
On a large board, don't re-read the whole thing every time:
- Map first, then drill.
list_shapesgives a cheap index; pull full detail for only the ids you need withget_shapes, or expand outward from a node withget_neighbors. - Filter server-side.
type/color/text(substring, case-insensitive) /idsonget_board,list_shapes, andget_shapesnarrow the result before it's sent. - Poll changes with the clock. Every read returns a
clock. Pass it back asget_board {since: clock}to get only shapes changed since (plusdeletedids from tombstones) — the cheap way to see the human's latest edits instead of re-reading the board. The response carries the newclock; keep it for the next poll.
HTTP API (what the MCP server calls)
GET /boards·POST /boards {name}·POST /boards/rename {id,name}·POST /boards/delete {id}·GET /boards/find?q=GET /board?board=<id>— semantic summary +clock. Optionalsince=<clock>(delta: changed shapes +deletedids), andtype/color/text/idsfilters.GET /shapes?board=<id>&fields=index|full—index= compact{id,type,label},full= full detail; sametype/color/text/idsfilters.GET /neighbors?board=<id>&ids=a,b&hops=1— graph neighborhood of the given shape ids.GET /snapshot?board=<id>— raw tldraw snapshot.POST /node|/text|/note|/connect|/update|/delete|/clear?board=<id>POST /mutate?board=<id>— low-level{puts, deletes}escape hatch
Notes / limits
- Coordinates are tldraw page pixels;
create_*place shapes atx,y(top-left). - Conflict model is last-write-wins per record — fine for a human + Claude taking turns, not CRDT-grade for two people typing in the same text field at once.
- Backend host defaults to
127.0.0.1(theserver.jsdefault); the bundled service overrides it to0.0.0.0for LAN/iPad use. Override withWB_HOST.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.