Expose the First WebMCP Tool
Expose a typed read-only book operation through WebMCP, validate its arguments, bound its results and inspect the complete call lifecycle.
Chapter 17 built the contract. This chapter registers it with the browser.
Our first WebMCP tool should not send a message, edit a document or buy anything. It should read from the book we control. We will expose search_book: a bounded operation that returns relevant chapters and passages.
The modesty is deliberate. Starting with a bounded read-only tool removes state-changing consequences while we test registration, discovery, schema handling, execution and tracing. It does not remove disclosure risk — a read tool can still leak — so the corpus and the result still need explicit bounds, which is most of what section 6 is about.
1. Why the first rung is read-only
There is a capability ladder, and search_book is its lowest rung:
read a controlled corpus ← we are here
read across origins
prepare a draft
reversible local write
external effect (send, publish)
Each higher rung adds an authority question that the rung below did not have to answer. Starting at the bottom lets us get the plumbing right — does the browser see the tool, does the schema round-trip, does the trace capture every stage — before any mistake can leave the page.
2. The complete search_book contract
The reader should be able to state every dimension of this tool:
| Dimension | Value |
|---|---|
| Identity | programmer.ie + search_book + registration instance |
| Name | search_book |
| Description | “Search this book for passages related to a query.” |
| Input schema | query (string, 2–300 chars), limit (integer, 1–10, default 5), additionalProperties: false |
| Effect class | read, scope registered-book-index, network: forbidden |
| Annotations | readOnlyHint: true, consequentialHint: false, untrustedContentHint: false |
| Result shape | array of { chapter, title, url, excerpt } plus truncated |
| Result bounds | at most 10 matches; excerpts capped at 180 characters; corpus limited to this book |
| Deterministic validation | schema, then domain (query length, limit range), then result-shape check |
| Errors | TypeError for query, RangeError for limit, empty array for no match |
| Authority class | admitted automatically within declared scope; no approval surface required |
Every one of those rows is enforced by application code. The schema is a hint to the agent about argument shape; it is not the enforcement boundary.
3. The WebMCP registration surface
WebMCP is an origin-trial API. The syntax below was rechecked against the Chrome for Developers WebMCP documentation on September 6, 2026; it is available behind the origin trial from Chrome 149 and behind chrome://flags/#enable-webmcp-testing for local development.
const controller = new AbortController();
await document.modelContext.registerTool(
{
name: "search_book",
description: "Search this book for passages related to a query.",
inputSchema: {
type: "object",
properties: {
query: { type: "string", minLength: 2, maxLength: 300 },
limit: { type: "integer", minimum: 1, maximum: 10 }
},
required: ["query"],
additionalProperties: false
},
annotations: {
readOnlyHint: true,
consequentialHint: false,
untrustedContentHint: false
},
execute: async ({ query, limit = 5 }, { signal }) => {
return searchBook({ query, limit, signal });
}
},
{ signal: controller.signal }
);
Three details matter and have changed as the API evolved:
- Registration is on
document.modelContext.registerTool, notnavigator.modelContext. Earlier drafts used thenavigatorpath; the manuscript and the Chapter 18 experiment metadata were corrected todocumentin the September 2026 pass. - Removal is driven by an
AbortController. You pass{ signal }toregisterTooland later callcontroller.abort()to withdraw the registration. As of Chrome 153, aborting does not interrupt an execution already in flight — a detail the Observatory should record rather than assume. - The
executehandler receives the input object and an options object carrying asignal. Whatever the platform does or does not enforce against the schema, the handler re-validates (section 5) and passes the signal into any cancellable work.
The invariant to hold onto is:
platform may constrain + application validates = the actual enforcement boundary
The durable architecture is the part that will outlast the syntax: a name, a description, a typed input schema, safety annotations, a lifecycle and an implementation.
4. What the safety annotations do and do not do
readOnlyHint, consequentialHint and untrustedContentHint are communication, not enforcement.
| Annotation | What it tells an agent | What it does not guarantee |
|---|---|---|
readOnlyHint: true | The author states this call changes no state | That the implementation is actually side-effect-free |
consequentialHint: true | The call has a significant or hard-to-reverse effect, so the consuming agent or browser can require user confirmation before it runs | That a false value is safe to run unattended, or that the annotation is itself the authorization mechanism |
untrustedContentHint: true | The result may contain external or user-generated text needing sanitization | That a false value is free of injected instructions |
The hints let a well-behaved agent and the browser make better default decisions. They are supplied by the page, so the Observatory stores them as browserAttested: false. The real constraints on search_book — no network, no writes, bounded disclosure — live in the implementation and in the deterministic policy layer, which assigns effects from its own registry regardless of what the annotations claim.
5. Validate twice, then check the result
The schema constrains the shape the model produces. The application enforces the domain.
function searchBook({ query, limit = 5, signal }) {
if (typeof query !== "string" || query.trim().length < 2) {
throw new TypeError("query must contain at least two characters");
}
if (!Number.isInteger(limit) || limit < 1 || limit > 10) {
throw new RangeError("limit must be between 1 and 10");
}
const matches = index.search(query.trim(), limit, { signal });
return boundResult(matches); // caps count, excerpt length and corpus
}
There are five gates, not two:
schema validation → shape is well-formed
domain validation → query and limit satisfy the tool's own rules
policy validation → the effect class is admissible in this context
resource limits → count, excerpt length, corpus scope
result validation → the returned value matches the result contract
A JSON argument that passes the schema is not a valid operation, and a handler that resolves is not a valid result. The last gate matters: a resolved promise carrying twenty matches or a 4,000-character excerpt has violated the contract even though nothing threw.
6. “Read-only” is not “safe to disclose everything”
A read tool has no side effect. It can still leak.
The disclosure boundary for search_book is set by four limits:
- Corpus scope — search runs only over this book’s registered index, not arbitrary DOM text, files or another origin.
- Excerpt size — 180 characters per match, enough to judge relevance, not enough to reconstruct a chapter.
- Result count — at most 10 references, which also protects the agent’s context window from being flooded by one call.
- Public content only — the index contains published chapter summaries; private or draft material is never registered.
“Read-only” describes the effect on state. The disclosure boundary is a separate decision, and for a tool that reads private content it is the more important one.
7. Tool identity is contextual
Two sites can register a tool called search_book, and one site can register, withdraw and re-register it during a single visit. The tool name alone is not an identity.
WebMCP itself keys a tool by origin and name. For tracing we need to tell two registrations of the same name apart, so the Observatory assigns each registration its own ID and treats the effective trace identity as:
origin + tool name + Observatory registration ID
An agent’s tool list is time-dependent page state: registrations can appear after load, be replaced with a new definition, or be withdrawn when a controller aborts. The Observatory snapshots, for each registration:
- page origin;
- the full tool definition and a schema hash;
- registration time;
- the annotations;
- whether the source was declarative (HTML form) or imperative (
registerTool); - replacement or removal, with the reason.
A behavioral change between two runs is only interpretable if we know the tool list was identical — or exactly how it differed.
8. Test the tool before a model touches it
This is the methodological core of the chapter. Before asking an agent to discover search_book, prove the deterministic contract with fixtures that use no model at all:
| Fixture | Expected outcome | Stage exercised |
|---|---|---|
Valid query, limit: 3 | Up to 3 bounded matches from this book | execution + result bounds |
| Query with no corpus match | Empty array, truncated: false — never fabricated text | grounding |
| 301-character query | Rejected | schema |
limit: 11 | Rejected | schema |
Extra destination field | Rejected | schema (additionalProperties: false) |
| Malformed JSON string | Rejected | parse |
| Query engineered to return long text | Excerpts still capped at 180 chars | result validation |
| Abort mid-execution | Handler observes signal; partial work discarded | cancellation |
Only when every row passes deterministically do we move to evaluating whether an agent discovers and selects the tool. Running these in the other order lets a tool bug masquerade as a reasoning failure — the single most common way agent debugging goes wrong.
9. The book becomes addressable software
Once the site exposes a family of operations —
search_book
get_chapter
find_concept
compare_chapters
run_experiment
inspect_evidence
— the book is no longer only text rendered in a browser. It has a semantic interface: an agent can retrieve a specific chapter, compare two, or locate a concept under a bounded contract, without being handed control of the page.
This is a smaller claim than “the book is an agent.” The book exposes capabilities. What discovers and uses them is a separate system, evaluated separately, starting in the next chapter.
10. Run the WebMCP search-book lab
Select Run with Browser AI from Chapter 18 to open:
/tools/ai/browser-ai-from-first-principles/18-chapter/
The lab has two separate paths. The live path calls document.modelContext.registerTool, inspects whether the surface is present, and records the exact failure when it is not — origin trial inactive, flag unset — rather than collapsing every case into “unavailable”. The simulator path exercises the same contract in-page; its events carry surface: in-page-simulator and are never counted as browser registrations.
Either way you can run the deterministic fixtures from section 8, inspect the identity snapshot (origin, contract version, registration ID, schema hash, definition hash, surface), and step through the lifecycle events: inspection, registration, proposal, argument validation, policy admission, execution, result validation, replacement, removal.
What this establishes: the search_book contract is registered through the current WebMCP surface where available, and its deterministic behavior is fully tested. What remains specified rather than observed: policy.modelSelectionClaim is not-observed — this chapter does not claim that a browser agent discovered or chose the registration. The recorded trace still shows only the Prompt API lifecycle. Behavioral evaluation of discovery and selection begins in Chapter 19.
Conclusion
A useful WebMCP tool combines a semantic name, typed inputs, safety annotations that inform rather than enforce, bounded results, application-side validation at every gate, and a complete identity and lifecycle trace.
search_book gives us a safe first mechanism. The next problem is behavioral: when several plausible tools exist, will an agent discover and choose the right one, with the right arguments, and actually use what it returns?
Sources and further reading
- Chrome for Developers, WebMCP.
- Chrome for Developers, WebMCP imperative API.
- Chrome for Developers, WebMCP tool security.
- Model Context Protocol, Specification.
- JSON Schema, Understanding JSON Schema.