Playwriter - For browser automation MCP

Let your agents control your own Chrome, via CLI or MCP. Your logins, extensions, cookies — already there.


Other browser MCPs spawn a fresh Chrome — no logins, no extensions, instantly flagged by bot detectors, double the memory. Playwriter connects to **your running browser** instead. One Chrome extension, full Playwright API, everything you're already logged into. ## Installation 1. [**Install Extension**](https://chromewebstore.google.com/detail/playwriter-mcp/jfeammnjpkecdekppnclgkkffahnhfhe) from Chrome Web Store 2. Click extension icon on a tab → turns green when connected 3. Install the CLI and start automating the browser: ```bash npm i -g playwriter playwriter -s 1 -e 'await page.goto("https://example.com")' ``` 4. Install the skill so your agent knows how to use Playwriter: ```bash npx -y skills add remorses/playwriter ``` ## Quick Start ```bash playwriter browser start # starts Chrome for Testing/Chromium with bundled Playwriter extension playwriter session new # creates stateful sandbox, outputs session id (e.g. 1) playwriter -s 1 -e 'await page.goto("https://example.com")' playwriter -s 1 -e 'console.log(await snapshot({ page }))' playwriter -s 1 -e 'await page.locator("aria-ref=e5").click()' ``` > **Tip:** Always use single quotes for `-e` to prevent bash from interpreting `$`, backticks, and `\` in your JS code. Use double quotes for strings inside the JS. ## CLI Usage Each session has **isolated state**. Browser tabs are **shared** across sessions. ```bash # Browser management playwriter browser start # auto-finds Chrome for Testing or Chromium, with recording flags enabled playwriter browser start /path/to/browser-binary # Session management playwriter session new # creates stateful sandbox, outputs id (e.g. 1) playwriter session list # show sessions + state keys playwriter session reset # fix connection issues # Execute (always use -s) playwriter -s 1 -e 'await page.goto("https://example.com")' playwriter -s 1 -e 'await page.click("button")' playwriter -s 1 -e 'console.log(await page.title())' ``` Create your own page to avoid interference from other agents: ```bash playwriter -s 1 -e 'state.myPage = await context.newPage(); await state.myPage.goto("https://example.com")' ``` Multiline: ```bash playwriter -s 1 -e $' const title = await page.title(); console.log({ title, url: page.url() }); ' ``` ## Examples Variables in scope: `page`, `context`, `state` (persists between calls), `require`, and Node.js globals. **Persist data in state:** ```bash playwriter -e "state.users = await page.$$eval('.user', els => els.map(e => e.textContent))" playwriter -e "console.log(state.users)" ``` **Intercept network requests:** ```bash playwriter -e "state.requests = []; page.on('response', r => { if (r.url().includes('/api/')) state.requests.push(r.url()) })" playwriter -e "await Promise.all([page.waitForResponse(r => r.url().includes('/api/')), page.click('button')])" playwriter -e "console.log(state.requests)" ``` **Set breakpoints and debug:** ```bash playwriter -e "state.cdp = await getCDPSession({ page }); state.dbg = createDebugger({ cdp: state.cdp }); await state.dbg.enable()" playwriter -e "state.scripts = await state.dbg.listScripts({ search: 'app' }); console.log(state.scripts.map(s => s.url))" playwriter -e "await state.dbg.setBreakpoint({ file: state.scripts[0].url, line: 42 })" ``` **Live edit page code:** ```bash playwriter -e "state.cdp = await getCDPSession({ page }); state.editor = createEditor({ cdp: state.cdp }); await state.editor.enable()" playwriter -e "await state.editor.edit({ url: 'https://example.com/app.js', oldString: 'const DEBUG = false', newString: 'const DEBUG = true' })" ``` **Screenshot with labels:** ```bash playwriter -e "await screenshotWithAccessibilityLabels({ page })" ``` ## MCP Setup Using the CLI with the skill (step 4 above) is the recommended approach. For direct MCP server configuration, see [MCP.md](./MCP.md). ## Visual Labels Vimium-style labels for AI agents to identify elements: ```javascript await screenshotWithAccessibilityLabels({ page }) // Returns screenshot + accessibility snapshot with aria-ref selectors await page.locator('aria-ref=e5').click() ``` Color-coded: yellow=links, orange=buttons, coral=inputs, pink=checkboxes, peach=sliders, salmon=menus, amber=tabs. ## Comparison ### vs Playwright MCP | | Playwright MCP | Playwriter | | ------------- | ----------------- | --------------------------------- | | Browser | Spawns new Chrome | **Uses your Chrome** | | Extensions | None | Your existing ones | | Login state | Fresh | Already logged in | | Bot detection | Always detected | Can bypass (disconnect extension) | | Collaboration | Separate window | Same browser as user | > **Note:** Playwriter video recording is **100x more efficient than Playwright video recording**, which sends **base64 images for every frame**. | | Playwright CLI | Playwriter | | --------------- | ------------------- | ----------------------------- | | Browser | Spawns new browser | **Uses your Chrome** | | Login state | Fresh | Already logged in | | Extensions | None | Your existing ones | | Captchas | Always blocked | Bypass (disconnect extension) | | Collaboration | Separate window | Same browser as user | | Capabilities | Limited command set | Anything Playwright can do | | Raw CDP access | No | Yes | | Video recording | File-based tracing | Native tab capture (30–60fps) | ### vs BrowserMCP | | BrowserMCP | Playwriter | | ------------- | ------------------- | ------------------------ | | Tools | 12+ dedicated tools | 1 `execute` tool | | API | Limited actions | Full Playwright | | Context usage | High (tool schemas) | Low | | LLM knowledge | Must learn tools | Already knows Playwright | ### vs Antigravity (Jetski) | | Jetski | Playwriter | | -------- | ---------------------------- | ---------------- | | Tools | 17+ tools | 1 tool | | Subagent | Spawns for each browser task | Direct execution | | Latency | High (agent overhead) | Low | ### vs Claude Browser Extension | | Claude Extension | Playwriter | | -------------------- | -------------------- | ----------------------- | | Agent support | Claude only | Any MCP client | | Windows WSL | No | Yes | | Context method | Screenshots (100KB+) | A11y snapshots (5-20KB) | | Playwright API | No | Full | | Debugger/breakpoints | No | Yes | | Live code editing | No | Yes | | Network interception | Limited | Full | | Raw CDP access | No | Yes | ### vs Built-in Chrome CDP (`--remote-debugging-port`) | | Built-in CDP | Playwriter | | --------------------- | ------------------------------------- | ---------------------------- | | Setup | Restart Chrome with special flags | Click extension icon | | Confirmation dialog | Shows automation infobar agents can't dismiss | No blocking dialog | | Autonomous agents | Interrupted by debug banners | Fully autonomous | | User disruption | Banners appear mid-workflow | Silent — no interruption | | Existing session | Must relaunch Chrome (lose state) | Uses your running browser | > Chrome's `--remote-debugging-port` flag shows a persistent "controlled by automated software" banner that agents cannot dismiss. It pops up in the middle of your workflow whenever you're using the browser. Playwriter runs silently — agents work autonomously without any confirmation dialogs, so you're never interrupted. ## Architecture ``` +---------------------+ +-------------------+ +-----------------+ | BROWSER | | LOCALHOST | | MCP CLIENT | | | | | | | | +---------------+ | | WebSocket Server | | +-----------+ | | | Extension |<---------> :19988 | | | AI Agent | | | +-------+-------+ | WS | | | +-----------+ | | | | | /extension | | | | | chrome.debugger | | | | | v | | v | | v | | +-----------+ | | +---------------+ | | /cdp/:id <--------------> | execute | | | | Tab 1 (green) | | +-------------------+ WS | +-----------+ | | | Tab 2 (green) | | | | | | | Tab 3 (gray) | | Tab 3 not controlled | Playwright API | +---------------------+ (no extension click) +-----------------+ ``` ## Remote Access Control Chrome on a remote machine over the internet using [traforo](https://traforo.dev) tunnels: **On host:** ```bash npx -y traforo -p 19988 -t my-machine -- npx -y playwriter serve --token ``` **From remote:** ```bash export PLAYWRITER_HOST=https://my-machine-tunnel.traforo.dev export PLAYWRITER_TOKEN= playwriter -s 1 -e 'await page.goto("https://example.com")' ``` Also works on a LAN without traforo (`PLAYWRITER_HOST=192.168.1.10`). Full guide with use cases (remote Mac mini, user support, multi-machine control): [docs/remote-access.md](./docs/remote-access.md) ## Security - **Local only**: WebSocket server on `localhost:19988` - **Origin validation**: Only our extension IDs allowed (browsers can't spoof Origin) - **Explicit consent**: Only tabs where you clicked the extension icon - **Visible automation**: Chrome shows automation banner on controlled tabs - **No remote access**: Malicious websites cannot connect ## Playwright API Connect programmatically (without CLI): ```typescript import { chromium } from 'playwright-core' import { startPlayWriterCDPRelayServer, getCdpUrl } from 'playwriter' const server = await startPlayWriterCDPRelayServer() const browser = await chromium.connectOverCDP(getCdpUrl()) const page = browser.contexts()[0].pages()[0] await page.goto('https://example.com') await page.screenshot({ path: 'screenshot.png' }) // Don't call browser.close() - it closes the user's Chrome server.close() ``` Or connect to a running server: ```bash npx -y playwriter serve --host 127.0.0.1 ``` ```typescript const browser = await chromium.connectOverCDP('http://127.0.0.1:19988') ``` ## Troubleshooting View relay server logs to debug issues: ```bash playwriter logfile # prints the log file path # typically: ~/.playwriter/relay-server.log ``` The relay log contains extension, MCP and WebSocket server logs. A separate CDP JSONL log is also created alongside it (see `playwriter logfile`). Both are recreated on each server start. Example: summarize CDP traffic counts by direction + method: ```bash jq -r '.direction + "\t" + (.message.method // "response")' ~/.playwriter/cdp.jsonl | uniq -c ``` ## Support If Playwriter is useful to you, consider [sponsoring the project](https://github.com/sponsors/remorses). ## Known Issues - If all pages return `about:blank`, restart Chrome (Chrome bug in `chrome.debugger` API) - Browser may switch to light mode on connect ([Playwright issue](https://github.com/microsoft/playwright/issues/37627))