Skip to main content
Glama

preview-bridge

A standalone MCP server that exposes live browser-preview state to Claude Code.

Lets Claude answer questions like:

  • "What's in the form on the page right now?"

  • "The preview just errored — what was the message?"

  • "What did the user click last?"

  • "Wait until the next route change, then tell me."

Works with any browser tab — frameworks-agnostic. Includes its own WebSocket relay and HTTP host page; no FastAPI, no Python, no studio-v2 dependency.


Architecture

3 layers, no backend dependency.

Claude Code ── MCP stdio ──> preview-bridge MCP server (Node)
                                      │
                                      ├── http://127.0.0.1:5250   serves /__bridge.js + host.html + /state + /health
                                      └── ws://127.0.0.1:5251     receives events + state-snapshot replies from the page
                                                      │
                                                      └── browser tab loaded with __bridge.js
                                                              ├── window.onerror / unhandledrejection
                                                              ├── console.{warn,error} (verbose mode: log too)
                                                              ├── form input + change
                                                              ├── click on [data-bridge="track"]
                                                              └── history.pushState wrap

500-entry ring buffer; all 5 MCP tools are read-only in v1.


Related MCP server: gotham-browser

Install

Requires Node.js 20 or newer.

git clone https://github.com/verbalogicproject-creator/preview-bridge.git
cd preview-bridge
npm ci
npm run build
npm run smoke
npm run test:multi

The smoke test reports 17 passing assertions; the multi-session test reports 8 more. Then add the built server to your Claude Code MCP config (for example, ~/.claude.json), replacing /absolute/path/to/preview-bridge with this checkout's absolute path:

{
  "mcpServers": {
    "preview-bridge": {
      "command": "node",
      "args": ["/absolute/path/to/preview-bridge/dist/index.js"],
      "env": {
        "PREVIEW_BRIDGE_HTTP_PORT": "5250",
        "PREVIEW_BRIDGE_RELAY_PORT": "5251"
      }
    }
  }
}

Restart Claude Code. /mcp should now show preview-bridge with 5 tools.


Two usage modes

Add one line to your app's HTML:

<script src="http://127.0.0.1:5250/__bridge.js"></script>

__bridge.js auto-detects it's running top-level, opens a WebSocket to the bridge, and starts emitting events. No iframe wrapper needed.

(b) Hosted-iframe mode

Open http://127.0.0.1:5250/?app=<URL> in a browser. The host page wraps <URL> in an iframe. Important: for cross-origin URLs the host cannot inject __bridge.js into the iframe automatically — the iframe's own HTML must include the script tag. Use mode (a) unless the app you're observing already includes __bridge.js.


MCP tools

Tool

Input

What it returns

get_event_log

{ source?, level?, kind?, since?, limit? }

Ring-buffered events, filtered

get_runtime_errors

{ limit?, sinceTs? }

Recent runtime + unhandled-rejection errors with stack/file/line

query_preview_state

{ kind: 'form'|'route'|'selection'|'focus'|'all', selector? }

Live snapshot from the connected page (800ms timeout)

tail_events

{ kinds: BridgeKind[], maxWaitMs? }

Long-poll: blocks until matching event arrives or timeout

get_session_info

{}

Bridge connection state, mode, event count

Composition pattern for "what is the user looking at right now"

1. get_session_info()                 → confirm bridge is connected
2. query_preview_state({kind:'all'})  → route + focused el + form + selection
3. get_event_log({source:'iframe', limit:20})  → user's recent path

Event taxonomy

Single envelope, ARIA-shaped:

interface BridgeEvent {
  id: string;                                            // monotonic
  ts: number;                                            // epoch ms
  source: 'iframe' | 'host' | 'bridge';
  level: 'debug' | 'info' | 'warn' | 'error' | 'system';
  kind: BridgeKind;
  component: string;
  message: string;
  data?: unknown;
}

Kinds: session-connected, session-disconnected, runtime-error, unhandled-rejection, iframe-click, iframe-form-change, iframe-console, iframe-route-change, bridge-reinstalled.


Captured by default

  • Errors and unhandled rejections

  • console.warn + console.error (always)

  • console.logonly when the page sets <html data-bridge="verbose"> or window.__PREVIEW_BRIDGE_VERBOSE = true before __bridge.js loads

  • Form input/change events (300ms debounce) on <input>, <textarea>, <select>type=password is filtered at source

  • Clicks on elements with data-bridge="track" (opt-in by the app)

  • history.pushState and popstate for route changes


Configuration

Env var

Default

Purpose

PREVIEW_BRIDGE_HTTP_PORT

5250

HTTP server port

PREVIEW_BRIDGE_RELAY_PORT

5251

WebSocket relay port


Privacy / hygiene

The bridge captures form values from the observed page. Password fields (type=password) are filtered at the __bridge.js source and never reach the wire. But text/email/textarea/select fields are captured as typed. Don't point the bridge at pages containing real credentials, PII, or secrets.

The ring buffer is in-memory only; nothing persists across MCP restarts. Events do not leave the local machine.


Tests

npm run smoke
npm run test:multi

The smoke suite spawns the MCP, opens a simulated WebSocket client, and checks all five tools plus state snapshots, long-polling, and ring-buffer filtering. The multi-session suite verifies leader/follower proxying and promotion.


Limits & known gaps

  • Cross-origin iframe injection: the hosted-iframe mode (option b) can't inject __bridge.js into a cross-origin iframe. Use mode (a) for cross-origin scenarios.

  • No reverse channel in v1: tools are read-only. No inject_dom_command yet. Future enhancement.

  • No screenshot capture: visual state is out of scope (requires a headless-browser path not currently solved on Termux).

  • Single ring buffer: 500 events, shared across all sessions. Multi-tab correlation (per-session ids) is future work.


Roadmap

  • Reverse channel (inject_dom_command)

  • Session archive tool that drops a draft into gemini-expert's review queue once propose_memory ships

  • Session replay UI in host.html

  • Per-session correlation IDs


Built on

  • Ring-buffer + pub/sub primitive ported from ARIA's RingBufferLogger

  • MCP server skeleton mirrors gemini-expert/mcp-server/

  • Event-taxonomy philosophy inspired by Background Studio's functional-journey logging


Multiple Claude Code sessions (leader / follower)

Claude Code spawns one MCP child process per session, but this server owns two fixed ports and there is only ever one browser page being observed. So instances elect a role at startup:

Role

Owns

Answers tools by

leader

the WebSocket relay + HTTP host + the 500-entry event ring

reading its own ring

follower

nothing

asking the leader over HTTP (/events, /errors, /session, /preview-state, /tail)

A follower's answers are the leader's answers — same ring, same page. get_session_info reports the leader's state and stamps via: {role:"follower", pid} so the two are never confused.

What this fixes. Before, the second session's child hit EADDRINUSE, called process.exit(1), and Claude Code reported only "Connection closed" — a totally deterministic failure that looked like a flaky server and never recovered while the first session lived.

Two further guarantees:

  • A follower promotes itself. It polls the leader every 5s; when the leader's session ends, the follower binds the ports and takes over.

  • A leader dies with its client. The process now exits on stdin EOF, not just on SIGINT/SIGTERM. Previously a client that closed the pipe without signalling left a live process holding both ports with nothing attached to it, which poisoned every future session permanently. This was the actual outage.

A follower refuses to proxy to a service that does not identify itself as preview-bridge on /health — forwarding your questions to some other project's dev server that happens to hold port 5250 would be worse than failing.

Available Tools

5 tools
get_event_logA

Returns recent BridgeEvents from the live preview, ring-buffered (500 entries). Optional filters: source (iframe/host/bridge), level (debug/info/warn/error/system), kind. Pagination via 'since' (event id cursor). bridge-reinstalled events are filtered out by default; pass kind:'bridge-reinstalled' to see them.

ParametersJSON Schema
NameRequiredDescriptionDefault
kindNoFilter by event kind.
levelNoFilter by severity.
limitNoMax events to return (1-200, default 50).
sinceNoReturn events after this event id (cursor pagination).
sourceNoFilter by source.

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden. It discloses meaningful traits: ring-buffered with 500 entries, 'bridge-reinstalled' events filtered out by default, and cursor-based pagination via 'since'. It does not mention output shape or access requirements, but it provides solid behavioral context beyond the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three dense sentences contain all the essential facts with no wasted words. The core behavior is front-loaded, and parameters are summarized economically before the important default-filtering caveat.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with no required parameters and a fully documented schema, the description covers the important invocation details: source, level, kind, limit, since, and the default filter behavior. It does not describe the event return shape, and there is no output schema, but the tool is still sufficiently specified for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description adds value above the schema by explaining the ring-buffer behavior, the cursor semantics of 'since', and the non-obvious default exclusion of 'bridge-reinstalled' events, which clarifies how the 'kind' parameter behaves.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states a specific resource ('recent BridgeEvents from the live preview') and a verb ('Returns'), and it is easy to understand what the tool produces. However, it does not explicitly differentiate from the sibling 'tail_events', which is likely the closest alternative.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description conveys the context in which the tool is useful (reading recent live-preview events) and details optional filters and pagination. It gives no direct guidance about when to prefer another sibling like 'tail_events' or 'get_runtime_errors', so the selection knowledge is only implicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_runtime_errorsA

Returns recent runtime errors and unhandled-rejection events from the preview iframe. Each error includes message, stack, file, line, column when available. Use this when the user reports a broken preview or you want to see what just crashed.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax errors to return (1-50, default 10).
sinceTsNoOnly return errors with ts >= sinceTs (epoch ms).

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the burden of explaining behavior. It discloses the source (preview iframe), the event types, and the available error fields, plus the 'recent' and 'when available' caveats. It does not mention ordering, retention limits, or whether retrieval has side effects, but for a read-only error lookup the description is reasonably transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences with no filler. The first sentence states the action and output fields; the second provides the key usage scenario. It is appropriately sized and front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers the tool's purpose, output fields, source context, and a clear trigger for use. The optional parameters are well documented in the schema. The only notable gap is not addressing how this tool relates to sibling tools such as get_event_log or tail_events, but this is not critical for basic invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already documents both parameters with descriptions, ranges, and defaults, so schema coverage is 100%. The description adds no additional parameter-specific semantics, which is acceptable because the schema fully carries that weight.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool returns runtime errors and unhandled-rejection events from the preview iframe, with a specific verb and resource. It is easy to tell it is error-focused, though it does not explicitly name or differentiate itself from sibling tools like get_event_log or tail_events.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives a direct usage cue: 'Use this when the user reports a broken preview or you want to see what just crashed.' This provides clear context for when to invoke the tool, but it does not mention when not to use it or how it compares to alternative event/log tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_session_infoA

Returns bridge connection state: whether a host page is connected, in which mode (hosted-iframe vs top-level), the preview URL if known, total event count, and bridge version. Use to verify the bridge is actually receiving data before relying on other tools.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden. It discloses what the tool returns, including the caveat 'if known' for the preview URL, and implies a read-only diagnostic operation. It does not explicitly state side-effect-free behavior, but the zero-parameter getter framing makes this reasonably clear.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences: the first lists what is returned, and the second gives the use case. It is front-loaded, efficient, and contains no redundant filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter diagnostic tool with no output schema, the description is complete enough. It names all relevant return fields and explains why an agent would call it, making it clear and actionable.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has no properties, so there are no parameters to document. The description correctly adds no unnecessary parameter details, meeting the baseline for zero-parameter tools.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool returns bridge connection state and enumerates specific fields: connection mode, preview URL, event count, and bridge version. It is specific and unambiguous, but it does not explicitly differentiate itself from sibling tools like get_event_log or query_preview_state.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides a clear usage directive: 'Use to verify the bridge is actually receiving data before relying on other tools.' This tells an agent when to use it, though it does not mention when not to use it or name alternative tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_preview_stateA

Asks the connected browser page for a live state snapshot. Kinds: 'form' (all input/textarea/select values, password fields filtered), 'route' (current URL/pathname), 'selection' (highlighted text), 'focus' (focused element selector + value), 'all' (combined). Times out in 800ms if no host page is connected.

ParametersJSON Schema
NameRequiredDescriptionDefault
kindYesWhich slice of preview state to query.
selectorNoOptional CSS selector to scope the query.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full behavioral burden, and it does so well. It discloses that the operation is a live snapshot request, that password fields are filtered out of form results, and that it times out in 800ms when no host page is connected. These are meaningful behavioral traits beyond the schema, giving the agent accurate expectations about safety and failure behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and front-loaded, with the core action in the first sentence and the most important behavioral caveats (password filtering, timeout) included without filler. Every clause earns its place, and the list of kinds is structured for quick parsing.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a two-parameter tool with no output schema and no annotations, the description is remarkably complete. It explains what the tool returns for each kind, warns about password filtering, and specifies the timeout failure mode when no host page is connected. An agent has enough context to select, invoke, and interpret the result correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents both parameters, but the description adds substantial meaning beyond the bare field names. It explains what each 'kind' value returns, notes password filtering, and clarifies that 'combined' is available. The optional 'selector' parameter is less elaborated, but the schema's 'Optional CSS selector to scope the query' is adequate there.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Asks the connected browser page for a live state snapshot.' It then enumerates the exact kinds ('form', 'route', 'selection', 'focus', 'all'), making the tool's purpose unambiguous. It also distinguishes itself from the session/log/event/error siblings by framing this as a page-state query rather than historical or session data.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description establishes clear usage context: use this when you need the current live state of the connected browser page, especially form values, route, selection, or focus. It does not explicitly name sibling alternatives or state when not to use them, but the sibling names (get_session_info, get_event_log, get_runtime_errors, tail_events) are clearly different domains, so the intended placement is reasonably obvious.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tail_eventsA

Long-poll: blocks until at least one event matching kinds arrives, OR maxWaitMs elapses. Returns the matched events. Use to wait for specific runtime signals (route change, build complete, etc.) without polling get_event_log. Default maxWaitMs=5000, max 30000.

ParametersJSON Schema
NameRequiredDescriptionDefault
kindsYesEvent kinds to wait for (matches any in the list).
limitNoMax events to return (default 20).
maxWaitMsNoMax wait in ms (default 5000, max 30000).

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does disclose the key blocking behavior, timeout semantics, and return of matched events. It does not explicitly state what happens when maxWaitMs elapses with no matching event (empty list vs. null vs. error), which is a meaningful ambiguity.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The definition is compact and well-structured: the core blocking behavior is front-loaded, followed by use-case guidance, examples, and defaults. Every sentence earns its place without unnecessary filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the absence of annotations and an output schema, the description covers the essential behavior, parameters, and alternative usage well. The only notable gap is the unspecified timeout result, which prevents it from being fully complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents kinds, limit, and maxWaitMs. The description adds helpful context like runtime-signal examples and the default/max wait time, but it mostly restates what the schema already provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies a specific behavior (long-poll blocking) and a specific resource (events matching `kinds`). It also distinguishes itself from the more obvious sibling, get_event_log, by explicitly saying this tool avoids polling.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It states exactly when to use the tool: to wait for runtime signals like route changes or build completion. It also names the polling alternative (get_event_log) and frames this tool as the non-polling option, giving an agent clear selection guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 5 tool updatesv0.1.0
    • First observedget_event_log
    • First observedget_runtime_errors
    • First observedget_session_info
    • First observedquery_preview_state
    • First observedtail_events

TDQS

A4.1/5.0

Scored across 5 tools

Disambiguation4/5

Most tools have clearly distinct purposes: session info, event log, runtime errors, page state, and long-polling. The only possible confusion is between get_event_log and get_runtime_errors, since runtime errors may also appear as error-level events in the log, and between get_event_log and tail_events, though the descriptions explicitly frame one as historical and the other as waiting for new events.

Naming Consistency4/5

Three tools use the get_ prefix and the remaining two use query_ and tail_, which are understandable action verbs. The names are readable and mostly follow a verb_noun pattern, but the mix of get/query/tail prevents a perfect consistency score.

Tool Count5/5

Five tools is well-scoped for a preview bridge debugging surface. Each tool covers a distinct aspect of inspecting live previews without redundancy or bloat.

Completeness4/5

The toolset covers the main observability workflow: check connection, read historical events, inspect errors, query live page state, and wait for future events. Minor gaps exist, such as no explicit tool for arbitrary DOM inspection or waiting for the bridge to connect, but the surface is strong for its apparent purpose.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers