Theme fit

AgentGate directly uses the WebMCP API — document.modelContext.registerTool(), getTools(), and executeTool() — to register three real WebMCP tools (propose_action, check_approval, list_pending) that any WebMCP-aware AI agent can discover and call on this page. It solves a real, open problem in the WebMCP spec itself: there is currently no built-in way for a page to require human confirmation before an agent's tool call takes effect (see webmachinelearning/webmcp#165 and #50). AgentGate is a working, application-level answer to that gap.

Inspiration

WebMCP lets a website expose real, callable functions to AI agents — a huge step up from agents having to fake being a human clicking through a UI. But that power cuts both ways: the moment an agent calls a tool, it runs, instantly, with nothing built into WebMCP to pause and ask a human first. The spec's own maintainers have this flagged as unresolved. We wanted to build the missing piece rather than just another demo of the happy path.

What it does

AgentGate gives a site a safe way to expose sensitive actions to agents. Instead of wiring a tool directly to something real (delete a file, send a message, spend money), the site registers propose_action. Calling it doesn't do the real thing — it queues a request and shows it in a live approval inbox for a human to review. A human approves or rejects, optionally with a note. The agent calls check_approval with the proposal id to find out what happened, and only proceeds with the actual action once it sees "approved".

How we built it

React + Vite front end. document.modelContext.registerTool() registers the three tools directly against the real WebMCP API — no framework or SDK wrapping it, just the native browser API as documented. State (proposals, their status, notes) lives in a small observable store backed by localStorage, so the approval history survives a reload. No backend — the whole mechanism works client-side, which is enough to prove the pattern end-to-end.

Challenges we ran into

WebMCP is genuinely experimental — getting a real document.modelContext in an unmodified browser required registering for Chrome's WebMCP Origin Trial and embedding a token scoped to our exact origin. Once we had a real API to test against (not a mock), we also found the actual behavior differs from a naive reading of the docs — getTools() and registerTool() are both asynchronous (return Promises), and executeTool() takes the tool object itself plus a JSON-string of arguments, not a tool name and a plain object. We also hit a real bug this way: React's StrictMode double-invokes effects in development, and since the real API has no unregisterTool, that caused duplicate-registration errors — fixed with an idempotency guard.

Accomplishments we're proud of

Getting this working against the actual WebMCP implementation, not a simulated one — calling propose_action through the real document.modelContext, approving it as a human in the UI, and watching check_approval reflect that back correctly.

What we learned

That an open spec with unresolved design questions is fertile ground for a real contribution, not just a demo — and that testing against the real API (even a gated, experimental one) surfaces mismatches with the documentation that no amount of reading catches.

What's next

A cancellation path using AbortSignal (the spec supports it for in-flight tool calls), and richer proposal types (e.g. structured diffs for code-change proposals) so a human reviewer has more context before approving.

Built With

Share this project:

Updates

Submission history