Inspiration

I kept noticing that "AI agent shops for you" demos all have the same hole in them: the agent describes what it found in a wall of chat text, and then either asks you to go do the buying yourself, or it just buys and you find out afterwards. Neither is how a person actually wants to delegate a purchase.

What I wanted was an app where the agent works inside the page I'm already looking at — where it can point at things on my screen — and where the moment money is involved, it has to stop and ask me.

What it does

Procura is a procurement marketplace for workplace equipment with 25 WebMCP tools. A person and their agent use the same application at the same time.

The agent draws its reasoning into your page. When it compares products it calls compare_products, which opens a comparison table over whatever page you're on. The agent decides which product wins each row — cheapest, best resolution, longest battery — and marks its overall pick with a badge. The app supplies only the facts; the judgment is the agent's, and it lands in your interface instead of in a paragraph you have to parse.

The agent cannot spend your money. request_order_approval prices an order server-side and opens an approval panel showing exactly what will be charged. There is no tool in the app that approves an order, and the server refuses to place a proposal that isn't approved. Both layers have to be defeated, and the agent has access to neither.

The agent stays responsive while it waits. That call returns immediately — it does not block on your decision. The agent then polls check_order_approval, which reports placed, rejected, awaiting, or abandoned.

The app records who did what. Every cart item carries addedBy, every order placedBy and cancelledBy. The agent can read them back, so it can say "you added that one, not me."

Why procurement is a good fit for WebMCP

Buying for a team is research-heavy and decision-light. Someone compares a dozen monitors across resolution, refresh rate, USB-C, delivery and financing terms — then makes one decision that spends real money. The research is repetitive, structured, and safe to delegate. The spend is not. That split is exactly the shape WebMCP is good at.

How I built it

Next.js 16, React 19, TypeScript, PostgreSQL with Drizzle, deployed on Railway.

Tools are registered through document.modelContext.registerTool, split into 9 public and 16 account tools. Account tools are registered under an AbortController that is torn down on sign-out, so a signed-out visitor's agent never sees them at all. Tools re-register on route change so client-side navigation doesn't silently drop the agent's session.

The tools and the UI share one implementation — search_products and the catalog's own search box call the same function; the comparison table and get_product_details resolve through the same intelligence layer.

Challenges

The agent and I were seeing different numbers. The comparison table computed product ratings with its own local formula while get_product_details used the real one. One monitor showed 4.7 out of 5 from 107 reviews on screen while the agent was being told 4.1 from 8. On a page whose entire premise is that the agent and the person share one view, that is the worst possible bug. I routed the table through the same layer the tool uses, so they cannot diverge again.

Blocking the agent was the wrong design. My first approval flow held the agent for up to three minutes waiting for a click. That's fragile — many agent harnesses cap tool calls well below that — and worse, it left the agent unable to do the one thing that actually helps: tell the user to go and approve. I split it into request_order_approval and check_order_approval.

Telling "still deciding" apart from "walked away." Once the agent was polling, a pending proposal was ambiguous. The fix only exists because WebMCP tools run inside the page: check_order_approval checks whether the approval panel is still in the DOM. Pending proposal, no panel, means the person reloaded or left — so the agent asks again instead of polling into the void. No server API could answer that question.

The panel hung on reject. Rejection dispatched the decision and then waited for the caller to close the panel — which the non-approve path never did. Reject, the X button, and Escape all froze. Rejection needs no server round-trip, so the panel now closes itself.

What I learned

The interesting part of WebMCP isn't wrapping an API in tools. It's that the tools run in the page, which means they can read and change what the person is actually looking at — and that a tool's honesty matters as much as its function. highlight_products used to return success while silently doing nothing off the catalog page; now it returns NOT_ON_CATALOG and says what to do instead. An agent that is told the truth about what happened is far more useful than one that is told it succeeded.

Built With

Share this project:

Updates

Submission history