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, per watch list)
What it is
WebMCP is a browser API, incubated in the W3C WebML Community Group and documented by Chrome, 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 earliernavigator.modelContextentry point is deprecated in Chrome 150 in favour ofdocument.modelContext.- A
toolsPermissions-Policy directive controls whether a document (and which embedded frames) may register tools at all. - Tool annotations
readOnlyHintanduntrustedContentHinttell 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.webmcpso tests can enumerate and call a page's tools deterministically. @mcp-b/webmcp-polyfillprovides the API in browsers that do not ship it yet.
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, 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.
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
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,descriptionandinputSchemafrom the permission's action metadata and the resource's Standard JSON Schema. - Sets
readOnlyHint: truefor actions marked read-only in the action metadata (read,list) andfalsefor mutations. - Sets
untrustedContentHintwhen the resource carries user-generated fields, or when configured explicitly, so the agent does not treat tool output as instructions. - Passes the caller's
AbortSignalthrough and additionally aborts and re-registers when the snapshot changes (usePermDock().statusmoving tostaleand back toready), 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
pendingorserver-only, so a tool is registered only when the snapshot can answer for it. - Returns
approval-requiredas a structured tool result carrying thetoken; the browser has no elicitation channel, so the page renders its own approval UI. - Does nothing when
document.modelContextis undefined and no polyfill is present;permdock doctorreports the missing Permissions-Policy header if the page is served withouttools.
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
- Chrome WebMCP documentation.
- WebMCP in Chrome 150:
navigator.modelContextdeprecation. - Product plan, "Standards and agent runtimes (Sept 2026)" and the
permdock/webmcpAPI sketch.
Last updated on
Shared Signals and CAEP
How the OpenID Shared Signals Framework 1.0 and CAEP 1.0 let permdock/ssf invalidate snapshots the moment an identity provider revokes a session instead of waiting for a TTL.
A2A
How permdock/a2a emits Agent Card security schemes and requirements per skill and filters the authenticated extended card by the caller's permissions.