# WebMCP

Source: https://permdock.com/docs/standards/webmcp

How permdock/webmcp registers browser-exposed tools through document.modelContext only when the client snapshot allows them, and how WebMCP hints and Permissions-Policy fit in.

Draft posture: build (pinned to the W3C Web Machine Learning CG draft as shipped in Chrome 150, `document.modelContext`; pin recorded on the [WebMCP adapter](/docs/adapters/webmcp), per [watch list](/docs/standards/watch-list))

## What it is [#what-it-is]

WebMCP is a browser API, incubated in the W3C WebML Community Group and documented by [Chrome](https://developer.chrome.com/docs/ai/webmcp), that lets a web page expose tools to agents running in or alongside the browser, in the same shape as MCP tools:

* `document.modelContext.registerTool(...)` registers a tool with a name, description, input schema and handler. The earlier `navigator.modelContext` entry point is [deprecated in Chrome 150](https://byteiota.com/webmcp-chrome-150-navigator-modelcontext-deprecation/) in favour of `document.modelContext`.
* A `tools` **Permissions-Policy** directive controls whether a document (and which embedded frames) may register tools at all.
* Tool annotations `readOnlyHint` and `untrustedContentHint` tell the agent whether a tool mutates state and whether its output may contain untrusted content that should not be treated as instructions.
* Registration accepts an `AbortSignal`; aborting it unregisters the tool, which is how a page removes tools when its state changes.
* Puppeteer exposes `page.webmcp` so tests can enumerate and call a page's tools deterministically.
* `@mcp-b/webmcp-polyfill` provides the API in browsers that do not ship it yet.

## Why it matters for PermDock [#why-it-matters-for-permdock]

A page that registers a "delete post" tool for every visitor is handing agents a capability the current user may not have. The client already holds a PermDock snapshot for exactly this reason: it knows which permissions the user has, including ownership conditions, without a round trip. `permdock/webmcp` is the same idea as `permission` on `registerTool` in [permdock/mcp](/docs/adapters/mcp), running on the client: register only the tools the snapshot allows, describe them from the permission metadata, and unregister them when the snapshot changes. See the [webmcp adapter](/docs/adapters/webmcp).

Two boundaries stay server-side. The snapshot decides what to *register*; the server still decides what to *execute*, because a page-level tool handler calls the same API route that `protect` guards. And tool arguments coming from an agent are untrusted input, validated at the boundary like MCP tool arguments.

## How PermDock uses it [#how-permdock-uses-it]

```tsx
import { usePermDock } from "permdock/react";
import { registerTools } from "permdock/webmcp";

function PostTools() {
  const permdock = usePermDock();
  useEffect(() => {
    const controller = new AbortController();
    registerTools(document.modelContext, permissions.post, {
      permdock,
      signal: controller.signal,
    });
    return () => controller.abort();
  }, [permdock]);
  return null;
}
```

What `registerTools` does:

* Iterates `listPermissions(permissions.post)` and registers one tool per collection action the snapshot grants (`can(permissions.post.create)`), plus each instance action the snapshot holds a grant for; its condition is checked against the validated input at call time.
* Fills `name`, `description` and `inputSchema` from the permission's action metadata and the resource's Standard JSON Schema.
* Sets `readOnlyHint: true` for actions marked read-only in the action metadata (`read`, `list`) and `false` for mutations.
* Sets `untrustedContentHint` when the resource carries user-generated fields, or when configured explicitly, so the agent does not treat tool output as instructions.
* Passes the caller's `AbortSignal` through and additionally aborts and re-registers when the snapshot changes (`usePermDock().status` moving to `stale` and back to `ready`), so revoked permissions disappear from the tool list without a reload.
* Registers an instance action once, taking the row as validated arguments, never one tool per visible row. The client check is a filter; the server route the handler calls decides on the real row.
* Skips any permission whose status is `pending` or `server-only`, so a tool is registered only when the snapshot can answer for it.
* Returns `approval-required` as a structured tool result carrying the `token`; the browser has no elicitation channel, so the page renders its own approval UI.
* Does nothing when `document.modelContext` is undefined and no polyfill is present; `permdock doctor` reports the missing Permissions-Policy header if the page is served without `tools`.

## Mapping table [#mapping-table]

| WebMCP concept | PermDock concept |
| --- | --- |
| `document.modelContext.registerTool` | `registerTools(document.modelContext, permissions.<resource>, options)` |
| `navigator.modelContext` (deprecated) | Not used; the polyfill's `document.modelContext` is targeted |
| Tool name and description | Permission key and action metadata (`title`, `description`) |
| Tool `inputSchema` | Resource's Standard JSON Schema |
| `readOnlyHint` | Action metadata `readOnly`, defaulted for `read` / `list` |
| `untrustedContentHint` | Adapter option or resource metadata |
| `AbortSignal` unregistration | Caller's signal plus automatic abort on snapshot change |
| Permissions-Policy `tools` | Deployment requirement checked by `permdock doctor` |
| Which tools exist for this user | `can(...)` against the client snapshot |
| Whether a tool call succeeds | Server-side `protect` on the route the handler calls |
| Puppeteer `page.webmcp` | Used in the `webmcp` example's tests to assert the registered tool list per role |
| `@mcp-b/webmcp-polyfill` | Supported; the adapter only requires the `document.modelContext` shape |

## Sources [#sources]

* [Chrome WebMCP documentation](https://developer.chrome.com/docs/ai/webmcp).
* [WebMCP in Chrome 150: `navigator.modelContext` deprecation](https://byteiota.com/webmcp-chrome-150-navigator-modelcontext-deprecation/).
* Product plan, "Standards and agent runtimes (Sept 2026)" and the `permdock/webmcp` API sketch.
