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.