Inspiration
We're actually running an early-stage jewellery business on the side with Shopify, so this isn't a random pick for a hackathon theme, it's a problem we already deal with. Bespoke jewellery design kinda bad right now, honestly. Two options we know:
- Fiddle with a rigid configurator by yourself till you give up, or
- Email a jeweller a description and wait days per round.
Same root problem both times: the design lives in one place, and only one side gets to touch it at a time.
We were also just tired of the usual "AI design demo" pattern, where the agent either describes a change and you go make it, or it takes the wheel completely and hands you a finished thing. That's not really collab, it's a queue with extra steps.
WebMCP was the first thing we'd seen where a page could expose its live state + actions to an agent sitting right there in the browser w/ you, so both of you could actually have hands on the same object at once. A ring felt like a great test case because it's precise and parametric (agent territory) but still fundamentally about taste (very much not agent territory). Ask it for "three halo variants under £900 in white gold" and it's great. Ask it for "a bit more to the left, no less than that" and good luck.
What it does
Loomlace is a ring configurator where you and an agent design on one live canvas. Live at loomlace.vercel.app, go try it.
- You drag the stone, resize the band, pick metals, w/ your hands.
- The agent calls the same store actions for the parametric/combinatorial stuff: resizing, re-costing, generating variations.
- You keep the taste calls, it keeps the math.
Nothing leaves the browser until you hit Review this ring and add to cart. That one matters more than it sounds like, bc engraving text is often the most private sentence someone types that month (a name, a date, an inside joke) and we didn't want any of that touching a server before you actually order.
Why WebMCP, specifically
A ring design is a rich, stateful, client-side thing, and that turns out to make it a bad fit for basically every other way an agent could touch it, but a great fit for WebMCP:
- Server-side MCP can't see it. The design lives in the browser as you're dragging things around. A server tool would either be working off a stale copy or forcing every drag through a round trip.
- A browser extension would just be DOM-poking. Generic click-and-scrape has no clue that
widthMmis clamped 1.2 to 4.0mm, thatxis a normalised position around the band, or that a halo needs a centre stone first. WebMCP lets us publish all that as actual schema instead of hoping a script guesses right. - The data's personal. Again, engraving text, names, dates, jokes. Tools that run entirely in-page means nothing's transmitted until added to cart, and we say that on screen too, not just in a privacy policy nobody reads.
The thing that actually decides it though is co-presence. The agent calls the exact same store actions the sliders call, so an agent edit and a human drag land on one surface instead of two copies fighting each other.
What's actually new here
Normally an agent either describes a change and waits for you to make it, or takes the wheel entirely and hands back a finished thing. Loomlace does neither.
- You drag the stone where it feels right. The agent reads the new position through
get_design_stateand builds the halo around it. - Mid-demo proof: agent's partway through restyling, you drag the centre stone, and its next call sees the new spot and builds on it instead of overwriting it. Works because the design has one home and you both reach it through the same door.
- The tools themselves change as the design matures.
add_engravingdoesn't exist until a setting's been committed to - nothing to engrave otherwise. The agent gets atoolchangeevent and re-reads the list, so it only ever sees tools that actually make sense right now.
How we built it
Ten tools, wired straight into the page. Any agent visiting the site can see and call them - no separate backend, no config screen.
The decision that mattered most: the ring's state lives in one place, always "live," kept deliberately outside our UI framework's normal state tracking. We didn't do this at first, and it bit us - an agent's instructions get built once but might not run for a bit, so reading state the normal React way meant it could act on a version of the ring that no longer existed. Moving the source of truth outside React fixed it: every tool call now reads the ring exactly as it is, not as it was when the agent's turn started.
That one fix carries everything else:
- Pricing and rendering don't know or care whether a slider drag or a tool call triggered them - same code path, so the two can't drift apart.
- Tools register and unregister cleanly as the page updates, no stale ones left behind.
- Every agent input gets double-checked and clamped on our side, not trusted blindly.
Stack: Next.js, React, TypeScript, three.js for the 3D ring.
Challenges we ran into
The worst bug was the one that never crashed.
- Silent stale state. If a human dragged the stone mid-agent-turn, the agent's edit could quietly build on the old position - no error, just a subtly wrong ring. We only caught it by testing that exact scenario on purpose. Fixing where state lived solved it; the scary part was how invisible it was.
- Chrome vs. spec. Chrome's actual behaviour didn't match WebMCP's spec for passing a tool's input, so calls failed with a vague error and no clue why. Took a while to realise it was Chrome, not us. Documented it once we knew, so it doesn't cost anyone else the hours.
Accomplishments that we're proud of
- The co-presence demo isn't staged, it actually works. Human drags the stone mid-agent-turn, agent's next call reads the new position and builds around it instead of stomping on it. That's the moment the whole submission hangs on, and it's real, not narrated over in a voiceover.
- The tool surface grows up along with the design:
add_engravingdoesn't show up until there's something worth engraving. Small thing, but it's what stops the agent from ever offering something the app can't back up yet.
What we learned
The real problem in agent-native UI isn't "can the agent call a function." It's "does the agent see the same truth the human does, at the same time." Store location, the toolchange event, handlers returning resulting state - every real decision we made was that one question in a different costume. Co-presence beats capability.
Also, the hard way: a browser's implementation of a spec ≠ the spec. Chrome 152's executeTool disagrees with the actual IDL, and the error doesn't tell you which side is "wrong." If you're building against WebMCP right now, test against the real browser, not just the doc.
What's next for Loomlace
- More materials & cuts: catalogue's small on purpose for the demo, but pricing/rendering were built to scale up w/o new code paths.
- Multi-item designs: pairs (engagement + wedding band), matching sets, places where the agent's combinatorial side matters even more than on a single ring.
- Persisted, shareable designs: right now a design lives for one browser session. Letting someone send a link to a jeweller or partner is the obvious next move, just needs doing carefully so it doesn't wreck the "nothing leaves the browser till checkout" thing.
- A real store integration, swapping out the current placeholder
NEXT_PUBLIC_STORE_URLsoadd_to_cartactually goes somewhere. - Voice as a second input, alongside typing and dragging, for when both hands are busy turning the ring on screen.
Built With
- javascript
- next.js
- react
- react-three-fiber
- tailwindcss
- three.js
- typescript
- webmcp
- zustand
Log in or sign up for Devpost to join the conversation.