Skip to content
All field notes

WebMCP / Implementation / Developer Guide / React / JavaScript / Tutorial

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

Implement the current experimental WebMCP imperative API with feature detection, scoped tools, explicit permissions, React cleanup, testing, and post-action verification.

Matheus Reis

/ 6 min read

Updated

Developer implementing a browser-side structured tool in a web application

WebMCP is experimental. A correct implementation begins with the current specification, feature detection, and a narrow action contract. Do not start from older examples that use navigator.modelContext or treat form annotations as a stable declarative API.

This guide follows the July 8, 2026 Community Group draft, which exposes the imperative API through document.modelContext. Recheck the specification and browser documentation before using the code in production.

Step 0: Define one bounded workflow

Write down five things before registering a tool:

  1. the current application state the action needs;
  2. the user permission that authorizes it;
  3. the input the agent must supply;
  4. the state change or result the action should produce;
  5. the observation that will verify success.

If those boundaries are unclear, the tool contract is not ready.

Step 1: Check the environment

WebMCP requires a supporting browser implementation and a secure context. Chrome announced an origin trial for Chrome 149 on June 9, 2026, but trial and browser status can change.

Feature-detect the current API and keep the human-facing application functional when it is absent:

function supportsWebMCP() {
  return "modelContext" in document;
}

if (!supportsWebMCP()) {
  console.info("WebMCP is not available in this environment.");
}

Avoid a polyfill claim unless you have verified which agent or browser consumes the polyfilled transport. Making document.modelContext exist in JavaScript does not by itself make every browser agent discover the tools.

Step 2: Register a read-only tool

Start with a low-risk query whose authorization already exists in the application:

const controller = new AbortController();

if (supportsWebMCP()) {
  document.modelContext.registerTool(
    {
      name: "search_records",
      title: "Search records",
      description:
        "Search records available to the signed-in user. " +
        "Returns identifiers and display names for matching records.",
      inputSchema: {
        type: "object",
        properties: {
          query: {
            type: "string",
            minLength: 1,
            maxLength: 120,
            description: "Text to match against record names."
          }
        },
        required: ["query"],
        additionalProperties: false
      },
      annotations: {
        readOnlyHint: true
      },
      async execute({ query }) {
        return api.records.search({ query });
      }
    },
    { signal: controller.signal }
  );
}

The AbortSignal scopes the registration. Aborting it removes the tool when the page context changes or the component unmounts.

Step 3: Add a state-changing tool carefully

A mutation needs a stricter contract than a query:

  • reuse the application’s authorization checks;
  • reject unsupported state transitions;
  • require clear user intent for risky or ambiguous actions;
  • return enough information to inspect the resulting state;
  • fail closed when the result cannot be confirmed.
document.modelContext.registerTool({
  name: "set_record_status",
  title: "Set record status",
  description:
    "Change the status of the record currently open in the interface. " +
    "Only transitions allowed for the signed-in user are accepted.",
  inputSchema: {
    type: "object",
    properties: {
      status: {
        type: "string",
        enum: ["active", "paused", "archived"]
      }
    },
    required: ["status"],
    additionalProperties: false
  },
  annotations: {
    readOnlyHint: false
  },
  async execute({ status }) {
    const before = app.currentRecord();
    await api.records.setStatus(before.id, status);
    const after = await api.records.get(before.id);

    if (after.status !== status) {
      throw new Error("The requested state was not observed after the update.");
    }

    return {
      recordId: after.id,
      previousStatus: before.status,
      currentStatus: after.status,
      verified: true
    };
  }
});

The important step is not the success message. It is the post-action observation.

Step 4: Scope tools to the current page

Register only actions that make sense in the connected document. A tool for the record currently open should not stay registered after navigation to a different record.

In React, use an AbortController for lifecycle cleanup:

import { useEffect } from "react";

function RecordPage({ recordId }) {
  useEffect(() => {
    if (!("modelContext" in document)) return;

    const controller = new AbortController();

    document.modelContext.registerTool(
      {
        name: "get_current_record",
        title: "Get current record",
        description: "Return the record currently open in this page.",
        inputSchema: {
          type: "object",
          properties: {},
          additionalProperties: false
        },
        annotations: {
          readOnlyHint: true
        },
        async execute() {
          return api.records.get(recordId);
        }
      },
      { signal: controller.signal }
    );

    return () => controller.abort();
  }, [recordId]);

  return <RecordView recordId={recordId} />;
}

Step 5: Test the contract, not only the handler

Test at four levels:

  1. Schema: valid inputs pass and malformed or extra inputs fail.
  2. Authorization: the tool cannot exceed the signed-in user’s permissions.
  3. Selection: representative prompts lead an agent to the right tool and irrelevant prompts do not.
  4. Verification: the observed post-action state matches the requested postcondition.

Because model behavior is probabilistic, repeat the selection tests across a fixed prompt set. Record the model, date, prompt, selected tool, supplied arguments, and outcome.

A bounded ecommerce example

The following is hypothetical. It does not claim a kn8 commerce integration.

Suppose a connected collection page exposes:

  • the currently visible product count;
  • a visible price-filter target;
  • a host-supported action named apply_price_filter;
  • a refreshed page observation after the filter runs.

An agent can guide the shopper to the visible control, invoke the supported action with the requested range, and verify that the resulting interface reports the expected filter and a new result count. It should not infer inventory, pricing, or checkout capabilities the host did not expose.

Production review checklist

Capability boundary

  • Every tool is necessary for the current page or workflow
  • The description names the relevant context and limits
  • Inputs reject unknown fields and invalid values
  • Tools disappear when their page context is gone

Security

  • Existing server-side authorization still applies
  • Consequential actions require appropriate user intent or confirmation
  • Untrusted output is labeled and handled
  • Tool output does not expose secrets or unnecessary personal data

Reliability

  • Errors are explicit and do not imply success
  • Mutations define an observable postcondition
  • The result is re-read or otherwise verified
  • The application still works when WebMCP is unavailable

Freshness

  • The implementation matches the current specification
  • Browser and trial requirements were checked on the publication date
  • No deprecated navigator.modelContext or unverified declarative API remains

kn8 product boundary

kn8 is not built on WebMCP. Its current code-proven behavior uses state and visible targets supplied by a connected host, guides through the interface already on screen, invokes supported actions, and verifies the result. The ecommerce example above is a design pattern, not proof of a catalog, inventory, cart, or checkout integration.

Primary sources

Further reading

Written by

Matheus Reis Co-founder at kn8 · Ecommerce AI

Matheus Reis is a product executive and co-founder at kn8, building the Storefront Agent for ecommerce brands. He writes about AI in retail, agentic commerce, and the future of the buying experience.

Private beta / hands-on demo

See kn8 on your storefront.

Bring us one customer request. We’ll show how kn8 answers in chat and completes the task in your storefront.

  1. 01Your storefront
  2. 02A customer request
  3. 03Live walkthrough