We will be undergoing planned maintenance on Oct 7th 6:00AM UTC / Oct 7th 2:00AM ET

Live URL: https://gallery402.stacktr.ee/ · Video: https://youtu.be/TEGE-Sl1RXg · Repo: https://github.com/stevysmith/gallery-402

Sixty seconds, no wallet, no setup

  1. Open the live URL. The WebMCP pill turns green — the page has registered its tools with the browser.
  2. Click Buy on the Print Room ($0.01). Your wallet was staked 5¢ of test USDC on arrival, so it just works.
  3. Watch the ledger: quote → policy → EIP-3009 signature → settled on-chain → ticket issued. Your balance drops. The transaction hash links to Basescan.
  4. Click Enter. The full-resolution Hokusai is served only because you hold the ticket.

Then, if you have ChatGPT desktop in Work mode, ask it "get me into the Van Gogh room" and watch it do the same thing through the page's tools. (Three host gates catch people out — Work mode, a Sol/Terra model, and an undocumented per-site permission prompt — all written up in CHATGPT-TESTING.md.)

If the first load hangs for ~30 seconds, that's the box office waking up — it's on a free host that sleeps when idle. The page retries and narrates the wait; it isn't broken.

Why this use case fits WebMCP

Museums are visual: the whole point is to stand in front of the picture. Paying for admission is not. Today a paywall interrupts the thing you came for — a form, a card, a redirect, a login. Gallery 402 puts the human and the agent on the same page, literally: the visitor looks at Monet while an agent in their browser handles the box office through the page's own WebMCP tools. The agent doesn't scrape the page or drive the cursor; it calls gallery_buy_ticket and the page pays over HTTP 402 with a wallet the visitor controls, inside a spending limit the visitor set.

That is the shape of a lot of the future web — content behind small, per-use prices, paid by agents on behalf of people — and WebMCP is the missing piece: a typed, page-provided surface so the agent can act with the site rather than on it.

The real problem this solves

The web's business model assumes a human who can be advertised to, tracked, or made to sign up. Agents break all three. A crawler that reads your archive shows nobody an ad; a subscription flow assumes someone to fill the form; a login assumes an account holder. So publishers reach for the only lever left — block the agent — and the open web gets smaller. That is happening right now: Cloudflare shipped pay-per-crawl because "block everything" was the only alternative on offer, and its Monetization Gateway — charge any caller for any resource over x402 — is currently waitlist-only. Gallery 402 is that product working end-to-end today, built from open parts: a Hono middleware, a public facilitator, a page that signs. Everything the gateway will do for a site, this repo shows a site doing for itself.

Per-use pricing is the way out, and it has been technically possible since HTTP 402 was reserved in 1997. What was missing is not the payment rail — x402 settles a median $0.028 call today across roughly 165 million transactions — but a way for a page to tell an agent what it sells and let it buy without pretending to be a human. That is exactly what WebMCP adds: a typed, page-provided surface where "this costs two cents" and "here is your receipt" are first-class actions rather than a checkout page to be screen-driven.

A museum is the demonstration, not the market. The same three pieces — a 402 at the door, a typed tool surface, a ticket bound to the payer — are what a news archive, a research corpus, a legal database, a stock-photo library or a metered API needs. We chose a gallery because it makes the paywall visible: you can see precisely what you bought, at the resolution you paid for, and the transaction hash under it. Swap the collection for a paywalled archive and nothing about the mechanism changes.

The alternative being built instead is agents with saved card details and human-shaped checkout flows, which is worse for everyone: the merchant can't price per use, the user can't cap spend, and the agent has to impersonate a person to transact at all.

How it improves the experience

  • Nothing leaves the room. No checkout page, no redirect, no popup. The wall changes colour and the painting is there.
  • The human sets policy; the agent operates inside it. "Auto-approve up to $0.05" or "ask me every time" — set in the wallet panel, honoured by buy_ticket, and deliberately not exposed as a tool.
  • Every step is legible. The ledger narrates quote → policy → signature → settlement, and each settled payment prints a ticket stub with its transaction hash.
  • Discovery is free; consumption is priced. The /wings manifest — works, artists, dates, real dimensions, teaser images — costs nothing and needs no ticket, so an agent can browse the catalog, compare rooms and quote prices before a cent moves. Only the full-resolution originals sit behind the 402. That split (open catalog → priced checkout) is the same shape agentic commerce is converging on, at museum scale.
  • Same tools, two doors. The identical catalog is the ⌘K palette for humans and the WebMCP registration for agents. A visitor without an agent-capable browser gets the same actions by hand, or via the in-page agent.

Why only WebMCP

Each alternative fails at a different wall. Preprogrammed tours can't know the visitor — no curator wrote "boats, for a ten-year-old, in ten minutes," and the museum's own in-page agent knows only what you type at it. The visitor's agent can't know the museum — but through plan_tour the agent that knows the person composes an itinerary from the catalog, and the museum prices the doors: neither side could author that tour alone. Screen-driving can't be trusted with money — a "Buy" button is just pixels, but buy_ticket is a typed, discrete verb without readOnlyHint, which is exactly why ChatGPT paused to ask permission before paying in our end-to-end run; safe delegation of spending requires the page to declare the boundary, and tool registration is the only mechanism the web has for it. A server-side MCP could plan and even pay — but not stand in the room: here the agent's tour_step glides the same wall the human is looking at, and the human's click comes back as visitorPointing. A travel agent versus a docent beside you.

One museum, four visitors, four different museums. We ran the same request — "ten minutes, my daughter loves boats and water, skip anything gloomy" — through four different agents, and got four different tours: a Claude harness composed four stops at $0.02; ChatGPT desktop composed five including the Great Wave; ChatGPT's cloud browser composed six and excluded the Wave as too fierce for a child, titling it "for a Young Explorer" and closing with a question to ask her ("Which boat would you choose?"); a second desktop run composed "Boats, bright water & blue skies." No curator wrote any of them. That variance isn't noise — it's the point: the agent brings everything it knows about this visitor (their kids, their taste, their ten minutes) to a page that knows only itself, and plan_tour is where they meet. Hyper-personalisation is what WebMCP unlocks that no preprogrammed tour, chatbot widget, or screen-scraper can: the page supplies the collection and the prices; the agent supplies the person.

What humans and agents can do together that they couldn't before

A visitor can say "give me a ten-minute tour about light" and watch the agent compose stops, pay two doors inside their limit, glide them in and spotlight the details — or "buy me a day pass and show me the Great Wave" and watch it happen on the page they are already looking at — the agent discovers the wings and prices, negotiates an HTTP 402, signs a gasless USDC authorization, receives a ticket bound to the visitor's wallet, walks into the room and stops at the right print — while the visitor keeps the only control that matters: how much the agent may spend without asking. Before WebMCP, an agent could only do this by screen-driving a checkout, with no way for the page to express prices, policy or confirmation as first-class actions.

Implementation

  • WebMCP: twelve tools in the lobby, thirteen once you hold a ticket, eighteen once you're inside a room, registered with document.modelContext.registerTool (falling back to navigator.modelContext) via agentk's useWebMCPRegistration — prefixed gallery_, with readOnlyHint annotations on the six read-only tools, titles, AbortSignal-based unregistration, and isError on failures so an agent can tell a rejected payment from a receipt. Tool errors are written as next-step instructions ("No ticket for the Van Gogh Room ($0.02). Call buy_ticket…") — the error channel is the agent's UX, and it is treated as API surface: the eval suite asserts on error texts the way UI tests assert on copy (a locked door must quote buy_ticket and the price; an unknown title must point at list_artworks).
  • Three ways in, none of them a dead end: the museum's own curated tours are one click from the lobby (no agent needed); the ⌘K palette runs the same tools with a multi-step in-page agent; and a WebMCP agent drives the whole thing. New visitors are staked a few cents of test USDC on arrival, and start_tour buys the cheapest combination of doors — singles or a day pass — so the money decision is made well rather than blindly.
  • You keep something: save_tour publishes the tour — its works, the docent's notes, and the settlement receipts for what admission cost — as a standalone page on Stacktree that outlives the session.
  • The docent layer: the agent plans a guided tour into a shared itinerary the visitor edits (reorder, drop), pays for exactly the doors the route needs (a day pass when cheaper), glides the visitor along a wall, spotlights details with its own notes as wall text, and hangs works side by side. The visitor clicks a spot on a painting and the agent reads it (visitorPointing) — collaboration in both directions on one canvas. Because WebMCP can't push to the agent, the page also keeps a docent of its own: a click sends the real painting and the click position to a vision model, and the answer comes back as a spotlight and wall text (ask_docent exposes the same to agents).
  • The tool surface, shown to the human, priced: a panel lists what the agent can do right now and animates capabilities in and out as the page changes, with live prices on the tools that spend money (start_tour quotes the exact doors the current itinerary needs). A WebMCP page shouldn't only be usable by an agent — it should show the human what the agent can do, what it just did, and what it costs. The compatibility suite asserts the panel matches the registered surface exactly, so it can never overstate what an agent may do.
  • Three kinds of tools, three disciplines. Read-only tools (list_wings, look_around, wallet_status…) are flat, always registered, and marked readOnlyHint — an agent never has to walk somewhere to ask a question. Navigation tools (enter_wing, walk, go_to_lobby) double as the page's system prompt: their descriptions and the howTo in look_around teach the flow. Write tools that spend money (buy_ticket, start_tour) run inside the visitor's spend policy and surface a confirm sheet above it — the human sees "here's what I'm about to pay, yes or no" before anything settles.
  • A live tool surface: the registered set follows page state — no walk in the lobby, buy_ticket only for wings you don't hold (gone once you have a day pass), enter_wing only for wings you can enter — re-registered on change so the browser's toolchange fires. Reading the tool list tells an agent where the visitor is.
  • Verified against Chrome's real implementation: the tour flow was driven through Chrome 151's document.modelContext.executeTool — which surfaced that pre-153 Chrome aborts an in-flight call when its tool is unregistered. agentk 0.6.2 now defers surface changes until calls return; the eval harness flags any regression.
  • It works in ChatGPT, end to end, through the tools. Asked "Get me into the Van Gogh room" in ChatGPT desktop's Work mode, ChatGPT discovered the site tools, told the visitor the ticket costs $0.02 and that their wallet was empty, asked permission to fund and pay, and on approval called fund_wallet, buy_ticket and enter_wing over document.modelContext — the museum's ledger shows the full x402 round trip (quote → policy → EIP-3009 signature → settlement → ticket) driven by the agent, not by clicking. Three gates a judge must pass first — Work mode, a Sol/Terra model, and a per-site browser permission prompt that isn't in the docs — are written up in CHATGPT-TESTING.md.
  • Tested inside ChatGPT's in-app browser, not just Chrome. We drove ChatGPT desktop over CDP and confirmed the museum registers and runs there — including the dynamic surface (12 tools → 16 after loading a tour). Doing it surfaced two host differences the docs don't spell out: ChatGPT exposes only document.modelContext (no navigator.modelContext, which many WebMCP tutorials still use — those pages register nothing there), and its executeTool enforces the spec's object argument where Chrome also accepts a JSON string. agentk 0.6.2 handles both; the method is written up in CHATGPT-TESTING.md.
  • Two tool surfaces on one page, coexisting: published on Stacktree, the agent sees thirteen tools — our twelve plus the host's own stacktree_page_info. Nothing in the spec says who owns document.modelContext, and in practice the page and its host both register into it, so an agent's view is the union. Namespacing every tool gallery_ is what keeps that merge unambiguous; a page that registers bare list or buy will collide with its host sooner or later.

  • Compatibility, verified not assumed: npm run compat asserts the page matches ChatGPT's documented site-tools subset (registration on document.modelContext, top-level page, no iframes, no declarative forms) and Chrome's published budgets for tool names, descriptions and output, across multiple page states. It caught a 4038-character list_wings response — the first call any agent makes — now split into two tools.

  • Evals: 34 cases against the real page through a fake document.modelContext. All run as scripted replays (tools compose; payments, approvals, isError and surface changes hold); 28 also run model-driven — a real LLM given the live tool list, judged on which tools it chose (one day pass for "see everything cheaply", no purchase for "what does it cost?"). The six scripted-only cases are error-text contracts, not natural asks. 34/34 and 28/28.

  • Social proof, live: the box office keeps a settlements ledger; the lobby shows who was admitted where with tx links, and whos_here exposes it to agents.

  • Payments: x402 v2. The box office (Hono + @x402/hono) answers 402 with PAYMENT-REQUIRED; the page (viem + @x402/fetch/@x402/evm) quotes, applies policy, signs EIP-3009 transferWithAuthorization, retries with PAYMENT-SIGNATURE; the public facilitator verifies and settles USDC on Base Sepolia; the box office reads the payer from the signature and issues an HMAC-signed ticket; artwork is served only to ticket holders.

  • Two payment rails: each ticket route accepts test USDC on Base Sepolia (the in-page visitor wallet, so judges need nothing) and real USDC on Base mainnet through the Coinbase CDP facilitator — the production x402 setup Stacktree already runs. An agent with a real wallet never has to touch the page: it can pay the 402 directly, get the same signed ticket, and hand it to the museum through present_ticket — the page verifies it with the box office and opens the door, like a human who bought online. A forged ticket gets a refusal that points at the 402. The deployed box office advertises both rails on every door (/health lists eip155:84532 and eip155:8453); testnet is offered first so an unfunded agent takes the free path. Proof of the mainnet rail: an agentcash wallet bought a Van Gogh Room ticket for real — 0xfa53dc8d…907f on Base, 0.02 USDC from 0xf2de…68bc to the payee, block 50583731 — and the ticket it received admitted it to the room.

  • Wallet: a self-custodied key generated in the page and kept in localStorage, so it works in ChatGPT's in-app browser with no extension. A testnet faucet tool lets judges try a real settlement in seconds.

  • Hosting: the museum is one self-contained HTML file published on Stacktree (which serves Chrome's WebMCP origin-trial token); the box office runs on Render.

  • agentk (pre-existing, extended during the challenge): @stevysmith/agentk@0.6.2, published to npm during the challenge and installed by this repo from the registry — not vendored. It adds WebMCP annotations/title/isError to useWebMCPRegistration, defers surface changes while calls are in flight, and runs multi-step agent turns; 180 tests. All museum code is new for the challenge.

The real-world version

We know the difference between a winning demo and a product. The transaction is proven; the product work that remains sits around it:

  • Discovery. Today the museum is a URL someone hands you. The real-world version is listed in the x402 Bazaar — via the CDP facilitator's bazaar extension — so an agent that has never met a human can find "virtual museum, $0.01 admission" the way it finds any paid API. We solved payment; discovery is where demand comes from, and it's the next integration.
  • The merchant's side. Everything here celebrates the visitor. The box office already holds the seller's story — revenue by door, which rooms earn, what agents asked for that isn't sold (tool calls are the new analytics) — and the real product surfaces it as a back office. That is the view a publisher or API owner judges this pattern by.
  • Entitlements that outlive the browser. Tickets are already bound to the payer's address, so "restore my visit on my phone" is just proving control of that address — an account system with no signup. The same bearer property makes present_ticket a gifting mechanism today: the payer and the visitor don't have to be the same person.
  • Metering the museum's own AI. Every docent answer costs the museum inference, capped at 60/day. The real-world version prices the docent itself over x402's upto scheme — pay what the answer costs — turning an eaten LLM bill into a per-use product. And because the 402 is generated per request, pricing is a decision point, not a constant: return-visitor discounts (the payer is in the signature), free-entry Sundays, and the day pass — our one shipped bundling feature — are each an if statement, not a billing integration.
  • Where does a normal person's USDC come from? Honestly: not from the page. The site can stake cents (our faucet, testnet-only); the durable answer is the agent layer — fund your assistant's wallet once from a card, and every site's 402 is paid by the same identity. That is the world this museum is built for, and why the box office accepts a ticket paid by anyone, presented by anyone.

Prior work vs. new work

  • New (Aug 28 → Sep 3): everything in this repository — gallery, box office, collection, wallet/x402 flow, tools, design.
  • Pre-existing: agentk (command palette + WebMCP registration hook, first released March 2026, on npm since May) and Stacktree (static hosting). Changes to agentk made during the challenge are the two commits on the webmcp-0.6.2 branch, dated Aug 30 (WebMCP annotations/title/isError, in-flight surface deferral, multi-step agent runs, and their tests).

Testing notes for judges

  • ChatGPT Work, cloud browser (announced Aug 31): open the live URL there — it's fully deployed, which is the only way to be testable in a remote browser.
  • ChatGPT desktop: open the live URL in the in-app browser; the "WebMCP · 12 tools" pill turns green. Ask: "Get me into the Van Gogh room."
  • Chrome: enable chrome://flags/#enable-webmcp-testing (or use the Stacktree URL, which carries the origin trial), then use the Model Context Tool Inspector or Gemini.
  • agent-browser (CLI): npx agent-browser open https://gallery402.stacktr.ee/ && agent-browser webmcp list — agent-browser ≥0.36 ships native WebMCP support, and the museum works through it with zero adaptation: webmcp invoke gallery_buy_ticket --params '{"wing":"ukiyo-e"}' settles a real testnet payment and returns the ticket + tx hash. A fifth independent client, and the fastest way for a judge to smoke-test the tools from a terminal.
  • Chrome DevTools: the experimental WebMCP panel shows the registered tools live — watch the surface change as you buy a ticket and walk between rooms, and invoke any tool by hand. All 23 tools carry descriptions on every parameter.
  • No agent: press ⌘K — same tools.
  • The museum stakes every new wallet 0.05 test USDC on arrival; "Top up" (or the fund_wallet tool) drips more if you run dry. Tickets are $0.01–$0.02; the day pass is $0.04.

Collection

24 public-domain works (Art Institute of Chicago, The Met, Cleveland Museum of Art, Rijksmuseum, J. Paul Getty Museum), each credited and linked to its source record.

Every work hangs at its true size. Each carries the real dimensions from its museum's own record, and a single pixels-per-centimetre factor governs the whole building — so Hokusai's Great Wave hangs at 25.4 × 37.6 cm, about the size of an open laptop, while Monet's Wheatstacks is more than a metre across. The wall spaces the works by their own width, so a room of prints shows six at once and a room of canvases shows two. The agent gets the same fact the human reads on the label: look_around and list_artworks report the dimensions, and so does the free /wings manifest — so an agent can tell you a room is six small prints before you pay to walk into it.

Built With

  • agentk
  • claude-api-(vision-docent)
  • coinbase-cdp-facilitator
  • hono
  • playwright-eval-harness
  • react
  • remotion
  • render
  • stacktree-hosting
  • typescript
  • usdc-on-base-mainnet
  • usdc-on-base-sepolia
  • viem
  • vite
  • webmcp
  • x402-v2-(@x402/*)
Share this project:

Updates

Submission history