Give any MCP client — Claude Code, Codex, Cursor, OpenClaw — the ability to run tasks in your real browser session: already signed in, cookies present, MFA already passed.
That session is the whole point. A headless automation framework starts logged out of everything and hits a login wall on the first useful page. WebBrain is already inside the browser you use.
Claude Code ──stdio──▶ webbrain-mcp ──ws://127.0.0.1:17374──▶ WebBrain extension ──▶ your tabs
npm install -g @webbrain/mcp-serverOr run from a checkout:
cd mcp-server && npm install && npm run buildChromium first. Chrome, Edge, Brave, Opera, Vivaldi run the bridge from the extension's offscreen document (
src/chrome/src/offscreen/cloud-bridge.js). Firefox has no offscreen document, sosrc/firefox/src/cloud-bridge.jsruns it in the background page, configured under the same Settings → Bridge tab; it has not yet been verified against this MCP server on a real Firefox install.This server does not use the optional browser-approval handshake (token,
connection_pending/connection_approved); leave the Cloud Bridge token empty. It authenticates the extension with a pairing key instead (below). See Cloud Bridge browser approval for backends that want approval.
The MCP server hosts the listener; the extension dials out to it. A Manifest V3 extension cannot listen on a socket, so the direction is fixed.
-
Install the WebBrain extension and open your browser.
-
Print the pairing key:
npx -y @webbrain/mcp-server pairing-key
The first run creates it in
~/.webbrain/bridge-key(%USERPROFILE%\.webbrain\bridge-keyon Windows), readable only by you. You can also open that file and copy its contents. -
In WebBrain → Settings → Bridge, set the URL to
ws://127.0.0.1:17374/extension, paste the key into Pairing key, and enable the bridge. -
Ask your MCP client to call
webbrain_connectionto confirm.
The LM Studio plugin reads the same key file, so one pairing covers both destinations.
Anything on your machine can open a socket to 127.0.0.1:17374 and claim to be WebBrain, so the server does not trust a socket until it proves it holds the pairing key:
- On connect the server sends
{type: "pairing_challenge", scheme: "hmac-sha256-v1", nonce}with a fresh random nonce. - The extension answers
{type: "pairing_proof", scheme, proof}, whereproofis the hex HMAC-SHA256 of"webbrain-bridge-pairing-v1:" + noncekeyed with the pairing key. The key itself never crosses the socket, and a recorded proof is useless on the next socket. - Only after a valid
helloand a valid proof does the socket become the extension. Until then it receives no commands and the already-connected extension keeps working. A wrong proof closes the socket with code4001; a socket that does not pair within 10 seconds is closed the same way.
The extension stops reconnecting after a 4001 and shows the reason under Settings → Bridge. Saving a new key reconnects it.
Earlier versions of this server accepted any socket that sent the right hello. After upgrading:
- Update the WebBrain extension first. A version without the Pairing key field cannot pair, and this server closes its socket after 10 seconds with "Pairing required". The updated extension still works with older servers: without a challenge it never sends a proof.
- Run
npx -y @webbrain/mcp-server pairing-key, paste the key under Settings → Bridge → Pairing key, and press Save & test connection.
Deleting ~/.webbrain/bridge-key and restarting the server rotates the key; paste the new one into WebBrain.
One bridge at a time. The extension holds exactly one outbound bridge socket. Pointing it here means it is not pointed at WebBrain Cloud (
17373) or the LM Studio plugin (17375). Switch it under Settings → Bridge.
Claude Code
claude mcp add --transport stdio webbrain -- npx -y @webbrain/mcp-serverCodex / Cursor / anything reading mcp.json
{
"mcpServers": {
"webbrain": {
"command": "npx",
"args": ["-y", "@webbrain/mcp-server"]
}
}
}The MCP client launches this command as a child process when it starts the configured server; you do not need to keep a second copy running in a terminal. After adding the configuration, restart or reconnect the MCP client if WebBrain does not appear in its tool list.
For a direct launch or connection test, run:
npx -y @webbrain/mcp-serverLeave that terminal open while using the bridge. The server owns the local
listener on 127.0.0.1:17374, and exiting it closes the bridge. Press Ctrl+C
to stop it.
From a source checkout, build and launch the same server with:
cd mcp-server
npm install
npm run build
npm startAfter it is running, enable ws://127.0.0.1:17374/extension under WebBrain →
Settings → Bridge with the pairing key from npx -y @webbrain/mcp-server pairing-key
(or node dist/index.js pairing-key in a checkout). Ask the MCP client to call
webbrain_connection to verify the complete connection.
Connection error: WebSocket error normally means that no process is
listening at the URL selected in WebBrain. Confirm the MCP server is still
running and that Settings uses port 17374, then check the listener:
lsof -nP -iTCP:17374 -sTCP:LISTENNo output means the MCP server is not listening. If it is listening but the
extension does not connect, make sure another bridge destination is not selected:
WebBrain Cloud uses 17373, this MCP server uses 17374, and the LM Studio
plugin uses 17375. Only one can be selected at a time. The bridge is available
in Chromium browsers only, not Firefox.
Rejected by the backend: Pairing key mismatch means the key under
Settings → Bridge → Pairing key is not the one this server uses. Print it
again with npx -y @webbrain/mcp-server pairing-key. If the server runs with
WEBBRAIN_BRIDGE_KEY or WEBBRAIN_BRIDGE_KEY_FILE, use that key instead.
This bridge requires a pairing key means the field is empty.
| Tool | Purpose |
|---|---|
webbrain_run |
Delegate a task. mode='ask' is read-only; mode='act' can click and type, gated by in-browser approval. |
webbrain_extract |
Read authenticated page data into a caller-supplied JSON Schema. Always runs in read-only Ask mode. |
webbrain_status |
Poll a run, or list every run. |
webbrain_respond |
Answer a run sitting at needs_user_input. |
webbrain_abort |
Stop a run. Actions already taken are not undone. |
webbrain_connection |
Report whether the extension is attached, and how to fix it if not. |
When a run pauses for an ordinary clarification, relay the question and pass the
user's answer verbatim to webbrain_respond. Permission prompts are structured:
after the user explicitly decides, send the exact stable value once, always,
or deny. A normal approval maps to once; use always only when the user
explicitly requests a persistent grant. Localized labels such as "allow" must
not be forwarded as the permission value because the extension deliberately
fails closed on unknown decisions.
Form-submission confirmations and saved-workflow repair prompts are structured
the same way, and fail closed the same way. Each lists the values it accepts on
a decisions: line in the status text — send one of those exactly.
"Open my Stripe dashboard and list last week's failed payments."
webbrain_run(task: "open the Stripe dashboard and list last week's failed
payments with amounts and customer emails", mode: "ask")
Read-only, in the tab you are already authenticated in. No API key, no headless login dance.
For predictable JSON instead of prose, give webbrain_extract an explicit
JSON Schema:
webbrain_extract(
task: "list the overdue invoices visible in this account",
output_schema: {
type: "object",
properties: {
invoices: {
type: "array",
items: {
type: "object",
properties: {
customer: { type: "string" },
amount: { type: "number" },
due_date: { type: "string" }
},
required: ["customer", "amount", "due_date"]
}
}
},
required: ["invoices"]
}
)
This is still a task-level delegation through the browser agent and its normal permission boundary; it is not a direct page-scraping primitive.
WebBrain exposes roughly fifty primitives internally — click_ax, type_ax, extract_data, iframe_read and so on. This server deliberately does not surface them.
Safety. WebBrain's capability × origin permission gate runs in the agent loop (_executeToolBatch), not inside executeTool(). An MCP layer calling primitives directly would sit below the gate and bypass every approval prompt the product is built on. Delegating a goal keeps the trust boundary in the browser, where the human is.
Cost. Driving a UI one primitive at a time over a socket costs a round trip and a slab of tokens per click. Handing over a goal costs one call.
| Variable | Default | Meaning |
|---|---|---|
WEBBRAIN_BRIDGE_PORT |
17374 |
Port the extension connects to. |
WEBBRAIN_BRIDGE_PATH |
/extension |
Path segment; must match the URL in Settings. |
WEBBRAIN_COMMAND_TIMEOUT_MS |
30000 |
Per-command reply timeout. |
WEBBRAIN_RUN_TIMEOUT_MS |
300000 |
Default ceiling for webbrain_run polling. |
WEBBRAIN_POLL_INTERVAL_MS |
1000 |
Status poll interval. |
WEBBRAIN_BRIDGE_KEY |
— | Pairing key to use instead of the key file (at least 16 characters). |
WEBBRAIN_BRIDGE_KEY_FILE |
~/.webbrain/bridge-key |
Key file to read, or create on first start. |
A host process that launches dist/index.js itself (for example over stdio, as
webbrain.me does) configures pairing through the same environment:
- Pass nothing to use the shared
~/.webbrain/bridge-key, and show the user the output ofnode <path-to>/dist/index.js pairing-key(or the file's contents) to paste into WebBrain. - Or generate a key, keep it in the host's own user-only storage, and pass it as
WEBBRAIN_BRIDGE_KEYin the child's environment.WEBBRAIN_BRIDGE_KEY_FILEpoints the server at a different file instead.
webbrain_connection answers Connected. only for a paired extension. Its
"Not connected" text names the key's source (a file path or variable name),
never the key.
- The listener binds
127.0.0.1only. Anything that can reach this port can drive your signed-in browser — never expose it to a network or a container bridge. - Connections from HTTP(S) pages and other non-extension browser origins are rejected outright. Every other socket must send the extension's
helloframe and answer the pairing challenge (see Pairing) before it receives a command; until then it cannot replace the connected extension. A local process without the key can still occupy the port before the server starts, or connect and be refused, but it cannot read tasks or answer them. - The pairing key is as sensitive as the browser session it unlocks. It lives in a user-only file; do not paste it into an MCP client chat. See
docs/security-model.md. - A
webbrain_runtimeout does not abort the run. A task that already submitted a form should not be silently killed — the browser keeps going andwebbrain_statuspicks it back up. allow_api_mutationslifts WebBrain's UI-first rule and is off by default. It is accepted only withmode: "act"; Ask runs remain read-only. The UI path is visible and stoppable; direct API mutations are neither.
npm run build && node --test test/bridge.test.mjsThe suite stands up the real listener and connects a fake extension speaking the exact frames src/chrome/src/offscreen/cloud-bridge.js emits — handshake, pairing, id correlation under concurrency, error propagation, disconnect mid-command, and the poll/timeout/clarify paths. test/pairing.test.mjs also runs the extension's real Chrome and Firefox bridge code against the server. If the extension's wire format changes, these fail. That is intentional.
For manual poking, scripts/fake-extension.mjs is
the same fake as a standalone process. It pairs with the server's key — run the server, then:
node scripts/fake-extension.mjsIt logs every frame in both directions, so you can watch what your MCP client actually sends without Chrome in the loop.
This independently published package remains MIT-licensed. See LICENSE.