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
- css
- html
- markdown
- typescript
- vite
- webmcp
- yaml
Log in or sign up for Devpost to join the conversation.