Skip to main content

Browsers Without WebMCP

WebMCP is a draft. Annotating a page for agents shouldn't wait for it to ship everywhere, so two small pieces cover the gap: a registry where the browser has none, and a relay that connects a tab to agents that live outside it.

The shim

npm install @axag/shim
import { installShim } from '@axag/shim';

installShim(); // does nothing where the browser has its own implementation

It implements the part of the draft a page calls — registerTool(tool, { signal }), with abort unregistering — so @axag/webmcp and the framework bindings work unchanged. Under 2 KB gzipped, no dependencies.

Installing it connects no agent. It is the socket a connection plugs into.

The bridge

npx axag-bridge --origin https://app.example.com
[axag-bridge] listening on ws://127.0.0.1:53422
[axag-bridge] pairing code: 7f3a91c2
import { connectBridge } from '@axag/bridge/client';

connectBridge({ url: 'ws://127.0.0.1:53422', pairingCode: '7f3a91c2' });

Any MCP client can now list and call that tab's tools, and the list changes as the person navigates.

browser tab  ──websocket──▶  axag-bridge  ──MCP (stdio)──▶  agent

Why a relay rather than a server in the page

A tab has no address an agent can dial, and isn't running when the agent starts. So the tab connects out to a small local process, and that process is the MCP server the agent talks to.

Calls run in the tab, in the session the person is already signed into. No credentials pass through the relay, and the page's safety enforcers wrap every call — a bridged agent meets the same confirmation and tenant checks as one running inside the browser.

What keeps it closed

  • Loopback only — the relay binds 127.0.0.1.
  • A pairing code printed in the terminal and copied into the page.
  • An origin allowlist via --origin, repeatable.
  • One tab at a time — a second connection is refused, not silently promoted.
  • Calls expire rather than hanging the agent when a tab stops answering.

Configuring an MCP client

MCP server configuration
{
"mcpServers": {
"axag-bridge": { "command": "npx", "args": ["axag-bridge", "--origin", "https://app.example.com"] }
}
}

Which to use

SituationReach for
Browser ships WebMCPNothing extra — @axag/webmcp uses it
Browser has no WebMCP, agent runs in the page@axag/shim
Agent runs outside the browser (desktop client, terminal)@axag/shim + @axag/bridge

MCP is an open protocol, and its remote transport is Streamable HTTP; the standalone SSE transport is deprecated. The bridge speaks stdio by default because that is what desktop clients configure most easily.