Skip to content

Build1 publisher2 min readPublished

A page behind Chrome's WebMCP flag registers three agent tools without an MCP server

document.modelContext.registerTool works in Chrome only when enable-webmcp-testing is on, and the demonstration page falls back to a third-party polyfill everywhere else. Its scoring tool runs entirely in the browser.

The Engineer · Build desk

What happened

  • A page registers a tool with document.modelContext.registerTool(), and an agent inspecting that page calls it. No MCP process, transport or working directory is involved at any point.
  • The API is Chrome's own and experimental, reachable only by users who enable it at chrome://flags/#enable-webmcp-testing.
  • Where the native API is absent, the page falls back to @mcp-b/webmcp-polyfill for the same registerTool surface, a package the post describes as real, independent and not the project's own.
  • One of the three registered tools, fill_6ws, is a plain HTML form with toolname, tooldescription and toolautosubmit attributes, and the browser turns that form into the callable tool.
  • score_faf scores entirely in the browser against a WASM kernel compiled from the same Rust source as the project's CLI and its edge workers, client-side from start to finish.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • capability Shipping an agent tool becomes a static deploy: there is no process to supervise or transport to configure, and a tab has no concept of a working directory to point at.
  • constraint Until the native API ships unflagged, any page that wants its tools called in a default browser depends on a third-party package for the registration surface itself.
  • exposure Once a tool takes a URL as an argument, the page fetches on a caller's instruction, and the host allowlist plus the recheck after redirects bound where that fetch can go.
  • decision Annotations travel inside the registration, so a page author now decides what an agent learns about side effects before it calls, and an agent client has to decide whether to honour that signal.

The fallback code does not assume document.modelContext is usable just because the object exists. It checks that registerTool is typeof 'function' before it returns source 'native' [5]. Only then does it dynamically import @mcp-b/webmcp-polyfill inside a try/catch, call initializeWebMCPPolyfill(), and ask the same question again [5]. If the import throws, or if no registerTool appears afterwards, the function returns { context: null, source: 'none' } and the page stays an ordinary page [6].

So a tool reaches an agent by one of two routes here, and only one of them is a browser feature. The native route needs Chrome with chrome://flags/#enable-webmcp-testing switched on [1], which I would treat as a test configuration rather than a deployment target. Everywhere the native API is missing, registration comes from @mcp-b/webmcp-polyfill, which the post calls "a real, independent package, not FAF's" [4]. In my view that package is the dependency to review before shipping anything that depends on this. The post does not say which Chrome build or which polyfill version it tested.

I would look at the URL argument first. score_faf and emit_agents_md accept a URL in place of pasted YAML, so a caller's argument decides what the page fetches [12]. assertAllowedUrl rejects a non-https protocol with ToolError('invalid_url', 'url must use https') and rejects any hostname outside ALLOWED_HOSTS [13]. The check then runs a second time after redirects, against the URL the request actually landed on [14]. That second run stops an allowlisted host from 302-ing the fetch somewhere off the list.

The denominator is worth copying. A comment in the result mapper records that ignored slots "are not missing", and that the score is "13/13, not 13/21" [15]. Count the inapplicable fields as gaps and the same project scores 61.9 percent instead of 100, a deficit of about 38 points that editing the file cannot close [16]. gapsFromSlots reports only the fields that are genuinely empty [15].

Both script-registered tools carry annotations of { readOnlyHint: true }, and an agent can read that before it calls anything [8]. emit_agents_md writes Setup and build, Run the tests, Stack and 6Ws from the same .faf fields the CLI reads [17]. Its own source comment calls it a "Minimal renderer", "not the full faf-cli AGENTS.md compiler", and it omits Guardrails tiers and branch-aware Commit and PR [18]. The rendered file ends with a one-line stamp saying it was compiled from application/vnd.faf+yaml, not "compiled with faf-cli" [19]. The post argues the underlying capability is "generic, open, and not owned by anyone's product" [20]; the evidence offered for it is one page registering three tools, written by the project that ships the CLI [7].

What to watch

  • Whether document.modelContext lands unflagged in a stable Chrome release, and whether the surface still looks like registerTool when it does.
  • Whether a second browser engine implements the API, or @mcp-b/webmcp-polyfill stays the layer every page ships to get registerTool.
  • Whether page-registered tools acquire discovery and permission rules, so a user can see which tabs are exposing tools to an agent.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories