slop audit
Blog post 39 / Agent readiness

WebMCP testing: a pre-launch checklist for developers

Use this WebMCP testing checklist to inspect document.modelContext, tool discovery, schemas, security annotations, lifecycle, and production registration.

By Slop Audit · 10 min read

WebMCP testing checks whether a supported browser can discover the tools your page intends to expose, whether those tools describe their behavior accurately, and whether registration changes with the page state. Start with discovery, inspect the catalog without executing anything, then test real tool behavior in a controlled environment.

WebMCP is still a Draft Community Group Report, not a W3C Standard or a specification on the W3C Standards Track. Treat browser support and API details as moving dependencies. Keep the feature optional, preserve the normal human workflow, and verify the deployed page instead of assuming a local prototype reached production.

WebMCP testing checklist

Use this sequence before releasing a page with WebMCP tools:

  1. Confirm that the underlying task already works through the normal website interface.
  2. Test with a browser build or origin-trial setup that supports the current WebMCP API.
  3. Feature-detect document.modelContext; do not infer support from a script tag or package alone.
  4. Call getTools() and record the tool names exposed in the current document state.
  5. Inspect every name, description, input schema, origin, and security annotation.
  6. Verify that route-specific tools register and unregister at the correct time.
  7. Check same-origin and cross-origin exposure deliberately.
  8. Test execution, cancellation, errors, and confirmation in an isolated account with safe data.
  9. Repeat discovery on the deployed HTTPS URL.
  10. Record unsupported or unavailable browser evidence separately from an empty tool catalog.

This order keeps a read-only discovery check separate from actions that can change data, submit a form, send a message, or complete a purchase.

Understand what WebMCP testing covers

The WebMCP API lets a web application expose client-side functions as tools with names, descriptions, and structured input schemas. The current draft places the API on document.modelContext. A page can register imperative JavaScript tools, while the broader proposal also describes declarative tools based on forms.

WebMCP is not the same as a remote MCP server. A remote MCP integration exposes server-side capabilities through a protocol endpoint. WebMCP exposes tools from the active browser document and can reuse the page's current interface state. It is also different from `llms.txt`, which is a published text file for discovery and orientation, not an executable browser tool catalog.

The distinction matters when you test. A valid llms.txt response does not prove that document.modelContext exists. A working backend MCP endpoint does not prove that a page registered its in-browser tools. Test each surface independently and label the result precisely.

Register one narrow WebMCP tool

Start with a task that is useful, bounded, and easy to verify. This example exposes a read-only documentation search while preserving the website's existing search function:

const registration = new AbortController();

await document.modelContext.registerTool(
  {
    name: "search_docs",
    title: "Search documentation",
    description: "Search public product documentation by a short text query.",
    inputSchema: {
      type: "object",
      properties: {
        query: {
          type: "string",
          minLength: 2,
          maxLength: 120,
          description: "Words that should appear in the matching documentation.",
        },
      },
      required: ["query"],
      additionalProperties: false,
    },
    annotations: {
      readOnlyHint: true,
      consequentialHint: false,
      untrustedContentHint: true,
    },
    execute: async ({ query }, { signal }) => {
      return searchPublicDocs(query, { signal });
    },
  },
  { signal: registration.signal },
);

// Remove the tool when its route or component is no longer active.
// registration.abort();

Chrome's WebMCP imperative API guide documents registerTool(), JSON Schema inputs, annotations, discovery, cancellation, and tool removal through AbortSignal. Use that documentation together with the live draft because the API is evolving.

Do not register a broad tool such as manage_account when the implementation actually performs several unrelated actions. Narrow tools are easier to describe, authorize, test, and remove when the page state changes.

Test WebMCP tool discovery without execution

Begin with the catalog. In a supported browser, run this in DevTools on the deployed page:

const context = document.modelContext;

if (!context || typeof context.getTools !== "function") {
  console.table([{ status: "runtime unavailable" }]);
} else {
  const tools = await context.getTools();
  console.table(
    tools.map((tool) => ({
      name: tool.name,
      title: tool.title,
      description: tool.description,
      origin: tool.origin,
      inputSchema: JSON.stringify(tool.inputSchema),
      annotations: JSON.stringify(tool.annotations),
    })),
  );
}

The official API describes `getTools()` as an asynchronous discovery method. By default, it returns tools available from same-origin documents in the frame tree. Merely reading this list should not invoke the tool callbacks.

Classify the result accurately:

  • Runtime unavailable: the browser environment did not expose the API. This is missing test coverage, not proof that the page has no tools.
  • Runtime available, zero tools: discovery worked, but no tools were registered for that document state.
  • Tools discovered: record the names and inspect the definitions before trying execution.
  • Discovery error: keep the exception, page URL, browser version, and active route as evidence.

Test the initial load and the state where the tools should exist. A single-page application may register tools only after hydration, navigation, authentication, or data loading.

Review names, descriptions, schemas, and annotations

A discovered tool can still be unsafe or confusing. Review its contract as if it were a public API.

Keep names stable and machine-readable

The current draft limits tool names to ASCII letters, digits, underscores, hyphens, and periods, with a length from 1 to 128 characters. Prefer stable verbs and nouns such as search_docs or preview_invoice. Avoid build hashes, translated display text, and names whose behavior changes between routes.

Describe the actual side effects

The description should state what the tool does, which object it affects, and whether it only previews or completes the action. Do not describe a purchase tool as “finalize cart” when it charges the user. The draft's security and privacy considerations call out ambiguous behavior, tool-description attacks, untrusted output, and unnecessary collection as risks.

Keep the input schema minimal

Require only the values the task needs. Add useful field descriptions, bounds, enums, and additionalProperties: false where appropriate. A narrow schema makes invalid inputs easier to reject and reduces the chance that an agent supplies private context the task never needed.

Set annotations from behavior, not intent

Use readOnlyHint only when the tool does not change state. Mark consequential actions such as purchases, transfers, destructive changes, or outbound messages with consequentialHint. Mark returned user-generated or external data with untrustedContentHint. An annotation is a signal to clients; it does not replace authorization, validation, confirmation, or server-side enforcement.

Test tool lifecycle and page state

Tool availability should follow the interface. A tool for editing a draft should not remain registered after the user leaves the editor, signs out, or switches to a read-only record.

For each route or component state:

  1. Call getTools() and save the initial names.
  2. Navigate to the state that owns the tool.
  3. Confirm that the expected tool appears once.
  4. Leave the state or unmount the component.
  5. Confirm that the tool disappears.
  6. Navigate back and check that a duplicate was not registered.

Use the AbortSignal passed during registration to remove tools cleanly. Also test fast route changes and a tool execution that is cancelled while work is pending. A stale registration can present an action that the visible page no longer supports.

Check origins and Permissions Policy

The current WebMCP design uses the tools Permissions Policy. Chrome's WebMCP overview says the default is self: top-level and same-origin contexts can use the feature, while cross-origin frames need explicit permission such as allow="tools".

Cross-origin discovery needs more than a permissive iframe attribute. The caller must request the origin, and the tool must be exposed to that origin. Test a denied origin as well as an allowed one. Do not use a wildcard as a shortcut when only one trusted integration needs access.

Record the origin returned with every discovered tool. When a page includes several frames, origin evidence helps a reviewer distinguish tools owned by the main application from tools contributed by an embedded service.

Test execution in a controlled environment

Catalog inspection cannot prove that a tool works. Execution testing should use a dedicated account, reversible fixtures, and a clear expected result.

Cover these cases for each tool:

  • the smallest valid input and a representative normal input;
  • missing, malformed, oversized, and unexpected fields;
  • authorization failure and expired session behavior;
  • cancellation through AbortSignal;
  • network failure, timeout, and duplicate request handling;
  • untrusted text returned from users or external sources;
  • repeated invocation and idempotency where the action can retry; and
  • user confirmation before a consequential action completes.

Do not execute a production purchase, message, deletion, or account change merely to prove discovery. Discovery and execution are separate gates. The normal application backend must still enforce identity, authorization, validation, rate limits, and audit logging.

Add WebMCP testing to CI

Run a catalog test in a browser environment that explicitly supports the API. If the CI browser lacks WebMCP, fail or mark the job unavailable according to your release policy; do not turn missing coverage into a pass.

import { expect, test } from "@playwright/test";

test("public docs route exposes the expected WebMCP catalog", async ({ page }) => {
  await page.goto("https://example.com/docs");

  const result = await page.evaluate(async () => {
    const context = document.modelContext;
    if (!context || typeof context.getTools !== "function") {
      return { status: "unsupported", names: [] };
    }

    const tools = await context.getTools();
    return {
      status: "available",
      names: tools.map((tool) => tool.name),
    };
  });

  expect(result.status).toBe("available");
  expect(result.names).toContain("search_docs");
});

Keep execution tests in a separate suite so their permissions, fixtures, cleanup, and confirmation expectations remain visible. The website audit with GitHub Actions guide shows how to attach public-URL evidence to a pull request, while the website regression testing checklist covers baselines and unavailable measurements.

Run WebMCP testing on the deployed page with Slop Audit

Run a Slop Audit on the deployed URL to add a bounded production discovery check. When Browser Run exposes the WebMCP runtime, Slop Audit feature-detects document.modelContext, calls getTools(), and records the tool count and up to ten readable tool names. It does not execute the tools.

The report distinguishes a detected catalog from a runtime with zero tools and from a browser run that could not verify the API. WebMCP status is informational: it does not change the readiness score or the slop-risk score. Slop Audit also does not validate every schema, test cross-origin exposure, prove that descriptions match behavior, or certify tool security.

Use the result as a release handoff: confirm that the expected tool names reached the public page, investigate missing or stale registrations, then run the controlled execution suite. The guide to how Slop Audit audits a public website explains the separate MCP and WebMCP observations and their evidence boundaries.

WebMCP testing release sign-off

  • The human-facing task works without an agent.
  • The production browser environment exposes the expected current API.
  • getTools() returns the expected catalog for each relevant route and state.
  • Tool names are stable, unique, and machine-readable.
  • Descriptions name real behavior and side effects.
  • Input schemas are minimal, bounded, and reject unexpected data.
  • Security annotations match the implementation.
  • Route changes, sign-out, and component cleanup remove stale tools.
  • Origin exposure and the tools Permissions Policy are intentional.
  • Safe execution tests cover errors, cancellation, retries, and confirmation.
  • CI treats an unsupported runtime as missing evidence, not a passing result.
  • A deployed Slop Audit records the production catalog without invoking tools.

WebMCP testing is strongest when it preserves uncertainty. A visible catalog proves that tools were discoverable in one browser state. It does not prove that every agent will call them correctly or that the implementation is safe. Keep discovery, contract review, execution, and production evidence as separate checks.