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 phrase
  • get_post: fetch one post as clean markdown
  • send_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.

■