Inspiration

The OpenAI WebMCP showcase is full of tools that hand a problem to an agent and let the human watch. We wanted the opposite. Packing a real bag is an asymmetric co-op: the human brings what cannot be left, the agent brings what the
threshold will not allow. Neither side should win unilaterally. An agent that unpacks a violin is wrong. A human who ignores a TSA rule is also wrong. We built Stow to make that negotiation visible on one shared page.

What it does

Stow is a packing cockpit for a human and their agent. They share one bag, one scale, and one manifest log.

  • The human pins items that cannot be lost (a violin, a toddler blanket, a photo album).
  • The agent proposes a repack that clears airline, TSA, and go-bag rules.
  • propose_repack is constitutionally unable to drop a pin. That contract is the product.
  • The human accepts or rejects the proposal. The agent does not touch the bag without consent.
  • seal_bag is irreversible and only succeeds when zero blocking violations remain.

Three presets cover the failure modes: a Ryanair weekend (10 kg cabin, illegal lotion, too much mass), a family cabin (toddler + liquids + 10 kg), and a wildfire go-bag (72 hours, no documents = block).

How we built it

  • WebMCP, native first. document.modelContext.registerTool runs in ChatGPT's in-app browser and in Chrome 149+ with chrome://flags/#enable-webmcp-testing. Chrome Labs' polyfill loads only as a fallback so judges without the flag still see the same tools.
  • Progressive tool registration. Tools register and unregister via AbortSignal as bag state changes. accept_proposal only exists while a proposal is pending. add_to_bag disappears after seal_bag.
  • Declarative + imperative API. A set_trip form uses toolname / tooldescription / toolautosubmit so a human or an agent can fill it. All other tools are imperative.
  • Actionable errors. Every tool returns a string the agent can recover from: Unknown item, Cannot remove "X": it is pinned, Cannot seal: cabin bag is 12.4 kg over a 10 kg limit, No pending proposal.
  • Veto-aware proposer. A deterministic greedy algorithm drops unpinned items by grams-per-utility until blocking rules clear. Pinned mass alone over the limit is reported in plain language instead of silently failing.
  • One source of truth. Human clicks and agent tool calls both go through the same Store. The UI re-renders from the same state the manifest log reads.
  • Stack: TypeScript, Vite, Vitest. No React, no Tailwind, no backend. Apache-2.0.

Challenges we ran into

  • License detection. The first LICENSE draft was a paraphrased Apache-2.0; GitHub's licensee flagged it as Other/NOASSERTION. Fixed by pulling the canonical apache.org text and adding a NOTICE file.
  • GitHub Pages base path. The polyfill script tag was hard-coded to /webmcp-polyfill.js, which 404s under a project subpath. Switched to new URL('webmcp-polyfill.js', document.baseURI).href so it resolves relative to the page.
  • Demo handoff visibility. The first cut put the agent's proposal card in the right-hand column, below the fold. Judges would have missed it. Lifted it to a brass-bordered banner above the bag with the line "Agent proposal — pins are
    kept" so the human/agent handoff is the first thing the eye lands on.
  • Video height for x264. Headless Chrome captured 1440x813; x264 requires even dimensions. Used scale=1440:trunc(ih/2)*2 instead of cropping.
  • Tool registration race. Loading the polyfill then immediately calling registerTool could collide with the in-flight script. Solved by serializing boot (await loadScript then syncTools).

Accomplishments that we're proud of

  • A WebMCP app you can hand to a non-technical person. Load a preset, pin an item, ask the agent to repack. No MCP, no ChatGPT account, no flag required. The on-page Agent Tools console lets a judge exercise every tool from Chrome
    DevTools.
  • A veto the agent cannot override. propose_repack is the only repack tool. It is non-mutating and refuses to drop a pin. The companion test (propose.test.ts) asserts the violin survives any number of over-weight scenarios.
  • Actionable, recoverable errors. Every tool returns a string a future agent can act on. No Error: undefined. The wrapper tests pin those strings.
  • 23 passing tests, 4 files, one engine. Bag invariants, airline/TSA/go-bag rules, veto-aware proposer, and progressive tool registration. The CI runs them on every push.
  • Apache-2.0. Canonical license text, NOTICE file, public repo, public Pages, public video. The "About" sidebar shows Apache-2.0.

What we learned

  • WebMCP's biggest win is shared state, not tool calling. Once human and agent write to the same store, the UI stops being a spectator sport.
  • Annotations matter. readOnlyHint and destructiveHint change how an agent decides between a propose_repack and a seal_bag. The Chrome DevTools WebMCP panel surfaces them; ignoring them makes the agent overconfident.
  • The polyfill is a real product surface. Even on Chrome 150 the flag isn't always on, and judges may not have ChatGPT's in-app browser open. Falling back to Chrome Labs' polyfill is the difference between a one-tool demo and a
    working app.
  • Progressive registration is the right pattern. Aborting and re-registering tools as state changes is more honest than letting accept_proposal silently return No pending proposal for every call. The agent can see what it is allowed
    to do.
  • The Devpost rules want a recognisable snippet. search_products with the exact schema from the rules is in the repo. Subtle but the judges' checklist would have flagged a missing one.

What's next for Stow

  • Live airline data. Pull the real Ryanair 10 kg / BA 23 kg limits from a feed instead of the static table.
  • Multi-human packers. Two people packing a shared family bag, each with their own pins.
  • A "trust meter" the agent earns. Track which proposals the human has accepted and start suggesting riskier repacks for items the human keeps un-pinning.
  • Real boarding-pass constraints. Seat-pitcher rules, gate-check bag size, lithium battery watt-hour caps.
  • A watch-your-language layer for agents. Detect when a tool result includes user-influenced content and warn the agent before it mutates the bag.
  • A backend for multi-device. Right now the bag lives in the page. With backend MCP the human and agent could keep packing on the train while the laptop sleeps.

Built With

Share this project:

Updates

Submission history