WebMCP lets a website hand tools to AI agents: JavaScript functions or annotated forms exposed through navigator.modelContext, the same tool-call shape MCP uses server-side. Every tutorial answers the same question: how to add it. After shipping WebMCP on this blog, I think that is the wrong question. The right question is what to expose, and my answer kept shrinking: I shipped six tools and removed three.
This site is the test bench. Every page registers WebMCP tools through a small adapter, verified live against Chrome 149’s modelContextTesting API. Enable chrome://flags/#enable-webmcp-testing in a Chromium browser and the three surviving tools are callable on this page right now.

TL;DR: I removed every WebMCP write path from this site: a guarded newsletter-signup tool and two declarative form annotations. The read tools (search, listings, page content) stayed. Agent-side writes collide with twenty years of bot defenses; a honeypot cannot tell an invited agent from a spam bot. The rule that survived: expose reads to agents, keep writes behind human intent.
The web spent twenty years treating a bot in a form as an attack. WebMCP invites the bot in, and your defenses do not read invitations.
What six tools look like on a static blog
The scripted API is one registration call per tool: a name, a description, an input schema, and an execute function, per the W3C explainer. Four scripted tools went live, all backed by data the site already had: searchPosts over the Lunr index that powers the site’s Cmd+K search, listRecentPosts from the same search.json, and getPageContent for the article the reader has open. The fourth, startNewsletterSignup, prefilled and focused the signup form but never submitted it; a human had to click Subscribe.
The read tools carry readOnlyHint, so an agent knows it can call them without asking. The write tool was deliberately guarded, and it is still the one I deleted.
The declarative API goes further: two attributes turn any HTML form into a tool.
<form action="/subscribe" method="post"
toolname="subscribeNewsletter"
tooldescription="Subscribe an email to the
newsletter. The reader confirms.">
...
</form>
No JavaScript. The browser synthesizes the tool’s input schema from the form’s controls. I annotated both newsletter forms, and Lighthouse’s WebMCP coverage flag went green.
That schema synthesis is where the trouble starts.
The honeypot paradox
Both newsletter forms carry a honeypot, the classic anti-spam trap: an input that human visitors never see and therefore never fill. Spam bots parse the HTML, find one more field, and fill it. Honeypots reject silently by design; you do not tell a bot it was caught. The newsletter stack sits behind this kind of trap.
Now put a WebMCP annotation on that form. The browser synthesizes an input schema from the form’s controls. The spec’s schema-synthesis rules for hidden controls are still TBD, so nothing today guarantees a visually hidden input stays out of the schema. If the browser exposes it, an agent behaves exactly like the spam bot the trap was built for: it sees a field, it fills the field. The submission dies server-side, silently, and the agent reports success to a user who never gets a confirmation email.
I patched around it the only way the declarative API allows: the honeypot itself carried toolparamdescription="Anti-spam field. Must be left blank." as its schema note.
That annotation is an instruction to the polite robots, posted inside a trap built for the impolite ones, hoping the browser passes the note along. That is a hope, not a defense.
I call this the honeypot paradox: every form defense (honeypots, timing checks, CAPTCHA) encodes the assumption that automation at a form is hostile. Agent-ready forms revoke that assumption, but the defenses do not know it. The same form now serves two bot populations, one invited and one not, and the machinery that filters the second cannot see the difference.
This is not a newsletter problem. Every form that relies on the rule “bots fill fields humans never touch” inherits the same conflict the moment it invites agents in.
You cannot annotate your way out. Either the defenses learn to recognize invited agents, or the invitation is withdrawn. For a newsletter form, the choice was easy once I weighed what the write tool was worth.
The tool that promised too much
Its first name was subscribeNewsletter, but the tool did not subscribe anyone; by design it only prefilled and focused the form. An agent reading the name would promise its user “I subscribed you,” and be wrong. So I renamed it startNewsletterSignup, which is what it actually did. Tool names are API contracts with a reader that acts on them literally; shipping an MCP server teaches the same rule.
The rename fixed the honesty problem and exposed the value problem. The tool saved one click: it scrolled to the form and typed an email into it. The reader still had to review it, click Subscribe, and confirm the double opt-in email. That click bought several new failure modes: a misread intent or someone else’s address produces a spam-trapped submission, a confirmation email nobody asked for, or a subscriber who never wanted in. No automated path produces a better subscriber than the manual one.
I only realized how one-sided this trade was after watching the declarative tool show up in the inspector with a schema I never wrote, honeypot and all. Six tools, and the three that touched the form all subtracted value.
So version 1.10.1 removed all three, and the site is better for it.
What the spec doesn’t answer yet
Shipping the tools left three questions the current explainers do not settle.
Which namespace? Early Chrome builds and the W3C draft expose navigator.modelContext; newer builds move to document.modelContext. My adapter resolves it in one place:
function getModelContext() {
if (document.modelContext) return document.modelContext;
if (navigator.modelContext) return navigator.modelContext;
return null;
}
One function, and every tutorial that hardcodes one namespace has an expiry date.
What happens to duplicate tool names? My footer form and in-article CTA both render on post pages; if their toolnames collide, which one is the tool? The spec does not say, so I gave them distinct names.
What do hidden controls become? The honeypot question, still open. Until schema synthesis specifies hidden, off-screen, and aria-hidden controls, every declarative form with an anti-bot field is exposing an undefined behavior to agents.
None of these blocked shipping, but the hidden-control rule decides whether the declarative API and the anti-spam web can coexist at all.
The read/write line
The rule the whole experiment reduced to: expose reads to agents; keep writes behind human intent. Call it the read/write line.
Reads made the site better with no downside. An agent that can call searchPosts and getPageContent answers its user’s question from the actual content, not a scrape or a stale snapshot. The data was already public; WebMCP just made it structured.

Writes are where value flips. A write that matters (a subscription, an order, a booking) matters precisely because a person chose it, and the defenses around it assume the chooser is human; automating the choice adds risk exactly where the site cares most about consent. I have made the mirror-image mistake before, giving an agent full API access it did not need; every writable tool is the same kind of unattended capability, and tool surfaces deserve a security review whether they live on a server or in a browser.
The line is not “never expose writes.” A retailer will want agents to fill carts; toolautosubmit exists because someone will eventually want fully delegated checkout. But a write tool must clear a bar a read tool never faces: its value must survive the removal of human intent, and the whole path, spam defenses included, must be engineered for automation. My newsletter form clears neither. Most marketing forms will not.
The correct amount of WebMCP
Three tools, all reads, all backed by data the site already served. That is the right amount for a content site in 2026.
The tutorials frame WebMCP adoption as coverage: annotate the forms, register the tools, clear the audit flag. Coverage is the wrong score. Every tool is a promise to an unattended caller.
If your agent-ready plan starts with the subscribe form, start over. WebMCP’s namespaces are still moving; the collision between agent-ready interfaces and twenty years of bot defense will outlast them. Until the specs and the defenses agree on which bots are guests, give the guests the library and keep them out of the guestbook.