Back to Blog
WebMCP Implementation Developer Guide React JavaScript Tutorial

How to Implement WebMCP in Your Web App: A Step-by-Step Guide

11 min read
Developer implementing WebMCP tools in a web application

Prerequisites: Basic JavaScript knowledge. Familiarity with your frontend framework (React, Vue, or vanilla JS). An HTTPS-served web application (WebMCP requires a secure context). No backend changes required.

Update — May 2026: Chrome 149 added WebMCP as a public Origin Trial (Google announcement, May 19, 2026). Registered domains can ship WebMCP to real users on Chrome 149+ without requiring a developer flag. For new projects starting today, the Origin Trial is the preferred testing path. The flag-based Chrome 146 Early Preview path documented in Step 1 below remains supported for non-trial-registered domains and local development.


Step 0: Understand What You Are Setting Up

WebMCP exposes your application’s capabilities to AI agents through a browser-native API — navigator.modelContext. You register named JavaScript functions (tools) with typed schemas. Agents discover these tools when they visit your page and call them with structured parameters.

This guide covers the complete implementation path:

  1. Set up your development environment
  2. Install the polyfill (required for non-Chrome browsers)
  3. Register your first tool using the Declarative API
  4. Register complex tools using the Imperative API
  5. Test with the Model Context Tool Inspector
  6. Add tools to your React or Vue components
  7. Deploy to production with the readiness checklist

Step 1: Set Up Your Development Environment

Option A — Chrome Early Preview (native WebMCP)

To test the native browser API without a polyfill:

  1. Install Chrome 146 Canary (version 146.0.7672.0 or higher)
  2. Navigate to chrome://flags
  3. Search for “WebMCP for testing” (flag name: #enable-webmcp-testing)
  4. Set it to Enabled
  5. Relaunch Chrome when prompted

Verify the flag is active by opening DevTools (F12) and running:

console.log("modelContext" in navigator); // true if flag is active

Install the @mcp-b/global package, which implements navigator.modelContext for all browsers:

npm install @mcp-b/global

Import it at the entry point of your application (before any tool registrations):

// main.js or index.js
import "@mcp-b/global";

The polyfill detects native API support and defers to it when available — so this pattern works on Chrome 146+ and all other browsers with a single codebase.


Step 2: Add Feature Detection

Always guard WebMCP calls with a feature check. This prevents errors on browsers where navigator.modelContext is not available — even with the polyfill, defensive checks improve clarity:

function registerWebMCPTools() {
  if (!("modelContext" in navigator)) {
    console.info("WebMCP not available in this environment.");
    return;
  }
  // Register tools here
}

// Call after the page is fully initialized
document.addEventListener("DOMContentLoaded", registerWebMCPTools);

In React: call your registration function inside a useEffect with an empty dependency array so it runs once after mount.


Step 3: Register Your First Tool — The Declarative API

The Declarative API requires no JavaScript. If your application has HTML forms, you can expose them as agent-callable tools by adding two attributes.

Before (standard form):

<form id="search-form" action="/search" method="get">
  <input name="query" type="text" placeholder="Search..." />
  <select name="status">
    <option value="active">Active</option>
    <option value="archived">Archived</option>
  </select>
  <button type="submit">Search</button>
</form>

After (WebMCP-annotated form):

<form
  id="search-form"
  action="/search"
  method="get"
  toolname="searchCustomers"
  tooldescription="Search the customer database by name or status. Returns matching customer records with account details. Leave query empty to list all customers."
>
  <input name="query" type="text" placeholder="Search..." />
  <select name="status">
    <option value="active">Active</option>
    <option value="archived">Archived</option>
  </select>
  <button type="submit">Search</button>
</form>

The browser reads these attributes and automatically generates a tool schema from the form’s input fields. No JavaScript needed.

When to use the Declarative API:

  • Simple search forms
  • Filter panels
  • Contact or request forms
  • Any form where the existing HTML structure already communicates the tool’s intent

Add toolautosubmit for read-only queries to skip the user click after the agent fills the form:

<form toolname="searchProducts" tooldescription="..." toolautosubmit>

Only use toolautosubmit on forms that do not modify state (searches, filters). For forms that submit orders, create records, or trigger actions, leave it off — the user should confirm before submission.


Step 4: Register Complex Tools — The Imperative API

The Imperative API gives you full control over tool schema, execution logic, and return values. Use it for tools that require JavaScript logic, access to application state, or multi-step operations.

Basic structure:

if ("modelContext" in navigator) {
  navigator.modelContext.registerTool({
    name: "createProject",
    description: "Create a new project in the current workspace. Returns the project ID and URL.",
    inputSchema: {
      type: "object",
      properties: {
        name: {
          type: "string",
          description: "Project name. Must be unique within the workspace.",
          maxLength: 80
        },
        template: {
          type: "string",
          enum: ["blank", "marketing", "engineering", "product"],
          default: "blank",
          description: "Starting template for the project."
        }
      },
      required: ["name"],
      additionalProperties: false
    },
    readOnlyHint: false, // This tool creates a record
    execute: async ({ name, template = "blank" }) => {
      try {
        const project = await api.projects.create({ name, template });
        return {
          content: [{
            type: "text",
            text: `Project "${project.name}" created. ID: ${project.id}. Open at: ${project.url}`
          }]
        };
      } catch (err) {
        return {
          content: [{
            type: "text",
            text: `Could not create project: ${err.message}`
          }]
        };
      }
    }
  });
}

For the complete guide to designing tool names, descriptions, and schemas, see: WebMCP Tool Design Best Practices.


Step 5: Add WebMCP Tools to a React Application

In React, register tools inside a useEffect that runs after the component mounts. Unregister tools in the cleanup function if they are context-dependent (e.g., tied to a specific record that may change):

import { useEffect } from "react";
import "@mcp-b/global"; // Polyfill — import once at app root

function ProjectPage({ projectId, projectName }) {
  useEffect(() => {
    if (!("modelContext" in navigator)) return;

    // Register tools relevant to this specific project
    navigator.modelContext.registerTool({
      name: "getProjectMembers",
      description: `List all members of project "${projectName}" (ID: ${projectId}). ` +
        "Returns name, email, role, and join date for each member.",
      inputSchema: {
        type: "object",
        properties: {
          role: {
            type: "string",
            enum: ["admin", "editor", "viewer", "all"],
            default: "all",
            description: "Filter members by role. Defaults to all roles."
          }
        },
        additionalProperties: false
      },
      readOnlyHint: true,
      execute: async ({ role = "all" }) => {
        try {
          const members = await api.projects.getMembers(projectId, { role });
          const list = members.map(m =>
            `${m.name} (${m.email}) — ${m.role}`
          ).join("\n");
          return { content: [{ type: "text", text: list || "No members found." }] };
        } catch (err) {
          return { content: [{ type: "text", text: `Error: ${err.message}` }] };
        }
      }
    });

    // Cleanup: unregister when component unmounts or projectId changes
    return () => {
      if ("modelContext" in navigator && navigator.modelContext.unregisterTool) {
        navigator.modelContext.unregisterTool("getProjectMembers");
      }
    };
  }, [projectId, projectName]);

  return <div>{/* Project page content */}</div>;
}

Key React patterns:

  • Import the polyfill once at your app root (App.js or main.js), not in individual components
  • Pass component props into the tool description to give agents context about the current record
  • Include projectId in the useEffect dependency array so tools re-register if the user navigates to a different project
  • Always return a cleanup function to unregister tools on unmount

Step 6: Test With the Model Context Tool Inspector

Google’s official testing tool is the Model Context Tool Inspector, available as a Chrome extension. It lets you inspect registered tools, view their schemas, manually execute them, and test them against the Gemini API.

Installation:

  1. Open Chrome Web Store
  2. Search for “Model Context Tool Inspector”
  3. Install the extension

Using the Inspector:

  1. Navigate to your application in Chrome 146+ (with the WebMCP flag enabled)
  2. Open the Inspector panel (click the extension icon or open DevTools → Model Context Tools)
  3. You should see all tools registered by the current page
  4. Click any tool to expand its schema
  5. Fill in parameter values and click Execute to test the tool manually

If your tools do not appear in the Inspector:

  • Confirm the WebMCP flag is enabled at chrome://flags
  • Confirm navigator.modelContext exists in console
  • Confirm your registration code runs after DOM ready (not before)
  • Check for JavaScript errors in the console that may have interrupted registration

Step 7: Register Tools at the Right Time

Tools must be registered before an agent queries the page. The safest timing:

FrameworkWhen to register
Vanilla JSDOMContentLoaded event
ReactuseEffect(() => { ... }, []) (after mount)
VueonMounted() hook
Next.jsuseEffect in page component (client-side only)
SvelteKitonMount() lifecycle function

Important for Next.js / SSR frameworks: WebMCP is a browser-only API. Guard registrations with typeof window !== "undefined" checks, or use dynamic imports with { ssr: false }:

// Next.js — safe pattern
useEffect(() => {
  if (typeof window === "undefined") return;
  if (!("modelContext" in navigator)) return;
  // Register tools
}, []);

Production Deployment Checklist

Before shipping WebMCP tools to production:

Security

  • All tools with state mutation have readOnlyHint: false (or omitted)
  • Irreversible operations use requestUserInteraction() for step confirmation
  • All execute() handlers are wrapped in try/catch
  • No sensitive data (credentials, tokens, PII) is returned in tool results
  • additionalProperties: false is set on all input schemas

Reliability

  • Feature detection guard ("modelContext" in navigator) wraps all registrations
  • Tools are registered after page initialization, not before
  • React/Vue components unregister tools on unmount
  • Error messages are human-readable and suggest next steps
  • All required parameters are explicitly listed in required

Quality

  • Every tool has a description of 2–4 sentences
  • All enum values are explicitly listed
  • All parameters have individual description fields
  • Tool names are camelCase verb + noun (no UI element names)
  • Tools are page-specific, not a global catalog

Testing

  • All tools visible in Model Context Tool Inspector
  • All tools execute successfully with valid parameters
  • All tools return readable error messages with invalid parameters
  • Tools tested with the @mcp-b/global polyfill on non-Chrome browser

Observability

  • Server-side logging captures agentInvoked flag from SubmitEvent
  • Tool call rate, success rate, and error types are tracked
  • Logs distinguish agent-driven from human-driven submissions

Frequently Asked Questions

Do I need to change my backend to support WebMCP?

No. WebMCP tools run in client-side JavaScript and call your existing frontend API clients. Your backend receives standard requests — it cannot tell the difference between a human clicking a button and an agent calling a WebMCP tool, unless you check the agentInvoked flag on SubmitEvent. No new API endpoints, no schema changes, no backend deployment.

How do I test WebMCP without Chrome Canary?

Use the @mcp-b/global polyfill, which implements navigator.modelContext for all browsers. Your tools will register and execute normally. The polyfill does not require any browser flags. You can also use the MCP-B Chrome extension for testing tool calls and responses without Chrome Canary’s native implementation.

Can I use WebMCP with TypeScript?

Yes. The @mcp-b/react-webmcp package includes TypeScript type definitions. You can also import types directly from @mcp-b/global. Input schemas defined with Zod can be converted to JSON Schema format using zod-to-json-schema for type-safe tool definitions.

What happens if a user is on a browser that does not support WebMCP?

Without the polyfill, navigator.modelContext will be undefined and your feature detection guard will skip registration silently. Your application functions normally for human users. With the polyfill installed, navigator.modelContext is available in all modern browsers via the polyfill’s transport layer, which communicates with agents through the MCP-B extension or other compatible clients.

How do I handle tools that depend on user authentication?

WebMCP tools execute within the user’s existing authenticated browser session. Your execute handlers have access to the same cookies, session tokens, and local state that your human-facing UI uses. You do not need to handle authentication separately — the agent inherits the user’s session automatically. If the user is not authenticated when the agent calls the tool, your API will return a 401 as normal, and your error handler should return a message directing the user to log in.


Key Takeaways

  • Install @mcp-b/global for cross-browser support; feature-detect before registering
  • Declarative API (HTML attributes) works for any existing form with minimal changes
  • Imperative API (JavaScript) gives full control for complex, stateful operations
  • In React: register in useEffect, unregister in cleanup, re-register when context changes
  • Test with the Model Context Tool Inspector before deploying
  • Use the production checklist to catch security and reliability issues before launch

References and Sources


Further Reading


The same agent-native infrastructure you’ve implemented here is what powers the Kn8 Storefront Agent in ecommerce — live catalog reads, real-time storefront state, and guided discovery built on top of WebMCP. See how it works →

M
Co-founder at Kn8 · Enterprise AI & Data Product Executive

Matheus Reis is a product executive with 10+ years of experience building AI and data products for enterprise customers. Based in Berlin, he has led data product strategy across B2B SaaS organisations — from early-stage through scale. He is now co-founder at Kn8, building the infrastructure that makes web applications natively executable by AI agents.

Ready to make your product agent-ready?

Request access to Kn8 and start instrumenting your application today.