Pular para o conteúdo

What a page registers with WebMCP, and what a client reads back

Browser readings 2026-09-07 (UTC) · Chrome 152.0.7977.77 · production · 12 tools

A page registers a tool by handing registerTool a name, a description, an input schema, an output schema and a set of hints. Four readers give back four different subsets of what went in: the page's own API, the DevTools protocol, a scanner driving a real browser, and a scanner that only fetches. The 2 that run inside one browser build disagree with each other, and both disagree with what the page declared.

LabelDetail
PlatformmacOS arm64
AutomationPlaywright 1.60.0, headless and headed
Flags testedwith and without --enable-features=WebMCP,WebMCPTesting,DevToolsWebMCPSupport
Fetch-only readscurl, the same day, no browser
Targetproduction trickybird.com, 12 tools across 10 route patterns, documented at /webmcp
Re-readthe tool count and both graders' public records, 2026-09-08

What you hand registerTool is not what a client reads back

Chrome 152.0.7977.77, production, 12 registered tools, 2026-09-07.

Chrome 152 exposes registered tools on 2 surfaces: document.modelContext.getTools() inside the page, and the WebMCP.toolsAdded event over the DevTools protocol, after WebMCP.enable. Page-side, every tool came back carrying exactly 7 keys, and that key set was identical on all 12: annotations, description, inputSchema, name, origin, title, window.

FieldRegistered withgetTools(), page-sideWebMCP.toolsAdded, protocol
name, descriptiondeclaredpresentpresent
inputSchemaobjectJSON string, parses back to the object the page passed inJSON object
outputSchemaobject, on all 12absent on all 12absent, and the Tool type the browser publishes at /json/protocol declares no such field
annotationsthe registered hintspresent, reduced to 2 keyspresent, the same 2 keys under other names
annotations, read-onlydeclared per toolreadOnlyHint, true on 9, false on 3readOnly, the same single bit
annotations, untrusted-contentdeclared or notuntrustedContentHint, false on tools that never declared ituntrustedContent
annotations, consequentialHint, idempotentHint, openWorldHintdeclaredabsentabsent
titledeclared or nonepresent, empty string on the 10 that register noneabsent, and that same Tool type declares no such field
origin, windownot passedadded by the browsernot in the event payload
frameId, stackTracenot passednot in the returned objectadded by the protocol

The absence is the browser's, not the page's. An init script installed before page scripts replaced registerTool on the document.modelContext instance and recorded each argument before calling through, in the same run: all 12 went in with an outputSchema, with inputSchema as an object, and with hints that never came back.

One read-only tool invoked through WebMCP.invokeTool answered with an invocationId and a toolResponded whose output was {"content":[{"type":"text","text":...}]}, referencing neither an output schema nor structured content.

Both surfaces answered with no Chrome flags set, adding the feature flags changed nothing in the output, and the headless and headed runs agreed. The page serves an origin-trial token; the token was never withheld to test it, so why the unflagged runs worked is inference, not measurement. navigator.modelContext was absent in 152, where document.modelContext is the live name.

Page-side, in a console on any page that registers WebMCP tools:

(await document.modelContext.getTools()).map(t => [t.name, Object.keys(t), typeof t.inputSchema])

Agent-side, with a debugging client. Enable the domain before navigating and read the tools off the event payload; a Playwright session opens its own client, so this needs no launch flag:

const cdp = await page.context().newCDPSession(page);
const added = [];
cdp.on('WebMCP.toolsAdded', event => added.push(event));
await cdp.send('WebMCP.enable');
await page.goto(url);

The domain's own schema, including its Tool and Annotation types, is served at http://127.0.0.1:<port>/json/protocol, which does require Chrome launched with --remote-debugging-port.

A description that instructs the agent is data, not a control

Production, 2026-09-07; the release that changed the strings merged 11:42 UTC and deployed 11:52.

Earlier that day, an audit by webmcp.ora.ai read 95 of 100 and put nearly all the missing points into one Trust check named Injection surface, weight 3 of the 13 points of Trust weight that scan found applicable. That reading is our own capture of the report as it rendered at 10:57:39 UTC: the grader publishes no audit history, so it cannot be retrieved from its site. The check looks for tool metadata that instructs the agent reading it instead of describing the tool.

That shape appeared in 5 agent-visible strings across 3 tools. Of those, 4 ended with a sentence telling the caller not to repeat a refusal automatically, and a fifth opened with a directive scheduling one tool's call before another's. All 5 changed in one production release: the 4 lost the trailing sentence outright, and the fifth lost the directive and kept the fact it rested on, which describes what the tool does instead of telling the caller what to do.

The removal was decided on what bounds a retry, not on the score. A grep of the frontend source for the description constants, re-run on 2026-09-08 against the shipped tree, returns 4 readers: their definitions, the hook that hands them to registerTool, the registry that renders the public tools table, and a test. None of them enforces anything a description says. Two examples, server first. Both read in the same tree, on 2026-09-08.

ControlWhere it runs
A server-side budget answering 429 with Retry-After when a caller exceeds itthe API endpoint a launch ultimately calls, outside the caller's edit path
An in-flight latch answering busy as the first statement of the call, ahead of any network workour own interface, where a caller is free to strip it

The grader saw 2 of the 5 strings, its capture being 1 page and 4 tools, so the other 3 changed on our own judgment of the rule rather than on its finding. Its re-audit at 12:28:54 UTC the same day reports nothing left for that check to flag. Its own text: "This check only ever flags; it never credits a site." Removing the strings moved it out of the applicable weight, from 13 to 10, rather than earning points back.

Check your registration literals against the strings a scanner greps for

curl against production, 2026-09-07 22:01 UTC, no browser. Specification text: the `webmachinelearning/webmcp` README on main, fetched the same day.

A record for this site sits in 3 directories, and they disagree about what it exposes.

DirectoryRecord dateMethodTools foundStatus or note
webmcp.com2026-09-07real browser8, listed with their JSON"Tools verified Sep 7, 2026"
webmcp.ora.ai2026-09-07real browser4, on the home pageregistration recorded 259 ms after load
webmcpdirectory.com2026-09-05static fetch"0 tools"Unverified; detected a WebMCP surface but "could not read individual tool names"

Those 3 records are 3 readings of 1 origin, each carrying its own date. They are not a comparison of the 3 products, and they say nothing about how any of the 3 reads any other site.

The third directory publishes what it looks for: a Cloudflare Worker fetches the submitted URL and scans "the HTML plus its same-origin scripts" for 3 signals. The served home page carries none of registerTool, modelContext or lowercase webmcp in its HTML; the only case-insensitive hits there are 2 React component names in the streamed payload.

Signal the scanner checksIn the served HTMLIn our scripts
navigator.modelContextabsentpresent once, reached as a fallback after document.modelContext
registerTool call sitesabsentpresent twice, in the same file
a descriptor at /.well-known/mcp.json404not applicable

All of those hits sit in 1 of the 18 same-origin script files the page links, at 22,938 bytes, which also holds the 4 home-page tool names as plain string literals. So 2 of that scanner's 3 signals are present, inside a file it says it reads, and the third is not present at all.

Why the record reads 0 is not determinable from outside. The HTML and that script both returned 200 with identical byte counts to 4 bot-shaped user agents, one identifying itself as a Cloudflare Worker, so no fetch wall was reproduced from here. The record is at least 2 days stale: its own description still names a tool renamed on 2026-09-06.

2 of those 3 signals also sit outside the current draft. The README spells document.modelContext 25 times, navigator.modelContext 0 times and .well-known 0 times, and files "Static Declarative Manifests" under alternatives considered and not taken, because a static manifest cannot follow page state or carry the execution handler.

A grade reads the crawl, not the surface

Both scans were requested through the graders' own self-serve forms. Neither was paid for, and neither is a human review.

webmcp.ora.aiwebmcp.com
Scan date2026-09-07, 12:28:54 UTC2026-09-05, 19:13 UTC
Basis1 page, 4 tools, against its spec snapshot of 2026-08-266 pages (/, /unblock, /security, /faq, /compare, /learn/what-is-a-web-proxy), 8 tools
Executionthe report states the run read the registry and invoked nothing, so its 2 execution-weighted checks, weight 6 each, scored not applicableexecuted all 8 it found
Score99 of 100, grade A+, over applicable weight88 of 100, grade A
BrowserChrome 151.0.7922.34the scan record discloses none
Where the number sitspublic at https://webmcp.ora.ai/trickybird.coman unlisted job record, held here as evidence and not linked; what the grader publishes for the host lists the tools and a verified date, with no score field

Neither grade reads the whole surface. WebMCP tools register per page, so the page set a crawler opens fixes both the tool count it reports and the sample its score is computed over. The surface registered 13 tools when the 2026-09-05 scan ran; 2 were merged into 1 and it has registered 12 since, with 3 renames on 2026-09-06 changing no count. Of today's 12, 4 register only on /unblock/<slug>, /learn and /proof, which neither crawl opened.

Both numbers moved after a change made in answer to that same grader. The 88 followed read-only tools added after 3 scans on 2026-09-04 that read B, 70 of 100, against a 1-tool surface, and the 99 followed the string removal above. Those earlier B readings sit in job records of the same unlisted kind, held here rather than published by the grader.

When this stops being true

WebMCP ships in Chrome behind an origin trial that expires 2026-11-17, and the WebMCP protocol domain it exposes is marked experimental. Any of it can move in the next patch, which is why this page carries 1 review, on 2026-11-10, and a second trigger that is not a date: any Chrome stable major after 152 re-opens the browser readings, whichever comes first. A measurement here that has gone false and cannot be corrected takes the page down rather than aging on the domain.

Back to WebMCP on TrickyBird