browser-relay
README.md
English | [简体中文](./README.zh-CN.md)
# 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`](https://github.com/microsoft/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.
## Prerequisites
1. **Node.js ≥ 18**.
2. **Install the original (upstream) playwright MCP server** — all real browser automation is done by it:
```bash
npm install -g @playwright/mcp
```
3. **Ready a browser**. `@playwright/mcp` drives the locally installed Google Chrome by default:
```bash
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
```bash
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:
```bash
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):
```json
{
"mcpServers": {
"browser-relay": {
"command": "node",
"args": ["/path/to/mcp-browser-relay/relay.mjs"]
},
"playwright": { "enabled": false }
}
}
```
**Claude Desktop / other JSON-configured hosts**:
```json
{
"mcpServers": {
"browser-relay": {
"command": "node",
"args": ["/path/to/mcp-browser-relay/relay.mjs"]
}
}
}
```
**Codex CLI** (`~/.codex/config.toml`):
```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`):
```yaml
- 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](https://github.com/microsoft/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](https://github.com/microsoft/playwright)** (Apache-2.0, Microsoft) — the foundation under all of the above: the cross-browser automation engine.
- **[Model Context Protocol](https://modelcontextprotocol.io) and the official TypeScript SDK [@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/typescript-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) license.
Third-party licenses and copyright notices: see [THIRD_PARTY_NOTICES.md](./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.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues