Keep the Agent
Action In Frame
WebMCP puts an agent on your page, working alongside you. Dolly follows the action — showing what it’s doing, what it did, and what it can do.
Contacts
24
Open deals
7
Pipeline
$86.4k
Contacts
Deals
WebMCP unlocks
multiplayer
WebMCP gives agents real controls for your site — ask in plain language and it happens, while you keep everything an interface is good at: click around, see your data, stay oriented
Not everything
should be a chatbox
The interesting shape is multiplayer: you and an agent working the same interface at the same time.
Move the Acme deal to Proposal
Done — Acme platform rollout is in Proposal now.
DollyCRM
Deals · $25,600 open
WebMCP handles the wiring: a page registers typed tools on document.modelContext and an agent calls them. Every action a menu once hid becomes a sentence — the learning curve shrinks to saying what you want. What WebMCP doesn’t handle is feedback. The agent can call tools and navigate while the page shows nothing: not what’s happening, not what’s possible, not even that an agent is connected.
For headless automation, invisible is fine. In multiplayer, the action has to be felt. Dolly is the rig that follows it — showing what the agent can do, what it’s doing, and what it did — so the work lands on the page instead of happening behind it.
The Open in
ChatGPT button
One click opens the site inside your agent — ChatGPT, Claude, or Gemini — and multiplayer starts: chat on one side, the live app on the other
The learning curve,
one sentence long
I believe buttons like this will be common. Land in an app you’ve never used, describe the outcome, and start working — no tutorial, no hunting through menus. And the agent shows up with context of its own: it already knows your calendar, your contacts, and what you’re trying to get done.
OpenInButton builds the deeplink for you — chatgpt.com/codex/deeplink?url=<your page> — and hides itself when the site is already inside an agent.
By default it links the current URL. Pass url to deeplink a specific page, connectionParam to change the handshake param, and children to replace the button label.
Note: ChatGPT is the only agent with a deeplink like this — I couldn’t find a standard way to open a page in Claude or Gemini yet.
Onboarding, once inside
The deeplink gets you in the door, but I’ve landed in an app I’ve never seen. What can it do? What do I ask? Someone has to say
Treat arrival like a first launch
The way a mobile app greets its first open: show what’s possible, plant a few ideas, point at a first action.
Manage contacts & deals
Add contacts, create deals, and move them through the pipeline — just by asking.
The deeplink carries ?webmcpconnected=true; when the provider sees it, it stores the connection, cleans the URL, and opens the onboarding dialog.
The dialog’s content comes from the provider. Each capabilities entry — a title and a one-line description — becomes a row in the default dialog, so the Trip Planner above greets you with “Plan a trip” and “Invite people”.
For a fuller welcome, steps replaces the list with a paged walkthrough — each step an optional image, a title, and a description — and doneLabel names the closing button. rig.openOnboarding() reopens the dialog any time, so a help menu can point back to it.
Show the connection
New technology has to show it’s working. Before anyone types a prompt, the page should answer the first question: is the agent actually connected?
Connected and ready to work
The header shows the connection and has room to reveal tools, history, and live progress. It tracks the live phase, so “connected” becomes “working” the moment a tool runs.
Detection is two separate signals. document.modelContext means the browser itself speaks WebMCP. detectAgent() identifies which agent injected the bridge, and the ?webmcpconnected handshake covers agents that can’t be detected directly.
How it knows it’s ChatGPT: the bridge leaves fingerprints — a __codexWebMcpModelContext global and codex-prefixed methods on the model context. detectAgent() checks for them; other agents will need their own fingerprints as they ship WebMCP support.
Show the work
This is the heart of it: follow the action. When a tool runs, the page has to show it — today the only feedback is the chat’s own spinner. On the page, nothing moves
An app that feels alive
That’s the difference: a live partner in the work, not dumb HTML being invisibly poked and prodded.
Trip itinerary
Dolly gives the tool’s execute() a visible lifecycle. startWork with overlay: true pulses a full-screen glow around the viewport edges. focus() outlines the element being changed and dims the rest of the page into a spotlight. progress() streams updates from inside the call, so long tools narrate themselves. endWork and failWork settle it with a toast.
Show what happened
One agent turn can call several tools: add something, search, navigate somewhere. For the agent to feel like a partner in the work, you need a simple log of what happened. What exactly did it do?
Magic, with a receipt
It has to feel like magic when the agent just does things, and you need a way to audit the trick when it goes wrong — like discovering it deleted your favourite pasta sauce from the shopping cart.
AGENT RECEIPT
WHAT AGENTS DID HERE
Added Maya Chen to contacts
Just nowTools declare a log template and Dolly records a readable, timestamped event each time they run. logTask() records anything else by hand. The history drawer shows it all, newest first.
The log is also the natural home for undo, retries, and error recovery. Those controls aren’t in the SDK demo, but this is the place they’d live.
Ask for permission
Some things an agent shouldn’t do on its own. For those, the tool pauses mid-execute and waits: the page pops an are-you-sure, and it resumes or aborts with the answer
A human stays in the loop
Keep destructive confirmations click-only, and let the countdown default through the safe ones.
Delete the Paris trip?
ChatGPT wants to delete this trip and its 12 bookings. This can’t be undone.
confirm() returns a promise that only settles when a person decides — the agent’s tool call simply waits on the other end.
By default the dialog waits indefinitely: nothing happens until Continue or Cancel is clicked. For routine confirmations, autoContinueMs approves after a countdown — ticking down on the button with a progress bar along the dialog edge, and pausing while the pointer hovers, so a hand reaching for Cancel is never raced by the timer.
Show what’s possible
The tools drawer shows the full range of what you and the agent can do together. Keyboard shortcuts, but for the agent
Set expectations up front
To a user the agent is a black box — ask for something the site can’t do and it fails quietly, reading as broken. Listing the tools heads that off, and the example prompts double as suggestions for what to try.
What agents can do here
3 tools
Add a contact
❝Add Maya from Northwind
Create a deal
❝Start a $12k deal for Acme
Move a deal
❝Move Northwind to Negotiation
Every tool registered with useWebMCPTool can carry an example prompt, and the drawer lists them all under “Try asking”.
AgentToolsDrawer renders the list — open it from anywhere, or let the status header reveal it.
A simple API
Adding all of this is one <RigProvider> around the app, one useWebMCPTool per tool, and a handful of imperative calls — startWork, confirm, logTask — dropped into code you already have
The core is what the agent sees: name and description tell it what the tool does, inputSchema types the arguments it must send, and execute() is plain async code — call your own functions, return a result.
The rest wires up the primitives above: example feeds the tools drawer’s “Try asking” list, log writes the receipt when the tool succeeds, and startWork/endWork inside execute() narrate the run on the page. One registration touches every surface.
useWebMCPTool registers against document.modelContext, unregisters on unmount, and does nothing when no bridge exists — the site works exactly the same without an agent.


