Inspiration
We kept drawing the same architecture twice. Once on a whiteboard for the conversation, once in a document for the record, and the two disagreed by the afternoon.
Then we watched an AI try to help with a diagram. It took a screenshot and guessed. It read a label wrong, missed an arrow that ran behind a box, and called a queue a database. That is not the model failing. We had handed it a picture of a system instead of the system.
WebMCP is the fix for that. A page can hand an agent typed tools instead of pixels. It only helps if the page has something typed to hand over, and that is where our tool already was.
What it does
KumiDraw draws as fast as a whiteboard and stores a typed graph.
Draw a box, and it is already a Service. Type cache and it becomes a Cache,
with the right glyph. Type redis and it becomes Redis with cache kind, the same node
refined rather than replaced. You are never asked to fill in a form. The
notes are a projection of that graph, so the drawing and the write-up cannot
disagree.
Because the graph is typed, rules sit on top of it. KumiDraw lints an architecture the way a compiler lints code: a synchronous call crossing a trust boundary, a queue with no consumer, a store read by four services with nothing cached in front of it, the one node every path has to pass through.
Your AI reads the graph, not the screen.
Through WebMCP it calls read_design and gets nodes, boundaries and typed calls as text. No
screenshot, and no guessing which arrow starts where. It knows an edge is
async because the model says so, not because a dash pattern hinted at it. It
sees the node that is scrolled off the canvas. It reads the same lint
findings you see, so it spends its attention on the question a reviewer would
ask next instead of on spotting the obvious.
It also imports formats we never wrote a parser for. Paste a Terraform
file and ask for it on the canvas. The agent already reads HCL, so it calls
add_node and connect, and because kind is a closed list of thirteen
words its mapping is constrained rather than invented. An
aws_elasticache_cluster becomes a Cache with the Redis glyph. A VPC becomes
a boundary. The same holds for CloudFormation, a Kubernetes manifest, a
Pulumi program, or a page of meeting notes. We ship no parser for any of
them, and we never will.
How we built it
using Nuxt 4 and Vue 3 and using codex, claude and gemini
Four decisions made the WebMCP work small:
- Kinds are a closed vocabulary. The union that picks a node's glyph is
the agent's JSON Schema enum. An agent cannot invent a type, and
Otherrecords the user's own word when the model has none. - Every change is a named, invertible op. Undo, persistence and the command registry come out of one mechanism, so an agent's edit is an ordinary edit.
- One write surface.
useDocumentends with a comment saying nothing outside its list may change the document. The tools are a projection of that list, not a second path into the store.
Tools register through document.modelContext.registerTool and tear down
with an AbortController. Work lives in IndexedDB. The build is static and
ships to Cloudflare Pages.
Challenges we ran into
WebMCP brought churn of its own. The API moved from navigator.modelContext
to document.modelContext during the origin trial, and most of what ranks
for "WebMCP" repeats itself and disagrees on dates, so we worked from the
Chrome docs and the spec repo and treated the rest as rumour.
The hardest call was paging. Chrome recommends 1.5K characters per tool result, and our first instinct was to page the node list. That is wrong. An agent handed page one reasons about a system whose datastore it never saw, and the failure is silent. We compress instead. One line per node, and the text that grows without limit moved to a tool an agent asks for on purpose.
Then labels. Our @ links bind by label, because people type words. An agent
cannot, because a label is allowed to be empty by design and two nodes can
share one. Agents get ids, and every write returns the ids it created.
Accomplishments that we're proud of
The type earns its keep three times over. One closed union draws the right glyph, feeds the linter, and becomes the agent's input schema.
We deleted a roadmap. Every import format we were going to write a parser for is now a conversation, and code we never write cannot rot.
No confirmation dialogs on agent writes. The op log makes undo the answer to a wrong edit, which is a better answer than a modal.
The privacy claim survives. Registering a tool transmits nothing, and no tool name, description or enum carries document content, so nothing leaves the machine until you ask a question.
What we learned
A screenshot is a lossy export of your own data. If a model has to look at your app to understand it, the app is hiding what it already knows.
Decide what can be decided in code. We nearly shipped a critique tool, which would have been our rubric competing with a better one inside the model. Rules that are true or false run as lint, and the model does the judgement.
Agents want intent shaped tools, not keystroke shaped ones. We nearly projected our command registry into forty tools. Commands are for hands.
Tool metadata is read on page visit, before anyone has asked anything. An enum of live node ids would have leaked the document for free.
Building for an agent made the app better for people. Every tool we wanted to expose turned out to be a gap in the write surface.
What's next for KumiDraw
Better Linter, More types, One node having multiple type (Like redis is cache, also a pubsub) Community Designs and Roast System
Built With
- cloudflare
- nuxt
Log in or sign up for Devpost to join the conversation.