DEV Community

Cover image for Your Website Has a New User — And It's Not Human. A Hands-On Guide to WebMCP
Nilam Bora
Nilam Bora

Posted on

Your Website Has a New User — And It's Not Human. A Hands-On Guide to WebMCP

Google I/O Writing Challenge Submission

This is a submission for the Google I/O Writing Challenge

The Web Just Got a New Kind of Visitor

Here's a question nobody was asking five years ago: what happens when the primary user of your website isn't a person?

At Google I/O 2026, sitting in my home office watching the Chrome session, I got my answer — and it wasn't what I expected. While everyone was buzzing about Gemini 3.5 and Antigravity 2.0, the Chrome team quietly dropped what I think is the most consequential announcement for web developers: WebMCP.

WebMCP (Web Model Context Protocol) is a proposed W3C standard that lets websites expose JavaScript functions as structured tools that AI agents can call directly — no scraping, no DOM simulation, no brittle Puppeteer scripts. Just a clean, typed API living right inside the browser tab.

I've spent the last few days building with it. This isn't a summary of the keynote. This is what I learned when I actually opened chrome://flags and started writing code.


What WebMCP Actually Is (In 60 Seconds)

If you've worked with MCP (Model Context Protocol), you know the concept: give AI agents structured tools to call instead of making them guess what to click on a screen.

WebMCP takes that idea and moves it into the browser. Instead of running a separate Node.js or Python server, you register tools directly on your webpage using a new browser API: navigator.modelContext.

Here's the fundamental mental model shift:

Traditional MCP WebMCP
Runs on Your backend server The browser tab
Auth API keys, OAuth tokens User's existing session & cookies
Sees Your database, internal APIs The page's JavaScript context
Best for Headless automation Human-in-the-loop workflows
Infrastructure New server required Zero additional infrastructure

They're complementary, not competing. MCP is for your backend. WebMCP is for your frontend. But if you're a web developer who's been wondering "how do I make my site work with AI agents?" — WebMCP is the answer Google just handed you.


Setting Up: 5 Minutes to Your First WebMCP Tool

Prerequisites

  • Chrome 149+ (currently in Canary/Dev channel, or behind a flag in stable)
  • A basic web page
  • That's it. No npm packages. No build tools. No server.

Step 1: Enable the Flag

For local development, navigate to:

chrome://flags/#enable-webmcp-testing
Enter fullscreen mode Exit fullscreen mode

Set it to Enabled, then relaunch Chrome.

For production deployment, you'll want to register for the origin trial at developer.chrome.com/origintrials — search for "WebMCP", register your origin, and include the token as a <meta> tag in your page's <head>.

Step 2: Open the WebMCP DevTools Pane

Chrome DevTools now has a dedicated WebMCP pane under the Application tab. This gives you:

  • Available Tools: Every tool registered on the page, with invocation counters
  • Invoked Tools Log: Chronological record of agent-tool interactions with input/output inspection
  • Manual Test: Execute any tool directly, bypassing agent logic
  • Schema Validation: Catches malformed JSON schemas before agents hit them

Alternatively, you can also install the Model Context Tool Inspector extension from the Chrome Web Store for a more visual workflow.

Step 3: Register Your First Tool

Create an index.html file:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>WebMCP Demo</title>
</head>
<body>
  <h1>🛠️ My Agent-Ready Page</h1>
  <div id="output"></div>

  <script>
    // Check if the API is available
    if ('modelContext' in navigator) {

      navigator.modelContext.registerTool({
        name: 'get_weather',
        description: 'Returns the current weather for a given city. Use this when the user asks about weather conditions.',
        inputSchema: {
          type: 'object',
          properties: {
            city: {
              type: 'string',
              description: 'The city name, e.g. "Tokyo" or "San Francisco"'
            },
            units: {
              type: 'string',
              enum: ['celsius', 'fahrenheit'],
              description: 'Temperature unit preference'
            }
          },
          required: ['city']
        },
        execute: async ({ city, units = 'celsius' }) => {
          // In a real app, this would call your existing API
          const mockData = {
            'tokyo': { temp: 22, condition: 'Partly Cloudy' },
            'san francisco': { temp: 18, condition: 'Foggy' },
            'london': { temp: 15, condition: 'Overcast' }
          };

          const data = mockData[city.toLowerCase()];
          if (!data) return { error: `No weather data for ${city}` };

          const temp = units === 'fahrenheit' 
            ? Math.round(data.temp * 9/5 + 32) 
            : data.temp;

          return {
            city,
            temperature: `${temp}°${units === 'fahrenheit' ? 'F' : 'C'}`,
            condition: data.condition,
            timestamp: new Date().toISOString()
          };
        }
      });

      console.log('✅ WebMCP tool registered: get_weather');

    } else {
      console.warn('WebMCP not available. Enable chrome://flags/#enable-webmcp-testing');
    }
  </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Open this in Chrome 149 with the flag enabled. Open DevTools console. You should see:

✅ WebMCP tool registered: get_weather
Enter fullscreen mode Exit fullscreen mode

Congratulations — your page is now agent-ready. Any AI agent that supports WebMCP (like Gemini in Chrome) can discover and call this tool instead of trying to scrape your page.


Going Deeper: The Declarative Approach

The imperative JavaScript API gives you full control, but WebMCP also offers a declarative approach for common form-based workflows. This is where it gets really elegant.

Instead of writing JavaScript, you add attributes directly to your HTML forms:

<form toolname="search_products" 
      tooldescription="Search the product catalog by name, category, or price range">

  <label for="query">Search</label>
  <input type="text" id="query" name="query" 
         placeholder="What are you looking for?" required>

  <label for="category">Category</label>
  <select id="category" name="category">
    <option value="">All Categories</option>
    <option value="electronics">Electronics</option>
    <option value="books">Books</option>
    <option value="clothing">Clothing</option>
  </select>

  <label for="max_price">Max Price ($)</label>
  <input type="number" id="max_price" name="max_price" min="0" step="0.01">

  <button type="submit">Search</button>
</form>
Enter fullscreen mode Exit fullscreen mode

The browser automatically generates the JSON schema from your form structure — input types, names, required fields, select options, min/max constraints. The agent gets a typed tool contract without you writing a single line of schema code.

This is honestly brilliant. If you have an existing website with forms, you can make it agent-accessible by adding two HTML attributes. That's it.


Building Something Real: A Task Manager with 3 WebMCP Tools

Let me walk through something more practical. I built a simple task manager that exposes three tools to AI agents:

// Tool 1: Add a task
navigator.modelContext.registerTool({
  name: 'add_task',
  description: 'Creates a new task in the task list. Returns the created task with its ID.',
  inputSchema: {
    type: 'object',
    properties: {
      title: { type: 'string', description: 'The task title' },
      priority: { 
        type: 'string', 
        enum: ['low', 'medium', 'high', 'urgent'],
        description: 'Priority level' 
      },
      due_date: { 
        type: 'string', 
        description: 'Due date in YYYY-MM-DD format (optional)' 
      }
    },
    required: ['title']
  },
  execute: async ({ title, priority = 'medium', due_date }) => {
    const task = {
      id: crypto.randomUUID(),
      title,
      priority,
      due_date: due_date || null,
      completed: false,
      created_at: new Date().toISOString()
    };

    tasks.push(task);
    renderTasks(); // Update the UI
    return { success: true, task };
  }
});

// Tool 2: List tasks with filters
navigator.modelContext.registerTool({
  name: 'list_tasks',
  description: 'Returns all tasks, optionally filtered by completion status or priority.',
  inputSchema: {
    type: 'object',
    properties: {
      status: { 
        type: 'string', 
        enum: ['all', 'pending', 'completed'],
        description: 'Filter by status' 
      },
      priority: { 
        type: 'string', 
        enum: ['low', 'medium', 'high', 'urgent'],
        description: 'Filter by priority' 
      }
    }
  },
  execute: async ({ status = 'all', priority } = {}) => {
    let filtered = [...tasks];

    if (status === 'pending') filtered = filtered.filter(t => !t.completed);
    if (status === 'completed') filtered = filtered.filter(t => t.completed);
    if (priority) filtered = filtered.filter(t => t.priority === priority);

    return { 
      total: filtered.length, 
      tasks: filtered 
    };
  }
});

// Tool 3: Complete a task
navigator.modelContext.registerTool({
  name: 'complete_task',
  description: 'Marks a specific task as completed by its ID.',
  inputSchema: {
    type: 'object',
    properties: {
      task_id: { type: 'string', description: 'The UUID of the task to complete' }
    },
    required: ['task_id']
  },
  execute: async ({ task_id }) => {
    const task = tasks.find(t => t.id === task_id);
    if (!task) return { error: 'Task not found' };

    task.completed = true;
    renderTasks(); // Update the UI in real-time
    return { success: true, task };
  }
});
Enter fullscreen mode Exit fullscreen mode

What's happening here is subtle but important: when an AI agent calls add_task, the UI updates in real-time because renderTasks() runs inside the user's browser. The user watches their task list populate while the agent works. The agent and the human share the same view. This is fundamentally different from a headless MCP server processing requests in the background.


Testing Your Tools (Without an Agent)

You don't need a full AI agent to test your WebMCP implementation. Use the programmatic API:

// List all registered tools
const tools = await navigator.modelContext.getTools();
console.log('Registered tools:', tools.map(t => t.name));

// Execute a tool manually
const result = await navigator.modelContext.executeTool(
  'add_task', 
  { title: 'Write WebMCP article', priority: 'urgent' }
);
console.log('Result:', result);
Enter fullscreen mode Exit fullscreen mode

Or use the Model Context Tool Inspector extension — it gives you a visual panel to browse tools, fill in parameters, and hit "Execute". During development, this was my most-used tool.


The Security Model: What You Need to Know

WebMCP isn't a free-for-all. The security considerations are thoughtful:

  1. Same-Origin Policy: Tools are strictly isolated by origin. Cross-origin tools cannot be accessed, enumerated, or invoked — even from iframes. Third-party widgets can't interfere with your app's tools.

  2. CSP Integration: WebMCP respects Content Security Policy headers. If your CSP blocks inline scripts, tool registration via inline <script> tags won't work (use external scripts instead).

  3. HTTPS Required: Tools can only be registered on secure origins. No HTTP.

  4. Human-in-the-Loop: This is the big one. The spec provides a requestUserInteraction() API for sensitive or destructive actions (payments, deletions, data exports). When invoked, the browser — not your website — renders a native consent dialog. Your site cannot suppress, restyle, or auto-click it. It's a mandatory choke-point.

   execute: async (params, { requestUserInteraction }) => {
     // For dangerous operations, require explicit user consent
     await requestUserInteraction(async () => {
       return `Delete all ${params.count} items? This cannot be undone.`;
     });
     // Only reaches here if user approved
     return await deleteItems(params.count);
   }
Enter fullscreen mode Exit fullscreen mode
  1. Agent Traceability: When an agent invokes a tool that makes HTTP requests, Chrome automatically adds a Sec-WebMCP-Agent: 1 header. On the server side, form submissions set SubmitEvent.agentInvoked = true. This means you can detect agent traffic, set up agent-specific rate limits, and build audit logs.

  2. Permission-First Design: Nothing is exposed by default. You must explicitly call registerTool() to make anything available. It follows a least-privilege model and supports Permissions Policy headers for fine-grained access control.

This is a sane, web-platform-native security model. It doesn't invent new trust boundaries — it inherits the browser's existing ones and adds agent-specific layers on top.


What I Think Is Underrated About WebMCP

1. Zero-Auth for Agents

This is the killer feature that nobody's talking about enough.

With traditional MCP, you need to solve authentication for the AI agent separately — API keys, OAuth flows, token management. With WebMCP, the agent inherits the user's existing session. If the user is logged into your app, the agent can act on their behalf, within the same session, using the same cookies.

No new auth infrastructure. No token management. No security review for a new API surface. The user's login is the auth.

2. Progressive Enhancement

WebMCP follows the web platform's best tradition: your site works fine without it. The if ('modelContext' in navigator) check means you can ship WebMCP tools today — users on older browsers simply don't see them. No polyfills, no graceful degradation code. It's additive.

3. The Declarative API Lowers the Bar Massively

Not every web developer wants to write JSON Schema by hand. The <form toolname="..." tooldescription="..."> approach means a WordPress theme developer, a Shopify store owner, or a junior dev on their first project can make their site agent-ready. This is how you get ecosystem adoption.


What's Still Missing (Honest Take)

No technology ships perfect. Here's what I noticed during my hands-on time:

Tool Discovery is Unclear

How does an agent find which websites have WebMCP tools? There's no registry, no manifest file, no equivalent of robots.txt for agents. Right now, the agent has to navigate to your page first, and then discover tools. That's fine for "help me on this website" flows, but limits proactive agent discovery.

No Inter-Tool Dependencies

What if complete_task should only be callable after list_tasks returns results? There's no way to express tool ordering or dependencies in the schema. Agents have to figure this out from descriptions alone.

Debugging Could Go Further

The DevTools WebMCP pane is solid for basic inspection, but there's no integration with the Performance timeline yet. You can't profile tool execution latency alongside your app's rendering pipeline. For a spec aiming at production adoption, this will need to come.

The Origin Trial Boundary

This is experimental. Chrome 149 only. The spec lives in the W3C Web Machine Learning Community Group (repo: github.com/webmachinelearning/webmcp) — which is a Community Group Report, not a formal W3C Standard or even on the Standards Track yet. No Firefox, no Safari. If you're building production features on this today, you're building on sand. If you're prototyping and providing feedback to shape the spec — you're in exactly the right place.


Who Should Care About This Today

If you are... What to do
A web developer Prototype one tool. Get familiar with registerTool(). Ship behind a feature flag.
An e-commerce dev The declarative form API is built for you. <form toolname="search_products"> is a one-line upgrade.
A framework author Start thinking about WebMCP primitives. React/Vue/Svelte components that auto-register tools will be huge.
A product manager Know that your website will have two interfaces soon: one for humans, one for agents. Plan accordingly.

The Bigger Picture

WebMCP isn't just a Chrome API. It's a signal about where the web is heading.

For 30 years, the web has been a visual medium — we write HTML for human eyes, style it with CSS for human aesthetics, and add JavaScript for human interactions. WebMCP introduces a parallel interface layer: the same page, the same code, but now also speaking machine.

The websites that embrace this won't just be "AI-friendly" — they'll be the ones that AI agents actually recommend, prefer, and route users toward. Because an agent that can reliably call book_flight({ from: 'SFO', to: 'NRT', date: '2026-07-01' }) will always choose that over scraping a booking page and hoping the DOM hasn't changed.

The question isn't whether the web will become agent-ready. It's whether your website will be ready when the agents arrive.

Start with navigator.modelContext.registerTool(). Start today.


I'm Nilam — a developer who builds things and occasionally writes about them. If you found this useful, the 💜 reaction helps this article reach more developers. Got questions about WebMCP? Drop them in the comments — I'll answer everything I can from my hands-on experience.

Top comments (0)