@lensmcp/bridge
v1.25.46
Published
LensMCP browser-event bridge — a build-agnostic WebSocket server that forwards @lensmcp/client-runtime envelopes onto the LensMCP event bus. Used by the Vite plugin and as a standalone sidecar for webpack / Next.js / no-build hosts.
Maintainers
Readme
@lensmcp/bridge
The build-agnostic browser-event bridge for LensMCP.
A tiny WebSocket server that the injected @lensmcp/client-runtime
connects to, forwarding every observed browser event (errors, console, fetch,
route changes, and pre-formed React/Valtio publishes) onto the LensMCP event
bus — either in-process or, more usually, cross-process over the same
LENSMCP_EVENT_FILE / LENSMCP_UDS / LENSMCP_IPC_SOCKET transport the
MCP server tails.
This logic used to live inside @lensmcp/vite-plugin, which is why
LensMCP only worked for Vite apps. It was lifted out here so the exact same
bridge can run:
- embedded in the Vite dev server (the plugin calls
createBridgeServeron an ephemeral port), or - standalone as a sidecar on a fixed port for webpack, Next.js,
or no-build hosts — where the browser runtime finds the endpoint by a
known URL (
ws://127.0.0.1:5747by default) instead of one a bundler baked in.
Standalone
LENSMCP_EVENT_FILE=.lensmcp/events.jsonl node bridge.js
# or
lensmcp bridge| Env | Default | Meaning |
| --- | --- | --- |
| LENSMCP_WS_HOST | 127.0.0.1 | bind host |
| LENSMCP_WS_PORT | 5747 | bind port (0 = ephemeral) |
| LENSMCP_SESSION_ID | — | session id stamped on each event |
| LENSMCP_EVENT_FILE | — | JSONL sink the MCP server ingests |
| LENSMCP_BRIDGE_ALLOWED_ORIGINS | — | browser origins admitted beyond loopback, comma-separated (https://app.test:8443, app.test, .app.test, null, *) |
| LENSMCP_BRIDGE_TOKEN | — | when set, every upgrade must carry it as ?token= |
On SIGTERM or SIGINT it stops accepting, drops every connected page (each
tab's gone is published), releases the port, settles the event transport,
and exits 0 — open tabs do not hold it. Anything that still hangs is cut off
after 3 s (exit 1); a second signal exits at once.
Who may connect
Browsers apply no same-origin policy to WebSockets, so every page the developer visits can reach the bridge's port. Each upgrade is therefore checked before a socket exists:
- Origin. A browser always sends
Originon an upgrade. Pages from loopback origins (localhost,*.localhost,127.0.0.0/8,[::1]) are admitted, plusLENSMCP_BRIDGE_ALLOWED_ORIGINS; anything else is refused with a 403 (and one console line naming the origin). An opaqueOrigin: nullis refused. NoOriginat all is a non-browser client (a Node producer, a test) and is admitted — a page cannot produce that shape. - Token (optional). With
LENSMCP_BRIDGE_TOKENset, an upgrade without it is refused with a 401. Give the page the same value viawindow.__LENSMCP_WS_TOKEN__or<meta name="lensmcp-ws-token">. - Bounds. At most 64 sockets; each socket gets a per-connection budget
(4096 envelopes, refilling at 512/s — every envelope inside a batch
counts). Envelopes past it are dropped and the next admitted one is
preceded by a
console.warnsaying how many. - Schema. Every envelope is checked field by field against the
BaseEventSchemafields it becomes, and the mapped event must parse as aBaseEvent; anything else is dropped.
Point the browser at it with @lensmcp/client-runtime/auto (zero build):
import '@lensmcp/client-runtime/auto';Embedding
import { createBridgeServer } from '@lensmcp/bridge';
const bridge = await createBridgeServer({ port: 0, bus, token, allowedOrigins: ['https://app.test'] });
console.log(bridge.port);
bridge.learnOrigin('https://app.local'); // an origin the host has seen itself serve
await bridge.close(); // drops connected pages, releases the port — the process keeps runningThe Vite plugin embeds it this way: it mints a token per dev-server start, bakes it into the runtime it serves, and teaches the bridge every origin it serves that runtime to (so a page behind the LensMCP gateway is admitted without configuration).
The bridge is strictly passive: it never mutates app state, never changes scheduling, and never throws into the host.
