Making this site usable by AI agents (WebMCP build log)
How a static Astro site learned to offer real tools to AI agents through WebMCP, what I had to fix while building it, and the rule I started with that turned out to be wrong.
Most websites are written for eyes and thumbs. A growing share of browsing now happens through an agent: you ask it to find something or reach out to someone, and it reads pages on your behalf. Most sites give it nothing better than HTML to squint at.
I wanted this site to do better. Not by adding a chatbot, but by telling the agents that visit exactly what they can do here, in a form they can call directly. This post is about that, and the parts that didn’t go the way I planned.
I built it the way I build most things now: AI coding agents do a lot of the implementation, and I set the plan, make the calls, and keep enough visibility into every step to steer and check the work. I’ll cover that workflow in an upcoming post.
What WebMCP is
WebMCP is a proposed web standard that lets a page register tools: named actions with a description and an input schema. A browser or agent that supports it can see those tools and call them, instead of guessing which buttons to click.
As of October 2026 it is still early. Chrome supports it behind a flag
(chrome://flags/#enable-webmcp-testing) and an origin trial, and only a
few agents can actually call tools today. Chrome’s documentation is the
best starting point: WebMCP on Chrome for Developers (opens in a new tab).
The surface: three tools
This site offers three:
search_posts: search the writing by topic or phraseget_post: fetch one post as clean markdownsend_message: send me a message
The tool data is built from published posts at build time, so there is no database behind search and no API call. Registration looks like this (trimmed):
const mc = detectModelContext();
if (mc?.registerTool) {
for (const tool of toolDescriptors()) {
mc.registerTool({
...tool,
execute: (input) => api.callTool(tool.name, input, "agent"),
});
}
}
If the browser doesn’t support WebMCP, nothing registers and nothing breaks. The home page panel shows a simulated example, and the Lab lets you run the same functions by hand and watch the results in a log. When WebMCP is there, the panel switches to a live badge, and agent calls made on the Lab page show up in that same log.
The API had already moved
The original proposal put the API on navigator.modelContext. Chrome’s
implementation lives on document.modelContext. I found this out by
reading the current docs before writing any code, which is the habit I’d
recommend for anything this new. The site checks for both:
function detectModelContext() {
if (document.modelContext?.registerTool) return document.modelContext;
if (navigator.modelContext?.registerTool) return navigator.modelContext;
return undefined;
}
I expect it to move again before it ships by default, so I built the fallback to work without it.
Testing it for real
In automated headless Chrome with the flag on, the tools registered and
document.modelContext.getTools() listed all three. But calling a tool
through the browser’s own executeTool() never returned in that
environment. Rather than pretend otherwise, I verified it by calling the
same functions the registration installs, and noted the gap.
The real proof came from an ordinary Chrome window. DevTools now has a WebMCP panel under Application. It lists the page’s tools, lets you run them, and shows each call’s input and output. Running the contact tool there completed cleanly and showed exactly what an agent gets back. If you build with WebMCP, that panel is the fastest way to see your site the way an agent does.
The rule I started with was wrong
My first version of the contact tool never sent anything. It prepared a draft and waited for a human to press send. I even had a line for it: agents compose, humans commit.
It sounded responsible, but it was solving the wrong person’s problem. If someone has set up an agent and authorized it to contact people for them, my site has no business second-guessing that. Consent belongs to the visitor and the agent they configured, not to me.
WebMCP already supports this. A tool can carry a consequentialHint
annotation, which marks it as a real-world action and lets the agent or
browser decide whether to confirm with its user first. So send_message
is marked consequential and it sends:
{
name: "send_message",
description: "Send Simon a message. This is a consequential action: " +
"your agent or browser decides whether to confirm with you first.",
annotations: { consequentialHint: true },
// ...input schema: email, kind, message, and optional details
}
What the site still owns is protecting me. The endpoint can only ever deliver to one inbox, and it has the usual abuse controls behind it. I’m not going to list them here. Visitors decide whether to send. I decide how much to accept.
A static site grows a tiny backend
Actually sending email meant the site needed its first server code. It’s
a small Cloudflare Worker that only runs for /api/*; every page is
still a static file. It uses Cloudflare’s send_email binding to
deliver to my verified Email Routing address, which turned out to need
no mail server and no paid plan, because sending to your own verified
destination is free.
The contact form at /contact is the same endpoint for
everyone. A person fills it in like any form, and it works without
JavaScript. An agent can call send_message with the same fields,
including what the message is about, which is how a company’s research
agent could reach out about a project.
One gotcha worth knowing
Every branch gets its own preview deployment, and my first preview test
of the contact endpoint crashed. Cloudflare’s docs say it plainly:
previews do not inherit production settings. Static assets carry over,
but bindings like email and rate limits have to be declared again in a
previews block. Once I did that, the preview sent a real message and
it landed in my inbox.
Try it
- Visit the Lab and run the tools by hand.
- To see the live path, turn on
chrome://flags/#enable-webmcp-testing, reload, and open DevTools → Application → WebMCP. - Or point an agent that supports WebMCP at this site and ask it to find something here, or to get in touch.
What I’m taking from this
Read the current docs before writing code. Build the version that works without the new thing first, then add the new thing on top. Write down what didn’t work right next to what did. And let the visitor own consent.
Next I want to figure out what else an agent should be able to do on this site. If you’re weighing something like this for your own site, or you want a second pair of eyes on an agent surface you’re about to ship, send me a note. The same goes for anything you wish more sites let agents do.
■