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

Share this project:

Updates

Submission history