Skip to main content
Glama
pm990320

ultimate-playwright-mcp

by pm990320
README.md
# Ultimate Playwright MCP

[![npm version](https://img.shields.io/npm/v/ultimate-playwright-mcp)](https://www.npmjs.com/package/ultimate-playwright-mcp)
[![npm downloads](https://img.shields.io/npm/dm/ultimate-playwright-mcp)](https://www.npmjs.com/package/ultimate-playwright-mcp)
[![license](https://img.shields.io/npm/l/ultimate-playwright-mcp)](https://github.com/pm990320/ultimate-playwright-mcp/blob/main/LICENSE)
[![CI](https://github.com/pm990320/ultimate-playwright-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/pm990320/ultimate-playwright-mcp/actions/workflows/ci.yml)

Multi-agent Playwright MCP server with tab isolation via `targetId`. Allows multiple Claude instances (or other MCP clients) to share a single Chrome browser while maintaining isolated tab groups.

## Why Ultimate Playwright?

The official [`@playwright/mcp`](https://github.com/nickmccurdy/playwright-mcp) gives you browser control for a **single agent**. But what if you have **multiple agents** sharing one browser?

**Ultimate Playwright MCP** solves this with **tab group isolation**:

- šŸ”€ **Multi-agent tab groups** — Each agent creates a `groupId` and only sees its own tabs
- šŸŖ **Shared cookies & sessions** — All agents share the same BrowserContext (log in once, everyone's authenticated)
- šŸŽØ **Visual Chrome tab groups** — Companion extension organizes tabs into color-coded Chrome tab groups
- šŸ’¾ **Persistent registry** — Tab groups survive MCP server restarts (`~/.ultimate-playwright-mcp/tab-groups.json`)
- šŸ”Œ **Connect to existing Chrome** — Uses CDP to attach to your running Chrome (keeps your profile, extensions, bookmarks)

### Comparison

| Feature | **ultimate-playwright-mcp** | @playwright/mcp | browser-use-mcp |
|---|---|---|---|
| Multi-agent tab isolation | āœ… Tab groups with `groupId` | āŒ Single session | āŒ Single session |
| Shared cookies across agents | āœ… Same BrowserContext | N/A | N/A |
| Connect to existing Chrome | āœ… CDP | āŒ Launches new browser | āŒ Launches new browser |
| Visual tab groups in Chrome | āœ… Extension | āŒ | āŒ |
| Persistent tab registry | āœ… Survives restarts | āŒ | āŒ |
| Accessibility tree snapshots | āœ… Element refs (e1, e2…) | āœ… | āŒ Screenshot-based |
| Open source | āœ… MIT | āœ… Apache-2.0 | āœ… MIT |

## Features

- āœ… **Tab Isolation** - Each agent gets its own tabs via unique `targetId`
- āœ… **Shared Cookies** - All agents share the same BrowserContext (cookies, sessions, localStorage)
- āœ… **Parallel Execution** - Multiple agents can operate simultaneously without interference
- āœ… **CDP Connection** - Connects to existing Chrome via Chrome DevTools Protocol
- āœ… **Native Page Checkpoints** - Capture structured artifacts and generate reports per `targetId`
- āœ… **Battle-Tested** - Extracted from [OpenClaw](https://github.com/openclaw/openclaw) (MIT licensed)

## Installation

```bash
npm install -g ultimate-playwright-mcp
```

Or run directly with npx:

```bash
npx ultimate-playwright-mcp --cdp-endpoint http://localhost:9222
```

## Quick Start

### 1. Launch Chrome with Remote Debugging

```bash
# macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
  --remote-debugging-port=9222 \
  --user-data-dir=/tmp/chrome-debug

# Linux
google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug

# Windows
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" ^
  --remote-debugging-port=9222 ^
  --user-data-dir=C:\\temp\\chrome-debug
```

### 2. Configure Claude Desktop

Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "ultimate-playwright": {
      "command": "npx",
      "args": [
        "ultimate-playwright-mcp",
        "--cdp-endpoint",
        "http://localhost:9222"
      ]
    }
  }
}
```

### 3. Restart Claude Desktop

Claude will now have access to browser control tools with tab isolation.

## Usage Example

```
User: Open two tabs and navigate them independently

Claude: I'll create two tabs with separate targetIds:

1. browser_tabs({ action: "new" })
   → **targetId: ABC123...**

2. browser_tabs({ action: "new" })
   → **targetId: XYZ789...**

3. browser_navigate({ targetId: "ABC123...", url: "https://github.com" })
4. browser_navigate({ targetId: "XYZ789...", url: "https://google.com" })

Both tabs are now navigated independently!
```

## Available Tools

| Tool | Description | Key Parameters |
|------|-------------|----------------|
| `browser_tab_group` | Create/list/delete tab groups for isolation | `action`, `name`, `color`, `groupId` |
| `browser_tabs` | List, create, close, or select tabs | `action`, `groupId`, `targetId`, `index` |
| `browser_navigate` | Navigate to a URL | `url`, `targetId` |
| `browser_snapshot` | Capture accessibility tree with refs | `targetId` |
| `browser_click` | Click an element | `ref`, `targetId` |
| `browser_type` | Type text into an element | `ref`, `text`, `targetId` |
| `browser_hover` | Hover over an element | `ref`, `targetId` |
| `browser_press_key` | Press a keyboard key | `key`, `targetId` |
| `browser_fill_form` | Fill multiple form fields | `fields`, `targetId` |
| `browser_wait_for` | Wait for conditions | `text`, `selector`, `url`, `loadState`, `targetId` |
| `browser_checkpoint` | Capture a structured checkpoint for a tab | `name`, `targetId`, `collectors` |
| `browser_checkpoint_report` | Generate reports from stored checkpoints | `format`, `resultsDir` |

## Checkpoints

Use `browser_checkpoint` when you want a persisted capture of the current page for later review or report generation.

- Checkpoints are scoped to the resolved `targetId`, so they work with this server's tab isolation model.
- Artifacts and manifests are written under `~/.ultimate-playwright-mcp/checkpoints` by default.
- Generated reports are written under `~/.ultimate-playwright-mcp/checkpoints/report`.

Example:

```text
1. browser_checkpoint({ targetId: "ABC123", name: "after-login" })
2. browser_checkpoint_report({ format: "html" })
```

## Tab Groups (Multi-User Isolation)

When multiple users or agents share one browser instance, tab groups keep everyone's
tabs isolated. Each session creates its own group, and all tab operations are scoped
to that group.

```
User: Research product pricing

Claude: I'll create a tab group first, then open tabs within it.

1. browser_tab_group({ action: "create", name: "pricing-research", color: "blue" })
   → **groupId: g_a1b2c3d4e5f6**

2. browser_tabs({ action: "new", groupId: "g_a1b2c3d4e5f6", url: "https://example.com/pricing" })
   → **targetId: ABC123...**

3. browser_tabs({ action: "list", groupId: "g_a1b2c3d4e5f6" })
   → Only shows tabs in this group (not other users' tabs)
```

Meanwhile, another user on the same server:

```
1. browser_tab_group({ action: "create", name: "docs-review", color: "green" })
   → **groupId: g_x9y8z7w6v5u4**

2. browser_tabs({ action: "new", groupId: "g_x9y8z7w6v5u4", url: "https://docs.example.com" })
   → **targetId: XYZ789...**
```

Both users share the same cookies/sessions but only see their own tabs!

### Tab Group Lifecycle

1. **Create** a group at the start of your session
2. **Open tabs** within the group using `groupId`
3. **Work** with tabs using `targetId` as before
4. **Delete** the group when done (optionally closes all tabs)

Group state is persisted to `~/.ultimate-playwright-mcp/tab-groups.json` so it
survives MCP server restarts.

## Architecture

```
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│        Single Chrome Process                │
│    (--remote-debugging-port=9222)           │
│  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”   │
│  │     Single BrowserContext            │   │
│  │  (shared cookies, storage)           │   │
│  │                                      │   │
│  │  Group: alice (blue)                 │   │
│  │  ā”Œā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”                    │   │
│  │  │ Tab │ │ Tab │                    │   │
│  │  │  A  │ │  B  │                    │   │
│  │  ā””ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”˜                    │   │
│  │                                      │   │
│  │  Group: bob (green)                  │   │
│  │  ā”Œā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”            │   │
│  │  │ Tab │ │ Tab │ │ Tab │            │   │
│  │  │  C  │ │  D  │ │  E  │            │   │
│  │  ā””ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”˜            │   │
│  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜   │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
                   ↑
           CDP Connection
                   ↓
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│    ultimate-playwright-mcp (MCP Server)     │
│    - Tab routing via targetId               │
│    - Tab groups via groupId                 │
│    - Shared ownership registry (JSON file)  │
│    - Stdio transport                        │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
         ↓           ↓           ↓
   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
   │ Alice   │ │  Bob    │ │ Charlie │
   │ (Claude)│ │ (Claude)│ │ (Cursor)│
   ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
```

## MCP Configuration

### Cursor / Windsurf / Generic MCP Client

```json
{
  "mcpServers": {
    "ultimate-playwright": {
      "command": "npx",
      "args": ["ultimate-playwright-mcp", "--cdp-endpoint", "http://localhost:9222"]
    }
  }
}
```

### With Environment Variable

```json
{
  "mcpServers": {
    "ultimate-playwright": {
      "command": "npx",
      "args": ["ultimate-playwright-mcp"],
      "env": {
        "CDP_ENDPOINT": "http://localhost:9222"
      }
    }
  }
}
```

## CLI Options

```bash
ultimate-playwright-mcp [options]

Options:
  --cdp-endpoint <url>  CDP endpoint URL (e.g., http://localhost:9222)
                        Can also use CDP_ENDPOINT env var.
                        If omitted, daemon-managed Chrome is started lazily on first tool call.
  --agent-id <id>       Optional agent ID for logging/debugging
                        Can also use AGENT_ID env var
  --keep-alive          Auto-restart daemon-managed Chrome if it exits
                        Use --no-keep-alive for testing workflows where you want Chrome to stay down after kill
                        Default: disabled (no auto-restart)
                        Can also use KEEP_ALIVE env var (set to "false" to disable)
  --checkpoint-output-dir <path>
                        Root directory for checkpoint manifests, artifacts, and reports
                        Can also use CHECKPOINT_OUTPUT_DIR env var
  -V, --version         Output version number
  -h, --help            Display help
```

## Multi-Agent Setup

### Running Multiple Claude Code Instances

Each instance connects to the same MCP server and gets isolated tabs:

**Terminal 1:**
```bash
claude-code --mcp-config ./mcp-config.json
# Agent A creates tabs with targetIds starting from ABC...
```

**Terminal 2:**
```bash
claude-code --mcp-config ./mcp-config.json
# Agent B creates tabs with targetIds starting from XYZ...
```

Both agents share cookies and sessions but operate on different tabs!

## Persistent Chrome Setup (macOS)

For a Chrome instance that auto-starts with debug port:

Create `~/Library/LaunchAgents/com.user.chrome-debug.plist`:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.user.chrome-debug</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Applications/Google Chrome.app/Contents/MacOS/Google Chrome</string>
        <string>--remote-debugging-port=9222</string>
        <string>--user-data-dir=/Users/YOUR_USERNAME/chrome-debug-profile</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
</dict>
</plist>
```

Load with:
```bash
launchctl load ~/Library/LaunchAgents/com.user.chrome-debug.plist
```

## Development

```bash
# Install dependencies
npm install

# Build
npm run build

# Type check
npm run type-check

# Lint
npm run lint

# Watch mode
npm run watch
```

## License

MIT

## Attribution

This project extracts browser control code from [OpenClaw](https://github.com/openclaw/openclaw) (MIT licensed), which provides battle-tested tab isolation and Playwright integration.

Key extracted components:
- CDP session management (`pw-session.ts`)
- Browser operations (`pw-tools-*.ts`)
- Role-based element refs (`pw-role-snapshot.ts`)

## Links

- [OpenClaw](https://github.com/openclaw/openclaw) - Source of browser control code
- [MCP Specification](https://spec.modelcontextprotocol.io/) - Model Context Protocol
- [Playwright](https://playwright.dev/) - Browser automation library

TDQS

A3.7/5.0

Scored across 14 tools

Disambiguation5/5

Each tool serves a distinct browser automation action (e.g., click, type, navigate, snapshot). Potential overlaps like browser_click vs. browser_hover are clearly differentiated by operation type, and browser_evaluate handles cases outside the snapshot tree.

Naming Consistency5/5

All tools follow a consistent 'browser_verb_noun' pattern using snake_case. For example, browser_click, browser_navigate, browser_tab_group. No mixing of conventions.

Tool Count5/5

With 14 tools, the server covers core browser automation tasks without being bloated. The count feels well-scoped for its purpose.

Completeness4/5

The tool set covers essential browser actions (navigation, interaction, snapshot, screenshots, tabs, checkpoints). Minor gaps exist, such as no explicit scroll or file upload, but these are advanced and not critical for most automation workflows.

Maintenance

ActivityInactive
ResponsivenessNo issues