iflow-mcp_alexrwilliam_playwright-mcp 0.2.0
A minimal, robust Playwright MCP server for browser automation
License: MIT
Requires Python: >=3.10

# Playwright MCP Server

A minimal, robust Playwright MCP (Model Context Protocol) server that exposes core browser automation capabilities via a simple API.

## Features

- **Browser Context Management**: Persistent browser context (headless or headed, configurable)
- **Multi-Page Support**: Handle multiple tabs/windows, switch between pages, manage popups
- **Navigation**: Open URLs, reload, go back/forward
- **DOM Interaction**: Click, type, fill, select, hover, scroll using Playwright selectors
- **Element Discovery**: Query elements using CSS, XPath, role, text, and other Playwright locators
- **Snapshotting**: Get HTML, accessibility snapshots, screenshots, and PDFs
- **Token-Aware Outputs**: Configurable caps keep element queries and accessibility snapshots within safe sizes for LLM consumption
- **Overflow Artifacts**: Large responses are trimmed automatically, auto-cleaned after ~2 hours by default, and include previews plus file references you can reopen via the artifact reader tools
- **Binary-Friendly Defaults**: Screenshots, PDFs, and other large blobs are stored as artifacts with metadata previews so LLMs stay in control of context size
- **Script Evaluation**: Run JavaScript in the page context
- **Network Monitoring**: Capture and analyze all network requests and responses
- **Network Interception**: Block, modify, or mock network requests
- **Cookie Management**: Get, set, and clear browser cookies
- **Storage Access**: Manage localStorage and sessionStorage data
- **Headers & User Agent**: Customize request headers and browser identity
- **Raw Output**: All outputs are raw Playwright results with no post-processing

## Installation

### Quick Install from GitHub

```bash
# Install directly from GitHub
pip install git+https://github.com/alexrwilliam/playwright-mcp-server.git

# Install Playwright browsers
playwright install
```

### For Development

```bash
# Clone the repository
git clone https://github.com/alexrwilliam/playwright-mcp-server.git
cd playwright-mcp-server

# Install in development mode
pip install -e .

# Install browsers
playwright install
```

## Usage

### Running the Server

After installation, you can use it from anywhere:

```bash
# Run with stdio transport (for MCP clients)
playwright-mcp stdio

# Run with HTTP transport
playwright-mcp http --port 8000

# Run in headed mode (default is headless)
playwright-mcp stdio --headed
```

### Command Line Usage

```bash
# Run the MCP server
playwright-mcp stdio

# Run with visible browser
playwright-mcp stdio --headed

# Run HTTP server
playwright-mcp http --port 8000

# Use different browsers
playwright-mcp stdio --browser firefox
playwright-mcp stdio --browser webkit

# Use real Chrome instead of bundled Chromium
playwright-mcp stdio --channel chrome

# Use real Chrome with your profile (cookies, extensions, history)
playwright-mcp stdio --channel chrome --user-data-dir "/Users/you/Library/Application Support/Google/Chrome"

# Adjust response budgets (defaults: 4k inline, 400-char previews)
playwright-mcp stdio --max-response-chars 6000 --preview-chars 600

# Choose where overflow artifacts are written
playwright-mcp stdio --artifact-dir /tmp/playwright-artifacts

# Control how much each artifact chunk returns inline
playwright-mcp stdio --artifact-chunk-size 8192

# Other Chrome channels
playwright-mcp stdio --channel chrome-beta
playwright-mcp stdio --channel chrome-dev
```

### Stealth & Fingerprinting Controls

- Built-in stealth preset: `--stealth` masks `navigator.webdriver`, stubs `chrome.runtime`, injects realistic plugins/mimeTypes, shims permissions, and masks devtools/CDP probes (disable the devtools masking with `--no-stealth-devtools`, or enable it standalone via `--stealth-devtools`).
- Init scripts: `--init-script ./hacks.js` injects your own JS via `context.add_init_script` before any page scripts execute.
- Fingerprint knobs (applied at context creation): `--user-agent`, `--sec-ch-ua*`, `--accept-language`, `--languages`, `--locale`, `--timezone-id`, `--platform`, `--vendor`, `--hardware-concurrency`, `--device-memory`, `--device-scale-factor`, plus viewport flags already present.
- Permission shaping: `--grant-permissions geolocation,clipboard-read --permission-state notifications=granted` pre-grants context permissions and overrides `navigator.permissions.query` responses.
- Example: `playwright-mcp stdio --stealth --user-agent "Mozilla/5.0 ..." --sec-ch-ua "\"Chromium\";v=\"122\", \"Google Chrome\";v=\"122\"" --languages en-US,en --platform MacIntel --vendor "Google Inc." --hardware-concurrency 8 --device-memory 8 --device-scale-factor 2`.

### Anti-detect presets (opt-in)

- Use a preset at startup: `--antidetect-preset pixelscan` or `--antidetect-preset clearance-safe` (opt-in, does not change defaults).
- Apply at runtime via tools: `apply_antidetect_preset(preset="pixelscan")` / `apply_antidetect_preset(preset="clearance-safe")` or `recreate_context(antidetect_preset="...")`.
- `clearance-safe`: headed Chrome channel, removes only Playwright automation flags (`--disable-blink-features=AutomationControlled` + `--enable-automation` stripped), fixed viewport 1366x900, UA `Chrome/120` on macOS with `Accept-Language: en-US,en;q=0.9` and locale `en-US`, no stealth/CDP patch, leaves client hints and userAgentData native, no plugin/runtime stubs or devtools masking. Reserve this for Turnstile/WAF clearance runs.
- `pixelscan`: headful, `stealth=True`, Chrome channel, CDP transport patch on; leaves viewport/language/chrome.runtime/plugins unspoofed; deletes `navigator.webdriver`; sets devtools sentinels to “closed”; uses native UA/CH/TZ unless overridden.
- Validation (live, optional): `PIXELSCAN_RUN=1 PIXELSCAN_PRESET=pixelscan venv/bin/python -m pytest -s tests/test_pixelscan_botcheck.py` (hits pixelscan.net/bot-check).

### Integration with Claude Desktop

Add to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "playwright": {
      "command": "playwright-mcp",
      "args": ["stdio"]
    }
  }
}
```

### Testing with MCP Inspector

```bash
# Install and run MCP inspector
uv run mcp dev src/playwright_mcp/server.py
```

### Response Budgeting & Artifacts

High-volume tools (`evaluate`, `get_accessibility_snapshot`, `get_network_requests`, `get_network_responses`, `get_html`, etc.) honor a shared response budget. When the serialized payload would exceed `--max-response-chars`:

- The inline result is trimmed to fit the budget and marked with `truncated: true`.
- A compact `preview` (default 400 chars) snapshots what was retained.
- The full payload is written to the artifact directory (default `tmp/playwright_mcp`) and returned as `overflow_path` plus the original size in `overflow_characters`.
- Use the `read_artifact` tool (or open the file locally) to retrieve the saved payload. Artifacts that are binary fall back to base64 when read.
- Artifacts are auto-pruned—by default anything older than two hours is deleted and the directory is capped at 200 files (`--artifact-max-age-seconds`, `--artifact-max-files`).

Binary-heavy tools (`screenshot`, `pdf`, `get_response_body` for non-text payloads) now default to artifact responses. They return metadata (size, hash, preview) plus `artifact_path`. Pass `inline=True` when you truly need the full base64 inline.

When you need more than the inline preview, use `read_artifact_chunk` to stream binary/text artifacts in manageable slices (default chunk size is configurable via `--artifact-chunk-size`).

Tweak the caps per run with `--max-response-chars`, `--preview-chars`, and `--artifact-dir` or override per-call limits where a tool exposes them.

## API Reference

### Tools

#### Navigation & Page Control
- `navigate(url: str)` - Navigate to a URL
- `reload()` - Reload the current page
- `go_back()` - Go back in history
- `go_forward()` - Go forward in history
- `get_current_url()` - Get current page URL with parsed components and query parameters
- `wait_for_url(url_pattern: str, timeout: int)` - Wait for URL to match pattern
- `wait_for_load_state(state: str, timeout: int)` - Wait for page load states (domcontentloaded, load, networkidle)
- `set_viewport_size(width: int, height: int)` - Set viewport dimensions

#### Multi-Page Management (Tabs/Windows)
- `list_pages()` - List all open browser pages/tabs with IDs, URLs, and titles
- `switch_page(page_id: str)` - Switch to a different page/tab by its ID
- `close_page(page_id: str)` - Close a specific page/tab (cannot close last one)
- `wait_for_popup(timeout: int)` - Wait for and capture a new popup/tab
- `switch_to_latest_page()` - Switch to the most recently opened page

#### Element Interaction
- `click(selector: str)` - Click an element
- `type_text(selector: str, text: str)` - Type text into an element
- `fill(selector: str, value: str)` - Fill an input field
- `clear_text(selector: str)` - Clear input field text
- `select_option(selector: str, value: str)` - Select an option
- `hover(selector: str)` - Hover over an element
- `scroll(selector: str, x: int, y: int)` - Scroll element
- `press_key(key: str)` - Press keyboard key

#### Form Handling
- `check_checkbox(selector: str)` - Check a checkbox
- `uncheck_checkbox(selector: str)` - Uncheck a checkbox
- `upload_file(selector: str, file_path: str)` - Upload file to input

#### Element Discovery & Validation
- `query_selector(selector: str, max_text_length: int | None = None)` - Query for single element (optional per-call text cap override)
- `query_selector_all(selector: str, max_elements: int | None = None, max_text_length: int | None = None)` - Query for all matching elements (optional per-call overrides)
- `query_selector_meta(selector: str, preview_length: int = 200, max_elements: int | None = None)` - Quick metadata preview (tag, role, short text, key attributes)
- Element query tools obey global caps, accept per-call overrides, and return `truncated` / `returned_count` metadata when results are clipped.
- `is_visible(selector: str)` - Check if element is visible
- `is_enabled(selector: str)` - Check if element is enabled
- `wait_for_element(selector: str, timeout: int)` - Wait for element to appear
- `get_element_bounding_box(selector: str)` - Get element position and size
- `get_element_attributes(selector: str)` - Get all element attributes
- `get_computed_style(selector: str, property: str)` - Get CSS computed style

#### Script Evaluation & Diagnostics
- `evaluate(script: str)` - Execute JavaScript in page context (results respect the shared response budget and emit overflow metadata as needed)

#### Network Monitoring & Overflow Retrieval
- `get_network_requests(url_pattern: str | None = None)` - Inspect captured requests; large result sets spill to artifacts with previews
- `get_network_responses(url_pattern: str | None = None)` - Inspect captured responses with the same budgeting behaviour
- `read_artifact(path: str)` - Load the first chunk of an overflow artifact referenced in a previous response (returns text or base64 content)
- `read_artifact_chunk(path: str, offset: int = 0, limit: int | None = None)` - Stream additional artifact data in bounded chunks
- `get_response_body(url_pattern: str)` - Fetch the latest matching response body; text is budgeted inline, binary payloads are saved to artifacts automatically

#### Content & Snapshots
- `get_html()` - Get page HTML
- `get_accessibility_snapshot(interesting_only: bool = True, root_selector: str | None = None, max_nodes: int | None = None)` - Get accessibility tree with optional filters and per-call node caps
- Accessibility snapshots are pruned to the configured node budget, accept per-call overrides, and include `truncated`, `max_nodes`, and `node_count` metadata.
- When Playwright lacks `page.accessibility`, the tool falls back to `locator.aria_snapshot()` and returns `format: "aria_snapshot"` (node counts are estimated).
- `screenshot(selector: str | None = None, full_page: bool = False, inline: bool = False)` - Capture page or element; stores the PNG as an artifact with metadata previews by default
- `pdf(inline: bool = False)` - Generate a PDF and return artifact metadata unless inline mode is explicitly requested

#### JavaScript & Debugging
- `evaluate(script: str)` - Execute JavaScript in page context
- `wait_for_network_idle(timeout: int)` - Wait for network activity to settle
- `get_page_errors()` - Get JavaScript errors from page
- `get_console_logs()` - Get console output from page

#### Network Monitoring & Interception
- `get_network_requests(url_pattern: str)` - Retrieve captured network requests with filtering
- `get_network_responses(url_pattern: str)` - Retrieve captured network responses with filtering
- `clear_network_logs()` - Clear all captured network request/response logs
- `intercept_route(url_pattern: str, action: str, ...)` - Intercept and handle network requests
- `unroute_all()` - Remove all route interceptors
- `wait_for_response(url_pattern: str, timeout: int)` - Wait for specific network responses
- `get_response_body(url_pattern: str)` - Extract response body content from network calls

#### Cookie Management
- `get_cookies(urls: List[str])` - Retrieve browser cookies with optional URL filtering
- `add_cookies(cookies: List[Dict])` - Add cookies to browser context
- `clear_cookies(name: str, domain: str)` - Clear cookies with optional filtering

#### Storage Management
- `get_local_storage(origin: str)` - Access localStorage data
- `set_local_storage(key: str, value: str)` - Set localStorage items
- `get_session_storage()` - Access sessionStorage data
- `set_session_storage(key: str, value: str)` - Set sessionStorage items
- `clear_storage(storage_type: str)` - Clear localStorage and/or sessionStorage

#### Request Headers & Identity
- `set_extra_headers(headers: Dict)` - Add custom HTTP headers to all requests
- `set_user_agent(user_agent: str)` - Change browser User-Agent string

## Examples

### Handling Multiple Pages/Tabs

The server automatically tracks all browser pages/tabs:

```python
# Example: Clicking a link that opens in a new tab
1. navigate("https://example.com")
2. click("a[target='_blank']")  # Opens new tab
3. list_pages()  # Shows all open tabs with IDs
4. switch_to_latest_page()  # Switch to the new tab
5. get_current_url()  # Get URL of new tab
6. switch_page(original_page_id)  # Switch back

# Example: Handling JavaScript popups
1. evaluate("window.open('https://example.com', '_blank')")
2. wait_for_popup(timeout=5000)  # Wait for and capture popup
3. list_pages()  # See all pages including popup
4. close_page(popup_id)  # Close the popup
```

All existing tools automatically work on the currently active page. When you switch pages, subsequent operations apply to the new active page.

### Configuration

The server accepts the following configuration options:

- `--headed` / `--headless` - Run browser in headed or headless mode
- `--browser` - Browser type (chromium, firefox, webkit)
- `--channel` - Browser channel (chrome, chrome-beta, msedge, etc.) for real browsers
- `--user-data-dir` - Path to browser profile directory for persistent context
- `--port` - Port for HTTP transport
- `--timeout` - Default timeout for operations (ms)
- `--max-elements` - Maximum number of DOM nodes returned by query tools (default: 20)
- `--max-element-text-length` - Maximum characters returned for element text and attribute values (default: 2000)
- `--max-accessibility-nodes` - Maximum accessibility tree nodes returned by `get_accessibility_snapshot` (default: 500)
- `--max-response-chars` - Maximum serialized characters returned inline before results spill to artifact files (default: 4000)
- `--preview-chars` - Maximum characters included in inline previews when truncation occurs (default: 400)
- `--artifact-dir` - Directory where overflow artifacts are written (default: `./tmp/playwright_mcp`)
- `--artifact-max-age-seconds` - Maximum age before artifacts are purged (default: 7200 seconds)
- `--artifact-max-files` - Maximum number of artifact files kept in the directory (default: 200)
- `--artifact-chunk-size` - Default number of bytes returned per `read_artifact_chunk` call (default: 4096)

All caps accept `0` or negative values to disable truncation entirely. When truncation occurs, tool responses include `truncated` flags and metadata so clients can optionally re-issue a narrower request.

### Real Chrome vs Bundled Chromium

By default, Playwright uses bundled Chromium. For web scraping that requires real Chrome features:

**Use `--channel chrome`** to use your installed Google Chrome:
- Access to all Chrome features and codecs
- Better compatibility with some websites
- Chrome-specific behaviors

**Use `--user-data-dir`** to access your real profile:
- All your cookies and login sessions
- Browser extensions (AdBlock, etc.)
- Browsing history and autofill data
- Bookmarks and saved passwords

**Example for macOS:**
```bash
playwright-mcp stdio --channel chrome --user-data-dir "/Users/$(whoami)/Library/Application Support/Google/Chrome"
```

**Example for Linux:**
```bash  
playwright-mcp stdio --channel chrome --user-data-dir "/home/$(whoami)/.config/google-chrome"
```

## Development

```bash
# Clone the repository
git clone <repo-url>
cd playwright-mcp

# Install dependencies
uv sync --dev

# Run tests
uv run pytest

# Format code
uv run black src/
uv run ruff check src/

# Type check
uv run mypy src/
```

## License

MIT