Inspiration

Real shopping rarely happens one store at a time. "Outfit me for a 3-day desert backpacking trip, under $600, I already own a stove" is one job, but today it is eight product searches across as many stores, a spreadsheet to keep the budget honest, and a lot of tabs. Agents can help, yet the ones we have shop a single site per page and treat you as a spectator: you type, they act, and if you disagree with a pick you start over in chat.

WebMCP's brief asks what people and agents can do together that was hard before. Our answer is a planning board both of them work on. The agent does the tedious part, searching every Shopify merchant and building a kit that fits the budget; the person does the part only they can do, locking the sleeping bag they trust, rejecting the shoes they hate, and the agent re-plans around those decisions instead of over them. Shopify merchants already expose public Storefront MCP endpoints, and Shopify's Global Catalog MCP searches across all of them, so the missing piece was not the data. It was a page that exposes the mission itself as tools an agent can use.

What it does

Skulora Outfitter is a shared board for shopping missions. Open https://outfitter.skulora.com in ChatGPT's desktop browser or in Chrome with WebMCP enabled and tell the agent what you need. Then:

  1. The agent plans the mission. create_mission parses the goal, budget, constraints and what you already own; plan_kit turns it into slots (pack, sleeping bag, pad, water, sun protection…) each with a reason and a share of the budget.
  2. It searches every store. search_products runs per slot, in parallel, over Shopify's Global Catalog MCP plus curated merchants' Storefront MCP, dedupes and re-ranks candidates for fit, and choose_candidate picks with a stated reason. The board shows a live budget meter across merchants.
  3. You co-drive on the board. lock a pick the agent must keep, reject one it must never retry, choose a candidate yourself, or change the budget. Every edit is queued and delivered to the agent as mission_delta inside its next tool result, so it re-plans around you without being told.
  4. It explains itself. explain_tradeoffs writes a grounded "why this, versus the alternatives" under every slot, using only candidates that are actually on the board.
  5. It hands off, it does not buy. prepare_checkout creates one real cart per merchant through Storefront MCP and the board shows one checkout link each. You complete the purchase; nothing is bought on your behalf.

In the reference run the agent planned 8 slots in about 6 seconds, filled them from 6 merchants at $553 of a $600 budget, and produced real checkout links. In ChatGPT's desktop browser it discovered the tools, found the Stage B tools that appeared after the mission was created, ran every search in parallel and finished with 8 merchants at $554.51 of $600. The demo video is one unedited run each: ChatGPT fills the board at $373.17 across 7 merchants; a lock and a reject lead to a re-plan that keeps the locked pack and swaps the rejected tent, ending at $446.96 across 8 merchants with real checkout links.

The four things the rules ask every description to answer, in their order:

Fit: why this use case belongs on WebMCP. A mission is structured state with a small, well-defined set of operations. Exposing it as typed tools lets any agent complete the job faster and more reliably than guessing through a UI, and lets the page shape what an agent may do (plan, search, choose, explain, prepare) while the human keeps the decisions that matter.

Better experience: what changes for the person. One sentence, "outfit me for a 3-day desert trip, $600, I own a stove", becomes a 9-slot kit searched across every merchant and chosen with reasons in 27 seconds, instead of eight store searches, a spreadsheet for the budget and a dozen tabs. The person never leaves the board and never re-explains: the agent sees each lock and rejection as first-class input, the budget is enforced across stores instead of per cart, and the finale is one checkout link per merchant with the reasoning next to it rather than a wall of chat.

Together: what people and agents can now do as a team. Plan a multi-store purchase as a team, in one place, with the agent doing the breadth (every merchant, every slot) and the person doing the judgment (this one, not that one, this much), and each sees the other's moves immediately. Lock a pack and reject a tent, say "re-plan", and the agent keeps the pack, replaces only the tent and says why, because the edits reached it as data.

How: the WebMCP implementation. The page registers nine typed tools with document.modelContext.registerTool(...), disclosed in three stages as the mission advances (toolchange tells the agent about each new stage), with readOnlyHint and untrustedContentHint annotations and results sized for an agent's context. Every result carries mission_delta (the person's edits since that agent's last call) and next_suggested_tools, and every rule (locks, rejections, budget, an incomplete kit) is enforced on the server with errors that name the valid ids and the next tool, so agents that call out of order or in parallel recover on the next turn. The built-in agent drives the same tools through document.modelContext.executeTool, so Chrome's DevTools logs it like any external agent. Details below.

How we built it

  • Tool surface. One tool table (src/lib/webmcp/tools.ts) defines nine tools with JSON Schema inputs and compact outputs sized for agent context: Stage A get_mission, create_mission, set_budget; Stage B plan_kit, search_products, choose_candidate, explain_tradeoffs; Stage C prepare_checkout, get_checkout_status. Read-only tools carry readOnlyHint; tools that return merchant content carry untrustedContentHint. Every result ends with mission_delta (the person's edits since that agent's last call) and next_suggested_tools.
  • Registration. src/components/WebMCPTools.tsx calls document.modelContext.registerTool(...) for the tools of the current stage, one AbortController per tool. When the mission advances it registers only the tools new to that stage, leaving existing registrations (and any call in flight) untouched, and agents learn about the additions through toolchange. Chrome's DevTools WebMCP pane shows the list, the per-tool call counters and the invocation log.
  • Progressive disclosure. Stage A is registered on load; Stage B appears once a mission exists; Stage C once every required slot has a pick. Within a mission a stage only ever advances, so a tool is never pulled out from under a call in flight. The agent only ever sees tools that make sense right now.
  • Shared state. Missions live server-side (Next.js route handlers on Vercel, Upstash Redis). Because agents call tools in parallel, writes are compare-and-set with retry, and anything slow (planning, merchant search, LLM re-ranking) runs outside the mutation. The board polls and the human's edits are stamped with a cursor the agent's mission_delta is computed from.
  • Merchants. Cross-merchant discovery uses Shopify's public Global Catalog MCP (with a self-hosted agent profile) and per-store Storefront MCP for details and carts; no store setup, no credentials. A planner and re-ranker run on a small OpenAI model with strict JSON schemas.
  • Same tools, any browser. A built-in agent panel drives the same tool table through the OpenAI Responses API. Where the browser provides WebMCP it runs each call through document.modelContext.executeTool, so Chrome's DevTools logs the built-in agent's calls exactly like an external agent's; elsewhere it calls the tool's execute directly. It makes the page work in browsers without WebMCP and gives us a deterministic harness (pnpm harness), probe (pnpm probe) and co-driving check (pnpm g3) whose outputs are the only source of the numbers we quote.
  • Abuse limits. Agent steps, mission mutations and mission creation are rate-limited per IP (Upstash), with a kill switch for the judging window; pnpm probe --burst exercises the limit.
  • Guardrails for careless agents. Every mutation is validated on the server and every error names the valid ids and the tool to call next; create_mission on a board with picks needs replace: true; a re-plan keeps what the person locked or chose; carts are cleared the moment a pick changes and prepare_checkout refuses while a required slot is empty; merchant_domain is allowlisted before the server contacts any host; tool stages only ever advance within a mission. pnpm misuse drives all of it out of order through document.modelContext.executeTool in headless Chrome (36 checks).

Challenges we ran into

  • Agents call tools in parallel. ChatGPT fired every search_products at once and a read-modify-write race dropped candidates. Fix: compare-and-set writes in Redis with retry, and slow work moved outside the mutation.
  • Mission state that survives the agent. The first version kept the mission in memory; the agent reloaded the page mid-mission and the board went blank. Missions moved server-side and became URL-addressable (?m=id).
  • Dev stores are password-gated, so their MCP endpoints redirect to /password. We dropped them for real, public merchants and probed which ones answer (7 of 7 in the curated set; a few big brands rate-limit or block).
  • Availability is not the same as in stock. A cart failed on a variant the catalog listed as available. The merchant's error now surfaces on the checkout card and in the tool result instead of being swallowed.
  • Keeping outputs small. Chrome's guidance caps tool output around 1.5 KB; explain_tradeoffs returns a summary for all slots or full detail for one.
  • The DevTools log stayed empty. Chrome only records calls that pass through the browser's model context; our built-in agent was calling the tool functions directly. Routing it through executeTool exposed a second bug: re-registering the whole tool table on a stage change aborted the very call that advanced the stage, and Chrome reported it as failed although it had run. Registrations now persist across stages and only the new tools are added.
  • Agents misuse tools, and the board has to stay coherent. Driving the tool table out of order in headless Chrome found six real problems: a second create_mission silently replaced a board in progress, a re-plan wiped picks the person had made but not locked, checkout cards stayed on the board after a pick changed, an arbitrary merchant_domain made the server call any host, a mission id the server no longer had left the board and the tools disagreeing, and un-registering Stage C tools when the person emptied a required slot aborted the agent's call in flight. Each fix is a server-side rule with an actionable error, not a hope that the agent reads the description.
  • Small UI things that break agent flows: a prompt() budget editor the agent could not drive, an OS dark mode that made the board unreadable, a useEffect cleanup bug that only appeared on Send. Each was reproduced with Playwright and fixed the same day.

Accomplishments that we're proud of

  • A complete mission in ChatGPT's desktop browser using only tools this page registered: plan, cross-merchant search, choose, explain, real checkout links, and the agent noticing tools that appeared after load.
  • Co-driving that works end to end: lock a pack, reject a tent, say "re-plan within budget", and watch the agent keep the lock, replace the rejection and say so, because mission_delta told it.
  • Real carts on real Shopify merchants with zero merchant onboarding, and a hard stop at the checkout link so the person stays in control of money.
  • Evidence you can re-run: pnpm probe, pnpm harness, pnpm g3 and pnpm misuse regenerate every number in this description; the saved pnpm g3 run on production passes all six co-driving checks (9/9 slots filled in 27 s, locked pick kept, rejected pick replaced, other slots untouched, checkout links open real carts in Chrome).

What we learned

  • Design the tool surface for the agent's context, not the developer's convenience: few tools, typed inputs, compact outputs, and a next_suggested_tools hint go further than a large API.
  • Human edits should be data the agent receives, not instructions the human has to repeat. Putting mission_delta in every result made collaboration automatic.
  • Assume the agent will call tools out of order, twice, or in parallel: put the rules in the server with errors that say what to do next, treat derived state (carts, explanations) as something to invalidate, and only ever add tools within a mission.
  • Progressive disclosure is a real UX tool for agents: fewer irrelevant options, fewer wrong calls, and a visible narrative of the mission's stages.
  • Verify like a judge: the bugs that mattered were only visible in ChatGPT's browser or in a plain headless Chrome, never in the dev console.

What's next for Skulora Outfitter

  • Compatibility checks between slots (pack volume vs. sleep system, fuel vs. stove) as a tool the agent can call before committing a kit.
  • Missions beyond the outdoors. Nursery, first apartment, race day, home office: the planner is domain-agnostic already.
  • More merchant surfaces: any store that exposes Storefront MCP works today; shipping and policy tools (search_shop_policies_and_faqs) can feed delivery constraints into the plan.
  • Shareable missions: a board two people and one agent plan together, with per-person locks.
  • Declarative tools for Chrome, and WebMCP Evals in CI so tool descriptions stay agent-readable as they change.

Built With

Share this project:

Updates