Prompt API · tool use · WebMCP

Closed circuit

Drop 007 put a model in the tab. Drop 027 put agent-callable tools on the page. They never met. Here the model reads a plain-English errand, decides which of this shop's four tools to call, calls them, and books the job. The request counter in the top right is measured live, not asserted. It should not move.

01 · the errand

Give it something to do

Cadence Cycle Works is a made-up bike shop with four real tools: match a symptom to a service, quote it, find a slot before your deadline, book it. Type an errand the way you would say it out loud. Watch which tools it reaches for.

detecting… webmcp: checking outbound requests 0

Registered tools

This run

0 tool calls0 rejected by schema
What you are actually seeing. Checking what this browser gives us…
02 · the loop

Where the server used to be

A booking agent is normally four network hops: browser to your backend, backend to a model vendor, back with a tool call, backend executes it, round again. Every hop is a place your customer's words get logged. Here the model and the tools are in the same tab, so the loop closes with none.

01

The tools are just functions

Four plain JavaScript functions with JSON schemas. The same objects are handed to the model and registered on document.modelContext for future browser agents.

02

The model picks

It gets the schemas and the errand, and decides the order. Nothing here routes by keyword when the model is present, which is why it sometimes picks badly.

03

The page validates

Every argument is checked against the schema before the function runs. A bad call is rejected and printed, not silently retried until it looks tidy.

// one object, two consumers, zero servers
const TOOLS = [{
  name: 'price_quote',
  description: 'Price and duration for a service on a bike type',
  inputSchema: { type:'object',
    properties:{ service_id:{type:'string'}, bike_type:{type:'string'} },
    required:['service_id'] },
  execute: (a) => quote(a.service_id, a.bike_type)
}, /* …three more */];

// consumer 1: the on-device model calls them itself
session = await LanguageModel.create({ tools: TOOLS });

// consumer 2: browser agents, once anything actually reads this
for (const t of TOOLS) await document.modelContext.registerTool(t);
03 · the counter

Why the receipt is a number, not a sentence

Any page can print "your data never leaves your device". This one wires a PerformanceObserver to every resource the document fetches and shows you the count. Run the agent as many times as you like. If a single byte went out to answer you, the pill turns pink and the number moves.

It counts everything the browser fetches after load, including anything a well-meaning future edit might add. That is the point of measuring instead of claiming: the check keeps working after we stop paying attention.

04 · receipt

Verify it yourself

> source verification
[MODEL] on-device Prompt API · detecting
[TOOLS] 4 functions · JSON schemas · every argument validated before execution
[WEBMCP] document.modelContext with a navigator fallback · detecting
[NETWORK] 0 outbound requests since load, measured by PerformanceObserver
[WEIGHTS] 0 bytes of model shipped: the browser's model or nothing
[ASSETS] 0 CDNs · 0 webfonts · 0 analytics · single HTML file · works offline
[LIMIT] Gemini Nano needs a supported Chrome and ~16GB RAM. Most visitors get the rule-based planner.
[LIMIT] Small models call tools badly. Rejections are printed in the transcript, not hidden.
[LIMIT] No mainstream agent consumes WebMCP tools yet. Registering is positioning. See drop 043.
Back to the Labs →