Inspiration
Car configurators are one of the most frustrating interfaces on the web, and it isn't because they're badly designed. It's because the options genuinely interact. The tow package needs all-wheel drive. Sport suspension drops the ride height below legal hitch clearance. The 21-inch wheels are locked to trims you may not want to pay for. Every real configurator has a hidden constraint graph, and the only tool it gives you for exploring it is clicking and backtracking.
So when someone asks the question they actually care about — "what's the cheapest build with all-wheel drive and a tow hitch that still comes in under fifty thousand?" — the interface has no answer. You brute-force it by hand.
That's a constraint-satisfaction problem behind a UI that can't express one. It seemed like exactly the shape of problem WebMCP exists to solve.
What it does
Meridian Voyager is a car configurator where an agent and a human edit the same build. Not a chat widget stapled to a product page — the page registers nine tools on document.modelContext, and the agent reads and writes the exact state the human is looking at.
You can click paint colours yourself and the agent sees your choice on its next read. You can ask the agent for the cheapest build meeting your constraints, and the car on screen changes while you watch. An activity log labels every change You or Agent, so the collaboration is legible rather than magical.
The centrepiece is find_cheapest_build. It enumerates the full option space against every compatibility rule and returns an exact cheapest answer, which the agent can then apply in one call. That's the question the UI could never answer.
The other half is that constraints get explained, not just enforced. Every rule carries a human-readable reason, and tool responses hand those sentences back:
▎ "The Tow Package needs the front motor for low-speed traction under load, so it requires Dual Motor AWD or Performance AWD."
A blocked set_option teaches the agent something instead of erroring. It can then offer a real trade-off — "I can add the hitch if we move you to Dual Motor AWD, which is $5,000 more and costs 12 miles of range" — which is exactly the conversation a good salesperson has and a form never can.
How we built it
React and Vite, with the domain deliberately separated from the tool layer:
- src/domain/catalog.ts — the option catalog as pure data
- src/domain/rules.ts — the constraint engine, pricing, and the exact cheapest-build solver
- src/store.ts — the single shared configuration
- src/webmcp/register.ts — the nine document.modelContext tools
- src/components/CarView.tsx — layered SVG that reacts to paint, wheels, and ride height
The catalog and the rules are plain data, so adding an option or a constraint never means touching tool code. The tools describe the catalog to the agent from the same source the UI renders from, which is what keeps the two views honest.
The nine tools: get_configuration, list_available_options, check_compatibility, get_price_breakdown, find_cheapest_build, set_option, remove_option, apply_build, reset_build.
One store, two writers. The click handlers and the tool implementations both go through store.ts. There's no separate agent-facing model to drift out of sync — that's the whole reason the demo reads as collaboration rather than automation.
Read and write get different trust. Read-only tools are marked readOnlyHint, so an agent can explore the entire option space and price out alternatives freely, and only needs the user's confidence when it's about to change the build.
Native first. The app waits for a native document.modelContext and uses it directly on Chrome 149+. Only when the browser hasn't shipped the API does it load @mcp-b/global, a polyfill of the same W3C spec, so anyone evaluating it on another browser still gets the full experience. A badge in the header shows which one is active.
Challenges we ran into
Getting the tool responses right mattered more than getting the tools right. The first version returned bare success and failure, and an agent driving it would just retry the same blocked option. Moving every constraint's rationale into the response — and adding a hint pointing at find_cheapest_build — changed the agent from something that poked at the form into something that reasoned about trade-offs.
The solver also had to be exact rather than greedy. A greedy walk produces a plausible build that isn't actually the cheapest, and a wrong confident answer is worse than no answer. The option space is small enough to enumerate honestly, so it does.
Detecting the native API was subtler than expected. Registering against the polyfill because document.modelContext hadn't appeared yet silently downgrades a native browser, so the app waits for the host's implementation before falling back.
Accomplishments that we're proud of
scripts/verify.mjs drives the app through the tool layer in headless Chromium — the same path an agent takes — and asserts the page moves with it. Nineteen checks, all passing, covering tool registration, constraint enforcement, the solver (including a deliberately impossible request it must refuse), agent writes reaching the UI, and human clicks becoming visible to the agent.
What we learned
WebMCP's real leverage isn't letting an agent click things faster. It's that a page can expose the reasoning behind its own interface — the constraints, the trade-offs, the search space — which is precisely the part a visual UI throws away. The tow-hitch rule exists in every real configurator; WebMCP is the first time the page can explain it rather than just enforce it.
What's next for Meridian Voyager
Multi-vehicle comparison, financing as a tool so the agent can optimise on monthly payment rather than sticker price, and tightening the transport's allowedOrigins from the demo's permissive setting to a real allowlist.
Built With
- model-context-protocol
- playwright
- react
- svg
- typescript
- vercel
- vite
- webmcp
Log in or sign up for Devpost to join the conversation.