-
-
Live xagent.id. Every card on screen is the deployed page: real companies, real public evidence, nothing claimed on their behalf.
-
The target published no public proof, so it stays pending. Missing evidence is never converted into a pass.
-
After the click: four commands, bound to a digest of the exact plan on screen, handed to the owner. The page executes none.
-
Machine speed, human authority. Live at xagent.id/agent, with a command and an expected result for every claim in VERIFY.md.
-
One task, three conditions. Without WebMCP: 134,552 bytes over 9 requests. With it: 6,460 over 2. Wall clock: inconclusive.
-
Asked for the install bundle before approval, the page refuses: human_approval_required. The bundle does not exist yet.
-
Chrome's own WebMCP panel, in DevTools, listing this page's eight tools registered with document.modelContext.registerTool.
-
Approval is a real browser-trusted click on a visible control. No tool parameter and no page script can assert it instead.
Inspiration
Onboarding a website to an identity service is a negotiation, not a page of text. It needs the current release, the exact target host, the modules the site is granting, the files that will be written, a rollback path, and a human decision in the middle.
We watched both halves of that fail. An agent reading our rendered HTML could only guess at the contract, so it stalled or invented commands. A human owner opened the docs, copied four commands into a terminal, and hoped the version, the host and the grants lined up. Neither party had what it needed, and the one step that genuinely required a person, deciding, was the step that copy-paste quietly skipped.
WebMCP suggested the fix. Not "let the agent click the buttons", but: let the page state its contract as tools, and keep the human decision a decision.
What it does
xagent.id registers eight top-level tools with document.modelContext.registerTool, so a WebMCP-capable agent can onboard a website together with its owner.
- inspect_onboarding reports the tool surface, the authority limits and the current step. Read-only.
- preview_agent_id previews the public Agent ID card for a target website.
- get_install_plan loads the target-bound install manifest, without commands.
- choose_verification_method selects public-file domain proof.
- request_owner_approval asks the human for a visible decision, and waits.
- prepare_install_bundle returns the approved commands for the human to run.
- verify_installation reads independent public evidence for the target.
- get_onboarding_status reports the current onboarding state. Read-only.
Only those two carry readOnlyHint, because only those two change nothing.
The shape of the journey is the point. The agent does the assembly: release identity, module grants, declared write paths, and a digest of the exact plan. The page shows the owner which files will change and which permissions are being granted. The owner approves on a visible control. Only then does prepare_install_bundle return commands, and it returns them to the owner. The page executes none of them. The agent executes none of them.
Ask for the bundle before approval and you get human_approval_required. Not a warning. A refusal.
Why WebMCP is the right fit here: the trust boundary already exists, and this keeps it intact.
The tools live in the page the owner already trusts, so there is no connector to install, no API key to paste, and no third party standing between an owner and their own website. The agent gets the real contract, bound to one canonical HTTPS origin, instead of its own reading of the rendered page.
What people and agents can now do together that neither could do alone: the agent assembles a plan a human would find tedious and error-prone, the exact release, the exact host, the module grants, the files that will be written, and a digest binding all of it. The owner supplies the one thing an agent must never supply, which is the decision. Before this, the owner did the assembly by hand and the decision collapsed into copy-paste. An agent alone still cannot finish, and that is the design, not a limitation.
How we built it
The tools are one vanilla JavaScript module shipped by the page itself, at /assets/webmcp-onboarding.js. No build step, no framework, and nothing for the owner to install.
Approval is the part we spent the most care on. It requires a browser-generated trusted activation on the enabled, visible wizard control, and completion of the permissions-to-install transition on screen. There is no public approval mutator and no writable exported state, so no tool parameter and no page script can assert approval on the owner's behalf. Editing the target, changing the manifest, or a failed refresh all revoke it.
For evidence we built two harnesses. The first walks the same task three ways, human, agent without WebMCP, and agent with WebMCP, and aborts the entire run if any request returns anything other than 200. The second drives a real Chrome over the DevTools Protocol with the WebMCP testing flag enabled, discovering tools through document.modelContext.getTools and giving approval with real mouse events at the control's own coordinates.
The submission video is that second harness running on a clean cloud VM against the deployed site, with Chrome's own WebMCP panel open in DevTools counting the calls as they happen. Every frame is a recording, and the uncut source recordings are published alongside the film.
Challenges we ran into
A microtask that arrived too early. Approval worked in our tests and never worked in Chrome. The browser runs a microtask checkpoint between event listeners, so our approval landed while the page was still mid-transition and was discarded. The fix revalidates every precondition on a real timer instead of trusting a single moment. Our tests had passed because they dispatched listeners in an order no browser actually uses; the regression test now reproduces the real ordering.
Feature detection that lied. executeTool kept failing with "Failed to parse input arguments" until we found that arguments must be passed as a JSON string rather than an object.
A release pipeline that had never once succeeded. Deploying the fix meant first repairing six separate faults in it, ending with a skaffold key that is spelled cloudrun and not cloudRun. Until that was done the correct code existed only on our own machines, which is not the same thing as shipped.
Measuring honestly when the numbers did not cooperate. Our wall-clock ranges overlapped between conditions, so we could not claim a speed win. Saying so turned out to be harder than not measuring at all.
Accomplishments that we're proud of
The refusal works, in a real browser, on the live site. Eight tools discovered, prepare_install_bundle refused with human_approval_required before approval, four commands returned after a real click, and none of them executed by the page. That run is recorded and published.
The measured result is specific. To reach the same target-bound install plan, an agent following our own documented public trail takes in 134,552 bytes over 9 requests. Through WebMCP it is 6,460 bytes over 2. That is 20.83 times less material and 4.5 times fewer round trips, and both figures came out identical on every run.
What we did not measure is stated as plainly as what we did. Wall clock is reported as inconclusive, because the ranges overlap. Human duration is reported as not measured, because no participant was timed. A test in the repository fails if either one is ever quietly converted into a number.
Every claim in our public README has a command and an expected result in VERIFY.md. A reviewer never has to take our word for anything.
What we learned
The interesting question is not what an agent can do. It is what it must not do. The value of this integration is concentrated almost entirely in the one call that refuses. Everything else is convenience; that one is the product.
Works in tests and works in a browser are different claims, and the gap between them was a single microtask. We now treat any test that models browser event ordering as suspect until it has been reproduced against a real browser.
Honest measurement is a design constraint, not a reporting step. Once we decided that nothing unmeasured could appear as a number, we had to build the harness so that not measured was a value it could carry all the way to the screen. That changed how we built, not just how we wrote it up.
A page can hand an agent a contract instead of prose, and it costs one module. The hard part was never the tool surface.
What's next for xAgent.ID — WebMCP onboarding
Measure the human path properly. Our comparison paths are scripted, not observed users. Real participants and real production timings would let us say something about time that we currently, and correctly, cannot.
Take the approval pattern out of onboarding. Trusted activation plus a digest-bound plan is not specific to installing an identity layer. It is a general shape for any agent action that needs a human to own the consequence, and we want to publish it as a pattern other sites can adopt.
Extend verification beyond public-file proof to the other evidence types our issuer already understands, keeping the same rule: missing evidence stays pending and never becomes a pass.
Follow the standard. WebMCP is moving, and we would rather track the spec than our own head start.
Built With
- a2a
- agent-identity
- chrome
- gleif
- javascript
- model-context-protocol
- python
- web-standards
- webmcp


Log in or sign up for Devpost to join the conversation.