@schwaizer/ocp-webmcp

Declare a Surface's OCP tool catalogue to the browser's own agent, via WebMCP.

npm i @schwaizer/ocp-webmcp
import { registerSurfaceTools } from '@schwaizer/ocp-webmcp';

const registration = registerSurfaceTools(createTaxHostTools());
// registration.registered → ['find_municipality', 'income_tax', …]
// registration.skipped    → [{ name: 'book_appointment', reason: 'write' }]
// registration.supported  → false on a browser with no WebMCP

Why this exists in a repository that concedes the case

OCP answers a question about a page from a device that is not the one showing it. When the page and the microphone are the same machine, a browser-native agent is simpler, and the root README says so without hedging: no install, no pairing, no relay, no protocol, and the site pays for no inference either way.

That concession creates a maintenance trap. A site supporting both paths would otherwise keep two tool catalogues — one shaped for OCP, one for WebMCP — and they drift apart at the first schema change, with the stale one being whichever nobody is looking at. This package removes the second catalogue. One definition, re-declared.

Write tools are never registered

A tool declaring write: true is skipped, and this is the whole design rather than a precaution.

Core §10 requires a human confirmation for a write, expiring to deny, and OCP puts that ceremony on the Surface — the page the visitor is looking at — precisely because the Companion cannot be trusted to have asked. WebMCP has no equivalent. The agent decides when to call, and the page receives the arguments with nothing between the model's intent and the effect. Registering book_appointment here would mean a booking made because a model inferred one was wanted, with no expire-to-deny and no record of who approved it.

There is deliberately no allowWrites option. A flag with that name gets turned on by someone who has not read this section. A site that wants writes over WebMCP needs its own consent step first, and that is a real thing to build rather than a boolean to flip.

What it does not claim

  • WebMCP is not a shipped standard. It is an origin trial and a W3C-track proposal, and its surface has changed shape more than once. This adapter feature-detects, supports both spellings it has been observed to take (provideContext and registerTool), and no-ops where neither exists. supported reports what actually happened rather than being inferred from a browser version.
  • It bridges one direction only. Nothing here lets a WebMCP agent reach an OCP Hub, a Companion or a paired phone. The two paths share a catalogue and nothing else.
  • Registration is not disclosure, but the first call is. Declaring a tool hands the agent a name, a description and a JSON schema. The moment the agent calls one, the arguments it extracted go wherever that tool sends them — on the site's own origin, into the site's own logs. A deployment's privacy copy has to describe that actor, and "the assistant" usually means the paired phone.

API

export
registerSurfaceTools(tools, options?)Declares the catalogue; returns { registered, skipped, supported, unregister }. Never throws.
isWebMcpAvailable(options?)Whether this browser exposes a surface the adapter can use.
SurfaceTool{ name, description, inputSchema, write?, run } — structurally identical to ocp-embed's HostTool, restated so this package needs no dependency to register a tool.

options.modelContext overrides where WebMCP is found (default navigator.modelContext), which is how the tests drive the real code rather than a re-implementation of it.

License: Apache-2.0