Inspiration
Agent whiteboards already exist — FigJam has one, and there are several Excalidraw MCP servers. They all work the same way: you describe a diagram to an agent somewhere else, it calls a server, a picture comes back.
Nobody is standing at the board.
A whiteboard's state is already a graph — nodes, edges, positions — that happens to be rendered visually. WebMCP lets the page hand that graph straight to the agent, so it can edit the board you're already looking at. No install, no config, no API key.
What it does
Scribble three sticky notes about why users are churning. Ask your agent to read the board and draw the steps a user actually goes through. A labelled diagram appears underneath, built from what you wrote, in one call.
Five tools — and the list changes with the board:
| Tool | Available when |
|---|---|
create_note |
always |
create_shape |
always |
create_diagram |
always |
list_elements |
board has elements |
connect_elements |
board has 2+ shapes |
Every tool calls the same functions the on-screen canvas uses, so agent edits and hand-drawn shapes share one board and one undo history.
Why WebMCP fits
A backend MCP server advertises its tools once at startup, so every whiteboard
MCP offers connect_elements against an empty canvas. Here, registration is a
function of board state — list_elements appears when there's something to list.
That's structurally impossible server-side, and it's what Chrome's WebMCP best
practices ask for.
How I built it
React, Vite, Excalidraw for the canvas, deployed on Cloudflare Workers.
Tools register on document.modelContext, torn down via AbortController when
the board's tool set changes. The JSON Schemas agents discover are generated with
Zod from the same contracts that validate arguments at runtime, so the advertised
shape and the enforced shape can't drift.
create_diagram is the one I'm happiest with. Excalidraw accepts caller-supplied
IDs, so the agent names each node with a short key and edges reference those keys
— nodes and arrows built in a single pass. A ten-box diagram is one tool call
instead of twenty round trips.
There's no delete tool. The board is shared, so list_elements hands the
agent text other people wrote — it's marked untrustedContentHint for that
reason. You can't talk an agent into calling a tool that doesn't exist. Clearing
the board is a button only a human can press.
Challenges
Chrome doesn't pass the documented second argument to execute. The docs
specify execute(input, { signal }); the build I tested calls it with none, so
destructuring the context threw before any work began.
Errors reached the agent as empty strings. A thrown exception surfaced as a
blank errorText, so an agent that passed an invalid shape got no reason and
couldn't self-correct. Returning failures as values instead of throwing fixed it.
A labelled shape is two elements — the container plus its bound text. That
made list_elements report every note twice, and made a single note look like
two, so connect_elements offered to connect a shape to itself.
All of these turned up by driving the deployed app through Chrome DevTools and actually calling the tools. None were visible by reading the code.
What's next
Five tools is where I stopped, not where it ends. Same pattern gives you grouping notes by theme, finding what's missing from a diagram, real auto-layout. Multiplayer through Durable Objects would make the shared board actually shared.
Built With
- cloudflare-workers
- excalidraw
- react
- typescript
- vite
- webmcp
- zod
Log in or sign up for Devpost to join the conversation.