Check out my upcoming talks for Boston AI Week — see the full lineup.

This Website Now Has Tools for AI Agents

OpenAI announced today that the ChatGPT desktop app’s built-in browser now supports WebMCP. So I spent the afternoon adding it to this website. If you’re reading this in a browser that speaks WebMCP, this page is no longer just a page — it’s a small set of tools your agent can call.

Below I’ll explain what WebMCP actually is, clear up a naming collision that tripped me up, walk through what I built, and finish with a pile of sample prompts you can paste into ChatGPT to try it yourself.

What WebMCP is

Right now, when an AI agent visits a website, it does what you’d do if you were locked out of the API: it reads the screen. It scrapes the DOM, guesses which <div> is a product listing, hunts for the button that looks like “Submit,” and hopes the markup didn’t change since last week. It works often enough to demo and badly enough to be frustrating.

WebMCP inverts that. Instead of the agent guessing what your site can do, your site tells the agent what it can do. You register tools — a name, a description in plain English, a JSON Schema for the inputs, and a function to run — and a supporting browser hands that list to the agent. No scraping, no coordinate math, no brittle selectors.

If you’ve built an MCP server before, the mental model transfers directly. The difference is that there’s no server. The tools live in the page, run in the page’s own JavaScript, and inherit the session the user is already signed into.

Here’s the entire API surface for a trivial tool:

if (typeof document.modelContext?.registerTool === "function") {
  await document.modelContext.registerTool({
    name: "get_page_title",
    description: "Read the title of the current page.",
    inputSchema: {
      type: "object",
      properties: {},
      additionalProperties: false,
    },
    annotations: { readOnlyHint: true },
    execute: async () => ({ title: document.title }),
  });
}

That typeof check is the whole compatibility story. In a browser without WebMCP, document.modelContext is undefined, the block never runs, and your site behaves exactly as it did before. It’s a progressive enhancement in the truest sense — there is no fallback to write, because there’s nothing to fall back from.

A naming collision worth knowing about

This one cost me twenty minutes, so let me save you the same detour.

There are two different things called WebMCP.

The first is a third-party JavaScript library at webmcp.dev. It predates the standard and works by running a localhost WebSocket server that bridges your page to an MCP client like Claude Desktop. To use a site built with it, every visitor has to npx a bridge, edit a config file, generate a token, and paste that token into a widget on your page. Its own README now says it “was an early WebMCP proposal” and is “not compliant with the W3C spec.”

The second — the one OpenAI just shipped, and the one I’m writing about — is a standards-track proposal from the W3C Web Machine Learning Community Group, authored by engineers at Microsoft and Google. It’s the document.modelContext API above. No install, no bridge, no token. The browser mediates everything.

If you go searching for WebMCP docs, check which one you’ve landed on before you start building. You want the W3C proposal.

What I built

Six tools, in about 400 lines of one Astro component. Here’s the reasoning behind each.

search_posts is the one that earns its place. I have a few dozen posts on this blog. For an agent to answer “what has Jesse written about fine-tuning?” today, it has to crawl a paginated index and read every page. Instead, the tool lazily fetches a prerendered JSON index and does the matching in the page. One call, structured results.

While building it I found something mildly embarrassing: at the time of writing, only about a sixth of my posts have tags. I’d originally given the tool a tag filter, and filtering by rails returned two posts — while a plain text search over titles and summaries returned thirty-two. A tag filter would have confidently under-reported my own writing to every agent that asked. I removed it. The lesson generalizes: when you expose a tool, you’re making a promise about your data, and it’s worth actually checking whether your data can keep it.

get_speaking_schedule returns my upcoming talks with dates, times, registration links, and panelist lineups. It filters on each event’s end time rather than its start, so a talk doesn’t vanish from the list halfway through it.

get_contact_card returns my email, location, vCard, consulting company, podcast, and socials.

list_portfolio returns my shipped apps, filterable by platform. Building it pushed me to pull the project list out of the portfolio page and into a shared data module, so the page and the tool can’t drift apart.

set_display and reset_display are the fun ones. This site has a faint blueprint grid in the background and a specific type scale. These tools let an agent change both: text size, grid density, grid weight, and whether entrance animations run. Ask ChatGPT to make the page easier to read and it can actually do it — bump the type, drop the grid, cut the motion.

That one required a small refactor first. The grid was hardcoded:

background-image:
  linear-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px),
  linear-gradient(rgba(255, 255, 255, 0.08) 1px, transparent 1px);
background-size: 20px 20px, 100px 100px;

Every value is now a CSS custom property on :root, and the tool just writes to those. The tool never touches a selector — it sets variables and the cascade does the rest. Honestly a better way to have written it in the first place.

The changes also persist to localStorage. This site is multi-page, so without that, clicking through to another post would silently undo whatever the agent just did for you.

What I deliberately did not build

My first instinct was a tool that fills out and submits my contact form. I killed it, and I’d encourage you to think hard before shipping the equivalent.

That form sends real email through Postmark. An agent-callable “send Jesse a message” tool is, in plain terms, a spam vector pointed at my own inbox, and I’d be the one who built it. WebMCP requires user confirmation for consequential actions, so the platform would prompt — but “the platform will catch it” is a weak reason to hand out a loaded tool. If I revisit this, it’ll be a tool that fills the form and leaves the Send button to a human.

Prompts to try

Open this site in the ChatGPT desktop app’s built-in browser (⌘⇧B on macOS) and try these.

Searching the blog

  • “What has Jesse written about fine-tuning?”
  • “Does he have anything on Hotwire Native?”
  • “Find posts about Rails and AI”
  • “What are his five most recent posts?”
  • “Has he written anything about context engineering?”
  • “Summarize what this site says about running multiple coding agents”

Speaking schedule

  • “When is Jesse speaking next?”
  • “What talks does he have coming up?”
  • “Who’s on the panel for the Architecting with AI roundtable?”
  • “Is he doing anything for Boston AI Week?”
  • “Give me the registration link for his fine-tuning workshop”

Contact and hiring

  • “How do I get in touch with Jesse?”
  • “What’s his consulting company?”
  • “I want to hire him for a Rails project — how do I reach him?”
  • “Where can I find his podcast?”

Portfolio

  • “What apps has Jesse shipped?”
  • “Show me his macOS apps”
  • “Does he have anything on iOS?”
  • “What has he built with AI in it?”

Changing how the page looks

This is the category worth actually watching happen, because the page changes under you as the agent works.

  • “Make this page easier to read”
  • “Make the text bigger”
  • “Hide the grid lines in the background”
  • “Make the grid lines bolder”
  • “Make the grid tighter and the text larger”
  • “Turn off the animations”
  • “Set the grid spacing to 40 pixels”
  • “Put the page back to normal”

Combining tools

The interesting behavior shows up when a request needs more than one call.

  • “Make the text bigger, then find me everything Jesse has written about agents”
  • “What’s he speaking about next, and how do I contact him about it?”
  • “Show me his AI projects and his posts about AI”

Requirements and rough edges

A few things will make this silently not work, and they’re worth checking before you conclude my code is broken:

  • You need the ChatGPT desktop app’s built-in browser, ChatGPT Work, or Codex. A normal Chrome tab won’t do it — no shipping browser implements document.modelContext natively yet.
  • It requires GPT-5.6 Sol or Terra. WebMCP is disabled on Luna.
  • It’s unavailable in Enterprise and Edu workspaces.

And the honest caveat: this is a draft specification with no production browser implementations outside of agent-embedded browsers. The API could change. I’m fine with that — the entire integration is one feature-detected component, and if the spec moves, I move one file.

Why bother now

The reach today is small. Almost everyone reading this is in a browser where these tools simply don’t exist, and the code costs them one typeof check.

But the thing that convinced me to build it this afternoon rather than next quarter is that the cost is genuinely near zero and the shape of the bet is asymmetric. If page-declared tools become how agents use the web, the sites that already describe themselves are the ones that work. If it doesn’t happen, I refactored some hardcoded CSS into custom properties and pulled a data file out of a page component, both of which I should have done anyway.

The whole thing is live on this site right now. If you’re reading this in ChatGPT, go ahead and ask it something.


I’m speaking about multi-agent development at Boston AI Week in September — including The Multi-Agent Symphony on running parallel coding agents. Details on all three talks here.

Jesse Waites
Jesse Waites, Technologist & Software Architect, Hiker, Rock & Ice Climber