Skip to main content
Glama

English | 简体中文

browser-relay — Multi-Session Browser-Isolated MCP

One MCP endpoint, N isolated browsers.

An MCP multiplexer: to your IDE / MCP host (ZCode, Claude Desktop, Codex CLI, dsh, …) it looks like a single, ordinary Playwright MCP server, but internally it routes every call tagged with a session_id to a dedicated @playwright/mcp child process for that id — so every session / subagent gets a fully isolated Chrome of its own (separate profile, separate tabs, cookies invisible to each other).

IDE (registers one MCP entry: browser-relay, stdio)
  └─ relay.mjs
       ├─ session_id "task-1" → child playwright-mcp --isolated → Chrome #1
       ├─ session_id "task-2" → child playwright-mcp --isolated → Chrome #2
       └─ ... (concurrency cap, idle reaping, crash auto-restart, full teardown on exit)

Within one relay process, different ids are isolated from each other. Different host sessions each spawn their own relay process, so even colliding ids are naturally isolated (process-level backstop).

Why

When multiple subagents / tasks run in parallel and share one playwright MCP, they share one browser: tabs stomp on each other, login state leaks between agents, one agent closes the page another agent is reading. browser-relay gives every session a browser of its own, while the host only registers a single MCP entry.

Related MCP server: chrome-devtools-mcp-mux

Prerequisites

  1. Node.js ≥ 18.

  2. Install the original (upstream) playwright MCP server — all real browser automation is done by it:

    npm install -g @playwright/mcp
  3. Ready a browser. @playwright/mcp drives the locally installed Google Chrome by default:

    npx playwright install chrome

    No Chrome, or prefer the bundled Chromium? Run npx playwright install chromium, then pass --child-arg --browser --child-arg chromium to the relay (forwarded to every child process).

  4. A global install is not mandatory: when the relay cannot find a global @playwright/mcp, it falls back to npx -y @playwright/mcp@0.0.82 (downloaded on first run; version is pinned — a global install is recommended to follow upstream updates). You can also point the RELAY_MCP_PATH environment variable or the --mcp-path option directly at the upstream cli.js.

Install & Run

git clone https://github.com/CarrotHu-secret-workstation/mcp-browser-relay.git
cd mcp-browser-relay
npm install

node relay.mjs --help          # show options
node relay.mjs                 # start (stdio, foreground)

Or skip the clone and run straight from GitHub with npx:

npx github:CarrotHu-secret-workstation/mcp-browser-relay --help

Running node relay.mjs on its own just waits on stdio — that is normal; that is how an MCP server gets launched by its host. To see it in action, register it with your MCP host as described below.

Hooking It into an MCP Host

Ground rules:

  • Register only this one entry per session; if you previously had a direct @playwright/mcp entry, disable it so you don't end up with two sets of near-identical tools.

  • MCP connections are established when a session starts — newly opened sessions only.

  • command is always node (or the absolute path to node on your machine); args points at the repo's relay.mjs.

ZCode (~/.zcode/cli/config.json; field names may vary by version):

{
  "mcpServers": {
    "browser-relay": {
      "command": "node",
      "args": ["/path/to/mcp-browser-relay/relay.mjs"]
    },
    "playwright": { "enabled": false }
  }
}

Claude Desktop / other JSON-configured hosts:

{
  "mcpServers": {
    "browser-relay": {
      "command": "node",
      "args": ["/path/to/mcp-browser-relay/relay.mjs"]
    }
  }
}

Codex CLI (~/.codex/config.toml):

[mcp_servers.browser-relay]
command = "node"
args = ["/path/to/mcp-browser-relay/relay.mjs"]
startup_timeout_sec = 60

dsh (profile overlay patch, e.g. ~/.dsh/profiles/<profile>/cordis.patch.yml):

- insert:
    - id: mcp-browser-relay
      name: '@deepseek-ai/dsh-mcp-client'
      config:
        serverName: browser-relay
        transport: stdio
        command: node
        args:
          - /path/to/mcp-browser-relay/relay.mjs

Verify with whatever your host offers (codex mcp list, dsh --profile web --dump-config, …). Once connected you will see the full upstream tool set (25 browser_* tools as of 0.0.82) plus the relay's own relay_status.

Rolling back / disabling: delete or enabled: false the entry above and restore your direct playwright entry.

Usage

Tool names are identical to @playwright/mcp; every tool takes one extra required parameter session_id:

browser_navigate { session_id: "task-1", url: "https://example.com" }
  • session_id rules: [A-Za-z0-9_-]{1,64}. Missing or invalid → immediate error asking you to fix it.

  • Different id = a completely different Chrome. Multiple agents sharing one id = sharing one browser (shared login state) — that is a feature.

  • relay_status { } lists active sessions (id / pid / idle time / concurrency cap); pass session_id to inspect a single one.

Assigning ids from the orchestrator (key discipline)

The session_id is assigned by the main session: when spawning a subagent, put the id in its prompt. Template:

For browser work, use only the mcp__browser-relay__* tools and pass session_id: "task-3" on EVERY call (that browser is yours alone). Do not use any other session_id.

  • Naming suggestion: <task>-<n> (e.g. audit-1, crawl-2); the orchestrator guarantees uniqueness.

  • To keep login state across relay restarts, start the relay with --profile-root <dir>: each id's profile persists at <dir>/<session_id> (the default --isolated is an in-memory profile, wiped when the browser is reaped).

relay.mjs Options

Option

Default

Description

--max-sessions <n>

auto

Cap on concurrent child browsers. Default: computed at every relay start as available RAM ÷ 400 MiB (byte-based, floored, min 1); an explicit value overrides the automatic one

--idle-kill-ms <ms>

1800000

Reap an id's browser after this much idle time (0 = never)

--headless

off

Headless child browsers (default headed, so you can eyeball the multiple Chromes)

--profile-root <dir>

none

Enable a persistent per-id profile (default --isolated in-memory profile)

--mcp-path <file>

auto

Path to the upstream @playwright/mcp/cli.js (default: global npm prefix first, then npx fallback)

--child-arg <arg>

none

Extra argument forwarded to every child playwright-mcp (repeatable)

The RELAY_MCP_PATH environment variable is equivalent to --mcp-path.

Known Limitations

  • Identity is "discipline + schema validation" grade: any caller can claim someone else's id. The orchestrator's id assignment discipline is the single source of isolation; colliding ids share a browser (exploitable on purpose).

  • Upstream capabilities follow @playwright/mcp upgrades automatically (the tool list is aggregated from the child at startup).

  • The concurrency cap takes effect per relay process: each host session spawns its own relay, which sizes itself from remaining memory at its own start (earlier sessions get a bigger quota, later ones shrink — natural first-come-first-served). The value stays fixed for the lifetime of the process. 400 MiB is a coarse per-browser budget (including all its child processes and pages); tune it to your machine.

  • Developed and verified on Windows; the macOS/Linux code paths (POSIX signals, process-tree kill, npx fallback) are in place but untested — issues and PRs welcome.

Acknowledgements

Every bit of browser capability in this project stands on the shoulders of the following projects — our respect and thanks:

  • @playwright/mcp (Apache-2.0, Microsoft) — the real upstream Playwright MCP server. The relay is just a session pool + forwarder: launching every Chrome, the whole browser_* tool set, snapshots / screenshots / clicks / typing — all done by it.

  • Playwright (Apache-2.0, Microsoft) — the foundation under all of the above: the cross-browser automation engine.

  • Model Context Protocol and the official TypeScript SDK @modelcontextprotocol/sdk (MIT, © Anthropic, PBC and community contributors) — the MCP protocol and SDK that make "one host, one stdio, bidirectional forwarding" possible.

Inspired by a real pain point: the browser turf war between parallel agents. This repo is a thin layer of glue — upstream upgrades (new tools, new capabilities) are picked up with zero code changes. Without the projects above, this repository would not exist.

License

This project's code is released under the MIT license.

Third-party licenses and copyright notices: see THIRD_PARTY_NOTICES.md — @playwright/mcp and playwright are Apache-2.0; @modelcontextprotocol/sdk is MIT. This repository does not copy or modify their source code; they are used at runtime as npm dependencies. The notices file exists for compliance and as a tribute.

Related MCP Connectors

Related MCP Servers