Browser Pool MCP
# π₯οΈ Browser Pool MCP
> Dynamic browser pool MCP server β every agent gets its own isolated browser.
[](https://opensource.org/licenses/MIT)
[](https://nodejs.org/)
[](https://modelcontextprotocol.io/)
**English** | [δΈζζζ‘£](./README_zh.md)
A [Model Context Protocol](https://modelcontextprotocol.io/) server that wraps
[Microsoft's `@playwright/mcp`](https://github.com/microsoft/playwright-mcp) and
turns it into a **dynamic browser pool**: each unique caller gets its own
isolated Chrome instance, so multiple Claude Code sessions, subagents, or any
MCP clients can run browser automation **concurrently without conflicts**.
## β¨ Features
- π§© **Per-agent isolation** β each unique `_agent_id` gets its own browser. No more
`"Browser is already in use"` errors when multiple agents hit the browser at once.
- π **On-demand spawning** β browsers are lazily started on first use (local CLI, ~1s),
never pre-allocated.
- π§Ή **Auto-cleanup** β idle instances are killed after 30 min; orphaned instances from
crashed wrappers are cleaned on startup.
- π‘οΈ **Never kills busy agents** β when the pool is full, the wrapper evicts only *idle*
instances; if all are busy it waits, so a running agent's browser is never yanked away.
- π **Backward compatible** β calling tools without `_agent_id` shares one "default"
browser, preserving original single-session behavior.
- π **Full Playwright toolset** β all `@playwright/mcp` tools (navigate, click, type,
screenshot, evaluate, tabs, networkβ¦), no feature loss.
## π§ Architecture
```
Claude Code main session ββstdioββ> playwright-pool-mcp (this server)
β
βββ agent "A" ββSSEββ> @playwright/mcp :9000 ββ> Chrome A
βββ agent "B" ββSSEββ> @playwright/mcp :9001 ββ> Chrome B
βββ agent "C" ββSSEββ> @playwright/mcp :9002 ββ> Chrome C
βββ "default" ββSSEββ> @playwright/mcp :9003 ββ> Chrome D
```
The wrapper does **not** control Chrome directly. It spawns `@playwright/mcp`
instances on demand and proxies MCP tool calls to them, adding per-agent routing.
## π Quick Start
### Prerequisites
- Node.js β₯ 18
- npm
### Install
```bash
git clone <your-repo-url> playwright-pool-mcp
cd playwright-pool-mcp
npm install
# First-time Chromium download (if needed):
npx playwright install chromium
```
### Configure Claude Code
**User-level global config** (`~/.claude.json`):
```json
{
"mcpServers": {
"playwright-pool": {
"type": "stdio",
"command": "node",
"args": ["/path/to/playwright-pool-mcp/playwright-pool-script.js"],
"env": {
"BROWSER_POOL_BASE_PORT": "9000",
"BROWSER_POOL_MAX_INSTANCES": "10",
"BROWSER_POOL_GLOBAL_MAX": "12",
"BROWSER_POOL_INSTANCE_TIMEOUT_MS": "1800000",
"BROWSER_POOL_DEBUG": "0"
}
}
}
}
```
Restart Claude Code. Tools appear as `mcp__playwright-pool__*`.
## π‘ Usage
### Mode 1: Shared (backward compatible)
Omit `_agent_id` β all calls share one "default" browser:
```json
mcp__playwright-pool__browser_navigate(url="https://example.com")
mcp__playwright-pool__browser_snapshot()
```
### Mode 2: Isolated per agent (recommended)
Pass `_agent_id` β each unique value gets its **own** browser:
```json
subagent A: mcp__playwright-pool__browser_navigate(url="https://news.ycombinator.com", _agent_id="agent-a")
subagent B: mcp__playwright-pool__browser_navigate(url="https://github.com/trending", _agent_id="agent-b")
subagent C: mcp__playwright-pool__browser_navigate(url="https://www.baidu.com", _agent_id="agent-c")
```
> β οΈ **Important**: pass the **same** `_agent_id` across all calls from one agent to
> keep its browser state (cookies, login, navigation). Different agents use different values.
### Subagent template
Create `.claude/agents/web-worker.md`:
```markdown
---
name: web-worker
description: Run browser tasks (scraping / research / testing / automation)
tools: mcp__playwright-pool__*, Read, Bash
---
Use mcp__playwright-pool__* tools to operate the browser.
**Key rule: pass the `_agent_id` parameter on every browser tool call.**
The main conversation may dispatch multiple web-workers concurrently; different
workers must use different `_agent_id` values so each gets its own isolated browser.
Examples:
- mcp__playwright-pool__browser_navigate(url="...", _agent_id="worker-1")
- mcp__playwright-pool__browser_evaluate(function="...", _agent_id="worker-1")
Use the same `_agent_id` for all calls within one worker.
```
## π οΈ Tools
All tools accept an optional `_agent_id` parameter. Aligned 1:1 with `@playwright/mcp` v0.0.78.
> βΉοΈ **Element targeting**: interaction tools use `target` (snapshot ref or CSS selector).
> The `ref` parameter is accepted as an alias for `target` (Claude Code's client maps it).
| Tool | Description |
|------|-------------|
| `browser_navigate` | Navigate to a URL |
| `browser_navigate_back` | Go back |
| `browser_snapshot` | Accessibility tree (LLM-friendly) |
| `browser_take_screenshot` | Screenshot (PNG/JPEG) |
| `browser_click` | Click element |
| `browser_type` | Type text into element |
| `browser_press_key` | Press keyboard key |
| `browser_hover` | Hover element |
| `browser_select_option` | Select dropdown option |
| `browser_drag` / `browser_drop` | Drag & drop |
| `browser_evaluate` | Run JavaScript |
| `browser_wait_for` | Wait for condition |
| `browser_find` | Find text in page snapshot |
| `browser_fill_form` | Fill multiple form fields (JSON array of `{name, target, type, value}`) |
| `browser_tabs` | Manage tabs |
| `browser_resize` | Resize window |
| `browser_handle_dialog` | Handle alert/confirm |
| `browser_file_upload` | Upload files |
| `browser_console_messages` | Get console logs |
| `browser_network_requests` / `browser_network_request` | Network activity |
| `browser_mouse_move_xy` / `browser_mouse_click_xy` / `browser_mouse_drag_xy` / `browser_mouse_down` / `browser_mouse_up` / `browser_mouse_wheel` | Mouse control (screen coordinates) |
| `browser_run_code_unsafe` | Run arbitrary code (dangerous) |
| `browser_close` | Close browser for this agent |
| `pool_status` | Pool status (per agent) |
## βοΈ Configuration
All config via environment variables in `~/.claude.json` β **no code changes needed**:
| Variable | Default | Description |
|----------|---------|-------------|
| `BROWSER_POOL_BASE_PORT` | `9000` | Starting port for browser instances |
| `BROWSER_POOL_MAX_INSTANCES` | `10` | Max concurrent browsers **per wrapper** (β300MB RAM each) |
| `BROWSER_POOL_GLOBAL_MAX` | `0` (disabled) | **Global** max browsers across ALL wrapper sessions (multiple Claude Code windows). File-lock based counter in `~/.browser-pool/`. Set e.g. `12` to cap total instances across every session. |
| `BROWSER_POOL_INSTANCE_TIMEOUT_MS` | `1800000` | Idle timeout (30 min) |
| `BROWSER_POOL_DEBUG` | `0` | Set `1` for debug logging to `debug.log` |
Changes take effect after restarting Claude Code.
> π‘ **Global limit** (`BROWSER_POOL_GLOBAL_MAX`): each Claude Code window runs its own
> wrapper, and `BROWSER_POOL_MAX_INSTANCES` limits only that wrapper. To cap browser
> instances across ALL sessions (e.g. two windows must share a total of 12), set
> `BROWSER_POOL_GLOBAL_MAX` to the same value in every session's config. Slots are
> counted via a lock-protected counter file (`~/.browser-pool/global-count`) and are
> released when instances close or the wrapper exits gracefully.
## π How It Works
1. Any browser tool call arrives β wrapper extracts `_agent_id` (defaults to `"default"`)
2. Looks up the per-agent assignment map β reuse existing browser if alive
3. If none: check the global limit (`BROWSER_POOL_GLOBAL_MAX`) β find a free port β spawn local `@playwright/mcp` CLI (`node cli.js --port X --isolated`)
4. Mark the instance `busy` during the call, so eviction never kills an in-flight call
5. Pool full? Evict only **idle** instances (oldest first); if all busy, wait up to 60s
6. Idle > 30 min β auto-killed; wrapper startup scans for orphaned instances and cleans them (PPID-guarded, never kills another live session's browsers)
7. Global slot accounting: acquired before spawn, released when the instance closes or the wrapper exits gracefully
## π¦ Dependencies
- [`@modelcontextprotocol/sdk`](https://www.npmjs.com/package/@modelcontextprotocol/sdk) β MCP protocol
- [`@playwright/mcp`](https://github.com/microsoft/playwright-mcp) β browser automation engine
- [`zod`](https://zod.dev/) β schema validation
## π License
[MIT](LICENSE)
TDQS
Scored across 25 tools
Most tools have distinct purposes, but browser_take_screenshot and browser_screenshot are explicit aliases, causing ambiguity. Additionally, browser_drag and browser_drop could be confused as both relate to drag-and-drop operations.
The majority of tools follow a browser_verb pattern (e.g., browser_navigate, browser_click), but there are exceptions like browser_snapshot, browser_console_messages, and browser_network_requests which use nouns. The pair browser_take_screenshot and browser_screenshot also breaks consistency.
At 25 tools, the server is on the heavy side but still plausible for a comprehensive browser automation tool. However, the presence of a duplicate screenshot tool and a test tool (pool_test) suggests some redundancy.
The tool set covers a wide range of browser actions: navigation, interaction, screenshots, snapshots, tab management, dialog handling, file upload, and more. Minor gaps exist (e.g., no explicit cookie management or scrolling) but core workflows are well covered.