smithery.ai

browserbridge

Use when a task needs real-time control of a connected browser via the Browser Bridge Broker — submit JS jobs over HTTP that browsers eval and return.

First seen Mar 19, 2026

Installation

$ npx skills add https://smithery.ai

Also in this package

Other skills from smithery.ai · top by installs.

npx skills add https://smithery.ai

Browse all from smithery.ai

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 8,317 B
  • docs SUMMARY.md 110 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

BrowserBridge — Browser Bridge Broker

A message broker for JavaScript jobs. Clients (you) submit JS over HTTP; connected browsers running browser-bridge-client.js execute it with eval and the typed result is routed back.

client ──HTTP──▶ broker ──WebSocket──▶ browser (eval) ──result──▶ broker ──▶ client

The broker is a zero-dependency Node server (server.js). Full docs live at GET /bridge/readme (unauthenticated) and a machine-readable manifest at GET /bridge/.

Endpoints & auth

Two base URLs point at the same broker:

  • Public: https://dw.ramsden-international.com/bridge — use this. Through Apache, incl. the /ws WebSocket upgrade.
  • Local: http://localhost:3141 — only from the broker host itself (rivsprod01). The broker binds loopback, so this fails from any other machine, workstations included.

Auth: send Authorization: Bearer <BRIDGE_TOKEN> on every call except the unauthenticated ones: /, /readme, /health, /client.js, /status.

The shared secret is the broker's BRIDGE_TOKEN env var. If you don't have it, ask the user, or read it from the running unit (systemctl show browser-bridge-broker -p Environment).

Method Path Body Behaviour
GET / — Self-describing manifest (unauthenticated discovery).
GET /readme — This README as Markdown (unauthenticated).
POST /jobs/sync {script, target?, timeout?} Dispatch and block until the result arrives. 503 if no browser, 408 on timeout.
POST /jobs {script, target?} Enqueue, return {jobId}. Runs now, or when a browser connects.
GET /jobs/:id — Job status + result (pending/dispatched/done/failed/expired).
GET /workers — Connected browsers: connectionId, ip, url, host, path, title.
GET /health — {status, workers, jobs} (unauthenticated).
GET /status — HTML dashboard (unauthenticated shell; paste token in-page).

target is a specific connectionId; omit it to run on any one connected browser.

Quick start

TOKEN=<BRIDGE_TOKEN>
BASE=https://dw.ramsden-international.com/bridge

# 1. Is the broker up and are any browsers connected?
curl -s $BASE/health          # {"status":"ok","workers":N,"jobs":M}

# 2. Which browsers / pages are connected (need the right one before targeting)?
curl -s -H "Authorization: Bearer $TOKEN" $BASE/workers

# 3. Run JS on any connected browser (blocks until result):
curl -s -XPOST $BASE/jobs/sync \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"script":"document.title"}'

# 4. Target a specific tab by connectionId:
curl -s -XPOST $BASE/jobs/sync \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"script":"document.title","target":"proxy_1783..._abc"}'

On the broker host itself, BASE=http://localhost:3141 works identically.

Structured result

A job returns the value with its type, anything the script logged, and on failure the error with a stack:

{
  "jobId": "...", "status": "done",
  "result": "Your Basket",                     // real JSON value where serializable
  "resultType": "string",                      // string|number|boolean|object|array|element|...
  "logs": [{"level":"log","message":"..."}],   // console output captured during eval
  "error": null, "stack": null,
  "workerConnectionId": "...", "createdAt": 1750, "completedAt": 1750
}

DOM elements come back as outerHTML with resultType: "element". On status: "failed", error and stack are populated instead of result.

Script form

A job's script is either a JS expression or statements ending in return, and top-level await works:

document.title                                              // expression
(await fetch('/api/cart')).json()                           // async expression
const r = await fetch('/api/cart'); return (await r.json()).total;   // statements + return

Long async jobs may exceed the default /jobs/sync timeout (10s) — pass timeout (ms) in the body, or use POST /jobs + poll GET /jobs/:id.

Escaping: keep it simple by using single quotes inside the JS and double quotes for the JSON: -d '{"script":"document.querySelector('"'"'#id'"'"')?.textContent"}', or write the JS to a file and build the body with a heredoc / jq -Rs.

Cooperative pages: the window.bridge helper

Raw eval works on any page, but selectors break when a page is restyled. Pages can opt into a stable contract. The client exposes window.bridge; a page declares addressable elements with data-bridge-node and/or registers named actions:

<span data-bridge-node="cart-total">£0.00</span>
<script>bridge.register('checkout', () => document.querySelector('#pay').click());</script>

Drive the page by stable name, not selector:

Call Returns
bridge.nodes() enumerate declared nodes [{node,tag,text,value}] — the page's contract
bridge.node(name) element tagged data-bridge-node="name" (or null)
bridge.all(name) all elements tagged data-bridge-node="name"
bridge.actions() registered action names
bridge.action(name, ...args) invoke a registered action (may return a Promise — await it)
bridge.register(name, fn) page-side: register a named action

Also advertised in GET / under pageHelper.

Getting a browser connected

Pages connect by loading the client. Either add it per-page:

<script>
  window.__BRIDGE_URL   = 'wss://dw.ramsden-international.com/bridge/ws';
  window.__BRIDGE_TOKEN = '<BRIDGE_TOKEN>';
</script>
<script src="/browser-bridge-client.js"></script>

…or auto-inject the broker's own worker script into every page via Apache mod_substitute:

AddOutputFilterByType SUBSTITUTE text/html
Substitute "s|</head>|<script src=\"https://dw.ramsden-international.com/bridge/client.js\"></script></head>|in"

The client evals incoming scripts, so the page's CSP must not block eval — don't set a restrictive script-src on pages that load the client. For proxied pages, moddeflate/gzip must be off so Substitute can see the HTML. See deploy/apache-inject.conf and deploy/apache-bridge.conf (the latter includes the WebSocket upgrade rule; a2enmod proxy proxyhttp proxy_wstunnel).

Troubleshooting

  • {"error":"unauthorized"} — missing/wrong Authorization: Bearer <token> header on an

authed endpoint. /health and /status are unauthenticated; /workers, /jobs* are not.

  • curl: (7) connection refused — broker not running. systemctl status browser-bridge-broker,

or start manually: BRIDGE_TOKEN=... node server.js.

  • 503 from /jobs/sync — no browser connected. Check /workers; open/reload a page that

loads the client.

  • 408 from /jobs/sync — job ran longer than the timeout. Pass a larger timeout, or

enqueue with POST /jobs and poll GET /jobs/:id.

  • Page never appears in /workers — its CSP is blocking client.js (script-src) or the

WebSocket (connect-src), or gzip is on for a proxied page so injection didn't happen.

  • GET /status — live dashboard; paste the token in-page (kept in sessionStorage, sent

only as a Bearer header) to see workers grouped by host.

Security notes

  • Broker listens on loopback (127.0.0.1:3141); Apache fronts the public /bridge path.
  • Token-authed, but it executes arbitrary JavaScript in connected browsers by design —

treat the token as a secret and don't run untrusted scripts.

  • Not a general-web tool: it only reaches browsers that have loaded the client.