WebMCP: how your page talks to an AI agent through navigator.modelContext

WebMCP: how your page talks to an AI agent through navigator.modelContext

navigator.modelContext still returns undefined, but behind that word hides a new way for the web to expose tools straight to an agent — no server. Turn on the flag, register a tool, and test it.

Jakub Kontra
Jakub Kontra
Developer

Summary

WebMCP is an experimental Chrome API through which your page exposes tools to an AI agent directly in-page — no server, transport, or SDK. You turn it on with the flag chrome://flags/#enable-webmcp-testing in Chrome 146+. In this article we'll walk through how to enable the flag, how to register your first tool in the console, and how to test it so the agent actually calls it.


Open DevTools on your site, type navigator.modelContext, and you'll most likely get undefined. Behind that one word hides a whole new way for your page to talk to an AI agent. Here's how to turn it on.

What WebMCP is, and why it isn't another server

WebMCP lets a page expose tools directly to an agent through navigator.modelContext. No background process, no transport, no SDK or configuration. The page registers itself in the browser as a tool provider, and the browser itself acts as the bridge between page and agent — it hands the agent the registered tools and brokers their calls. The agent sees them directly on the user's live session — that is, in the context where the user is logged in, where data is loaded, and where they've already gone through the whole flow.

This is where WebMCP fundamentally differs from a classic MCP server. That one runs as a persistent process, holds its own credentials, and lives independently of whether anyone has a browser open. WebMCP tools, by contrast, are ephemeral. They live only as long as the tab is open. Close the tab and the tools vanish with it. For the agent it means it isn't talking to some abstract service somewhere in the cloud, but to the specific page the user has in front of them right now.

The consequence is purely practical. You don't have to handle the tool's authentication separately, because it inherits the user's session. You don't have to deploy a backend, because the tool is a piece of JavaScript on the page. And you don't worry about running a server at all — the tool's lifetime matches the tab's lifetime.

Turn on the flag (and run the right Chrome)

The API is behind a flag, so you have to enable it first. The steps are short:

  1. Go to chrome://flags/#enable-webmcp-testing.
  2. For the "WebMCP for testing" item, switch the value to Enabled.
  3. Click Relaunch to restart the browser.

WebMCP needs Chrome 146 or newer, which in practice means the Canary, Dev, or Beta channel. If you don't see the flag in the list at all, you probably have an old browser version. On the stable channel the flag may not be there yet, so if you're searching for it in vain, grab one of the development channels and try again.

Your first tool in the console

A tool has four key parts. name is its identifier, description tells the agent what the tool is for, inputSchema describes the input arguments as JSON Schema, and execute is the function that runs the tool. The return value from execute has the shape { content: [{ type: "text", text }] } — that is, an array of content blocks through which the agent gets text back.

You can register a tool right in the console. Just wrap it in a guard that checks you're running in a secure context and that the API even exists:

if (window.isSecureContext && navigator.modelContext) {
  navigator.modelContext.registerTool({
    name: "addTodo",
    description: "Adds a new item to the user's to-do list.",
    inputSchema: {
      type: "object",
      properties: {
        text: { type: "string", description: "The task text to add." },
        priority: { type: "string", enum: ["low", "medium", "high"], description: "Task priority." },
      },
      required: ["text"],
    },
    async execute({ text, priority = "medium" }) {
      const li = document.createElement("li");
      li.textContent = `[${priority}] ${text}`;
      document.querySelector("#todos")?.appendChild(li);
      return { content: [{ type: "text", text: `Added task "${text}" with ${priority} priority.` }] };
    },
  });
}

The tool here actually does something — it adds a <li> to a list on the page — and returns a text confirmation to the agent. The inputSchema says that text is a required string and priority is an enum with the values low, medium, and high. When you no longer need the tool, you remove it via navigator.modelContext.unregisterTool("addTodo").

There's also a declarative variant where you define the tool right in the HTML. You add the toolname and tooldescription attributes to a <form> and the browser derives the tool from the form on its own, without writing any JavaScript:

<form toolname="addTodo" tooldescription="Adds a new item to the user's to-do list.">
  <input name="text" required />
  <button type="submit">Add</button>
</form>

For simple cases where you already have a form, this is the shortest path.

Test it with the Model Context Tool Inspector

Registering a tool is nice, but you want to see the agent actually call it. That's what the Model Context Tool Inspector extension from the Chrome Web Store is for. After installing it, it lists all the tools the page has registered, including their schemas — so you immediately see whether the inputSchema looks the way you intended.

You can then run the tools two ways. Either manually, where you enter JSON arguments and call the tool directly, or in natural language via Gemini, where you simply write what you want. You type "add a task buy milk with high priority" and watch the agent pick your addTodo on its own, fill in the arguments, and call execute. At that moment the page is really talking to an agent.

When it doesn't work

The most common problem is that navigator.modelContext is undefined. Run through this list:

  • HTTP instead of HTTPS. The API is SecureContext-only, so it won't be available over plain HTTP. The exception is localhost, which counts as a secure context.
  • Flag turned off. Check that chrome://flags/#enable-webmcp-testing is actually Enabled and that you hit Relaunch after switching it.
  • Old Chrome version. Don't expect the API below 146.
  • Headless or non-tab context. The provider is only a normal top-level tab, not every browser runtime context.

When registration fails, registerTool throws an InvalidStateError. This happens with a duplicate tool name, with an invalid inputSchema, or when you leave name or description empty. Also watch the context: only a top-level tab is a provider. A cross-origin iframe needs the allow="tools" attribute on the <iframe>, otherwise it won't expose its tools.

Where it makes sense today

Ephemeral tools, dependence on an open tab, and access to the user's live session make WebMCP a fit mainly for prototypes and internal tooling. When you want to quickly give an agent the ability to drive an existing web app the user is logged into, you save yourself the entire server layer and the authentication work.

At the same time, be realistic. This is an early preview, the API is changing, and the Origin Trial that would allow real deployment outside the flag isn't planned until Chrome 149. So WebMCP doesn't belong in production yet.

But the main benefit is elsewhere. In a few minutes you have a tool the agent actually calls, and that's without a backend and without deployment. Along the way you get a feel for the mental model of in-page tools — a tool as a piece of the page, not as a remote service — and you learn to design an inputSchema so the agent understands and fills it correctly. That's a skill that sticks with you even once navigator.modelContext stops returning undefined in stable Chrome.