Inspiration
I maintain Airlock, an open-source launcher that lets one Claude Code session use Anthropic, OpenAI, Grok, and OpenRouter models side by side, with separate logins that never mix. Its local router records every rate limit, cooldown, and failover. So I had a runtime that already knew why a session stopped and what else was available.
What I did not have was a way to act on that at 2 AM. When the active model's plan hits a rate limit mid-refactor, the session dies, and a person has to read router diagnostics, work out which route is free, whether it spends metered usage, whether the conversation even fits the next model's window, and how to resume without losing the checkpoint. A browser agent could do that reading for me, but only if it could understand the runtime instead of guessing from a dashboard, and nobody wants an agent switching their models on its own.
WebMCP is exactly the missing piece: the page declares what a session, a route, and a handoff are, and both a person and an agent act on the same object.
What it does
Airlock Relay is a control room where a human and a browser agent supervise a stuck coding-agent session together.
The agent sees the runtime through seven typed WebMCP tools: get_session, get_events, get_routes, prepare_handoff, get_handoff, execute_handoff, and get_replay. It inspects what broke, then proposes a handoff. The page refuses routes on cooldown, routes whose context window is smaller than the conversation, and metered routes it was not allowed, each refusal listing alternatives.
The human sees the same state with ordinary controls: change the target, add a note, approve or reject. There is deliberately no approve tool. Approval issues a token bound to the exact proposal revision the human saw; any edit revokes it; execution is idempotent. The agent re-reads the proposal, sees what the human changed, and executes only with that token. The session resumes from its checkpoint and a replay shows who did what, in order.
Two demo scenarios ship: a rate-limited Anthropic plan where only a metered Claude route or another provider can continue, and a context overflow where a 412k-token conversation needs a route with room. Every route carries its provider's plan headroom.
It is built to be used, not only demoed. A small Python bridge runs the same console against a real Airlock session, plain Claude Code through hooks, or any agent through a documented ingest contract. It notifies you when the session blocks, records your approval itself with a one-shot nonce, persists the approved order with Airlock's own command, and offers a human-only button that relaunches the same conversation on the approved model.
How I built it
- Page and tools: TypeScript with Vite, no framework. One store holds the shared state; the human UI and the WebMCP tools mutate it through attributed actions. Tools are registered with
document.modelContext.registerTool, each with a JSON Schema, a use-case description, and annotations (readOnlyHinton reads,untrustedContentHinton the event log). Results are bounded, every error is structured with a code, a message, and a hint, and every result starts with a one-sentencemessageso the agent reads the outcome before the fields. - Demo data: the hosted demo is seeded from a real
GET /diagnosticsexport of an Airlock router. Events copied verbatim are taggedairlock, seeded onesscenario, page-made onesrelay. The session card's task and checkpoint are invented and say so. - Bridge: a single standard-library Python file. It discovers the router and the Claude Code session id, serves the built page, feeds live facts, records approvals with one-shot nonces, runs
airlock handoff setwith the approved target first and the user's declared chain kept, and opens a terminal runningairlock hybrid <route> --resume <id>(orclaude --resume <id> --model <alias>for plain Claude Code). Airlock's launcher is a Bash script, so on Windows the bridge runs it through Git Bash. - Verification: 22 Vitest tests on policy, approval, staleness, idempotency, and attribution; 25 bridge tests; a Chrome smoke test that drives the whole flow through the browser's own
document.modelContext.executeToolagainst the deployed URL; a real hook run with theclaudeCLI; and a real resume run that launched Airlock with--resumeon a scratch session and observed the Claude Code process. - Console: dark-first with layered elevation, one accent, four actor tints, a live handoff pipeline, toasts, a Ctrl+K command palette, keyboard approval, inspectable tool calls with input and output, and a one-click Markdown incident report.
Challenges I ran into
- Chrome serialises whatever a tool's
executereturns into a JSON string. Returning MCP-style content arrays double-encoded everything; returning the plain result object gives agents one level of JSON. - "The human decides" has to be enforceable, not a convention. That took a revision counter, a token bound to it, revocation on any edit, and idempotent execution so a retry cannot resume a session twice. In live mode the bridge keeps its own approval record, so a page script cannot reach the real command.
- A real orchestrator switch crosses a process boundary that only Airlock's launcher can drive. Rather than pretend, the bridge prepares the exact command, gates it behind a human-only button, and opens a terminal that runs it. Testing that for real on Windows exposed that the launcher is a Bash script Python cannot execute directly.
- A user's declared failover chain must survive a handoff. The bridge reads the current chain and moves the approved target to the front instead of replacing it.
What I learned
WebMCP changes the interaction model, not just the transport. Once the page declares semantics, the interesting design question stops being "how does the agent click this" and becomes "what may the agent do, what must the human do, and how does the page prove it". Tool descriptions are product copy: an agent that reads CONTEXT_EXCEEDS_ROUTE_WINDOW with a list of routes that fit makes a better next move than one that reads "error".
What's next
Ship the bridge as airlock relay inside Airlock itself, read live plan headroom from Claude Code's status line so live routes match the demo, and let the agent subscribe to events instead of polling. Airlock existed before the challenge; everything in the airlock-relay repository was written during it, documented in WEBMCP_CHALLENGE.md.
Log in or sign up for Devpost to join the conversation.