Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Browser WebMCP guide

The built-in webmcp target generates an ESM npm package that exposes your API as browser tools through the generated TypeScript SDK. Your frontend explicitly registers the tools when a page is ready and removes them when that page leaves. No MCP server or stdio process is needed.

WebMCP is experimental. Its current API is document.modelContext, with async registerTool(tool, {signal}) and abort-based removal. The runtime can also adapt the legacy navigator.modelContext shape. Browser support varies: detect the capability and let your application continue when it is unavailable. The checks include structural adapters and a generated-package native smoke passed in Chromium 154.0.8037.57 with WebMCPTesting enabled. That smoke covers document backend detection, page-scoped registration, real HTTP execution, argument validation before requests, duplicate rejection, abort/disposal preserving unrelated host tools, and re-registration. Full cross-browser conformance is future work.

Generate and build

Add a browser target alongside the TypeScript SDK in tungsten.yml:

targets:
  typescript:
    package: "@acme/petstore"
    version: "1.0.0"
    out: generated/typescript
  webmcp:
    package: "@acme/petstore-webmcp"
    out: generated/webmcp

Run tungsten check and tungsten generate. Install and build the generated TypeScript SDK first, then install and build the browser package:

(cd generated/typescript && npm install && npm run build)
(cd generated/webmcp && npm install && npm run build)

The target accepts package, version, sdk, sdk_path, runtime, runtime_path, sdk_runtime and sdk_runtime_path, plus out. Without overrides, the sibling TypeScript target supplies the SDK package and version. runtime selects the @elysra/tungsten-webmcp version range; sdk_runtime selects the browser package's direct @elysra/tungsten-runtime dependency (default ^0.1.0). Published dependency defaults use version ranges.

For development from a compiler source checkout, build runtimes/ts and runtimes/webmcp first (npm ci && npm run build in each, in that order). Then configure all local dependencies explicitly, replacing /path/to/tungsten with your compiler checkout:

targets:
  typescript:
    package: "@acme/petstore"
    out: generated/typescript
    runtime_path: /path/to/tungsten/runtimes/ts
  webmcp:
    package: "@acme/petstore-webmcp"
    out: generated/webmcp
    sdk_path: ../typescript
    runtime_path: /path/to/tungsten/runtimes/webmcp
    sdk_runtime_path: /path/to/tungsten/runtimes/ts

Relative dependency paths are relative to the generated package directory. The two browser runtime paths select separate packages; both are needed for a fully local install. Regenerate, then install/build the SDK before the browser package using the commands above. Bundle the browser package through your existing frontend toolchain. The output contains manifest.json, src/index.ts, a preserved src/custom/index.ts, build configuration and a README. The main module exports manifest and register.

Register for a page

import { register, manifest } from "@acme/petstore-webmcp";
import { detectWebMcpBackend } from "@elysra/tungsten-webmcp";

const backend = detectWebMcpBackend();
if (backend) {
  const lifetime = new AbortController();
  const readNames = manifest.tools
    .filter(tool => tool.kind === "operation" && tool.safety === "read_only")
    .map(tool => tool.name);
  const registration = await register({
    backend,
    client: { baseUrl: new URL("/api", window.location.origin).href },
    allowlist: readNames, // narrow this to the reads needed on this page
    signal: lifetime.signal,
  });
  console.log(registration.registeredNames, registration.backend);
  // In your route/component cleanup:
  // lifetime.abort();
  // await registration.dispose();
}

Importing the package has no registration side effects. Without an allowlist, only read-only operations register. An explicit allowlist selects exact catalog names, including writes or macros when requested; an empty allowlist exposes nothing. Use the manifest to select tools for the current page. Dispose an old registration before replacing its scope. Disposal removes only tools owned by that registration and cancels its active executions.

The helper constructs a ClientCore using the SDK's descriptors and your ClientOptions. Supply the API base URL and browser session from your application. Generated browser code does not read environment variables or bundle service credentials. Use an authenticated same-origin API proxy or appropriate short-lived user credentials. The API must enforce authorization, CORS and CSRF protections. WebMCP requires the browser's supported secure context.

Safety, results and confirmation

Browser input schemas are not enforced by the browser. SDK argument validation, idempotency, diagnostic envelopes and confirmation rules still apply. Browser tool annotations describe intent; they are hints, not an authorization boundary.

When an explicitly selected operation requires a confirmation token, registration also adds <tool-name>__preview. Preview prepares the request and effects without executing the mutation. Pass its token in the original tool's reserved confirmation field with identical arguments. Keep caller-owned idempotency keys stable across retries. After OUTCOME_UNKNOWN, follow the diagnostic's verification action.

Tool execution receives the browser's cancellation signal and returns a JSON-serializable result. The runtime limits UTF-8 JSON output to 1 MiB by default; set maxResultBytes when registering to adjust the limit. Oversize output fails explicitly. Streaming and binary output require an explicit bounded result policy; see tool results. Tools with sensitive or one-time response fields are rejected before an API call. Await dispose() or abort the lifetime signal on route teardown.

For custom integration, the runtime also exports registerTungstenWebMcp({core, manifest, macros?, allowlist?, backend?, signal?, maxResultBytes?}). It returns a promise for {registeredNames, backend, dispose(): Promise<void>}. The generated src/custom/index.ts hook is preserved across regeneration.

Scope and API references

The adapter registers discrete tools with explicit page scope. The lifecycle controller connects scoped dynamic loading to route changes and React or Vue cleanup. The tool catalogue describes transport-neutral groups. Full cross-browser conformance and progressive browser meta-tools remain follow-ups. Server sandbox execution and run_script are not part of this browser package.

Track the WebMCP specification, implementation status and Chromium IDL for changes to this experimental API.