Claude Marketplaces
SkillsMCP serversMarketplacesDigestTools
Submit

This week in Claude

Every Monday: Claude Code, Agent SDK, MCP, and the Anthropic platform moves worth your time.

Skills by category
Frontend DevelopmentBackend & APIsTesting & QASecurityDevOps & CI/CDGit & Pull RequestsDocumentationCode Review & QualityAI & Agent BuildingSkill Development
MCP servers by category
Sales & MarketingWeb & Browser AutomationDatabasesAI & LLM ToolsCloud & InfrastructureCommunication & MessagingDeveloper ToolsDesign & CreativeDocuments & KnowledgeSearch & Web Crawling
Marketplaces by category
AI Agents & OrchestrationLLM IntegrationDevelopment ToolsFrontend & UIBackend & APIsDatabasesTesting & Code QualityDevOps & CloudSecurity & ComplianceGit & Version Control
Claude Marketplaces

An independent directory of skills, plugins and MCP servers for Claude Code. Not affiliated with Anthropic.

Browse

  • Skills
  • MCP servers
  • Marketplaces
  • Weekly digest
  • Skill index
  • MCP index
  • Marketplace index

Site

  • About
  • Tools
  • Submit
  • Feedback
  • Privacy
  • llms.txt
  • Plugins reference

Maker

  • Snug
  • Agent Caffeine
  • knobs
  • Wax
  • Vectoys
  • mertduzgun.com
Crafted at
MCP servers/Web & Browser Automation
remorses avatar

Playwriter

remorses/playwriter

Playwriter connects AI agents to a user's existing Chrome browser via a browser extension and CLI tool, enabling browser automation through the full Playwright API while preserving existing logins, extensions, and cookies. It provides tools for session management, page navigation, element interaction, and state persistence across multiple automation commands executed against the same browser instance. This solves the problem of fresh Chrome instances being memory-intensive, lacking authentication context, and easily detected by bot protection systems.

GitHub stars
3.5K
last updated
Dec 15, 2025
View on GitHubSave

Sponsors

SnugThe clipboard manager you and your agent can use, safelyAgent CaffeineClose your lid, come back to finished workWaxBlock anything with supervision, without erasing your iPhoneOpen StepsSponsoredFree and open-source skills that make AI coding agents easier to understand, verify, and control.

Readme


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 from Chrome Web Store

  2. Click extension icon on a tab → turns green when connected

  3. Install the CLI and start automating the browser:

    npm i -g playwriter
    playwriter -s 1 -e 'state.page = await context.newPage(); await state.page.goto("https://example.com")'
    
  4. Install the skill so your agent knows how to use Playwriter:

    npx -y skills add https://playwriter.dev -g -y
    

Quick Start

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 'state.page = await context.newPage(); await state.page.goto("https://example.com")'
playwriter -s 1 -e 'console.log(await snapshot({ page: state.page }))'
playwriter -s 1 -e 'await state.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.

# Browser management
playwriter browser start             # auto-finds Chrome for Testing or Chromium
playwriter browser start /path/to/browser-binary

# Session management
playwriter session new              # creates stateful sandbox, outputs id (e.g. 1)
playwriter session new --tab-group agent1 --tab-group-color blue
playwriter session update 1 --tab-group research
playwriter session list             # show sessions + state keys + group
playwriter session reset <id>       # fix connection issues

# Execute (always use -s)
playwriter -s 1 -e 'state.page = await context.newPage(); await state.page.goto("https://example.com")'
playwriter -s 1 -e 'await state.page.click("button")'
playwriter -s 1 -e 'console.log(await state.page.title())'

There is no default page. Create your own tab and store it in state. To use a tab the user already opened, find it by URL:

playwriter -s 1 -e 'state.page = context.pages().findLast((p) => p.url().includes("github.com"))'

Tab groups

Local extension sessions use a Chrome tab group named playwriter by default. Remote-control sessions put the tabs they open into remote. Use the shortest clear single-word name with no spaces, such as docs, shop, test, or scrape.

# Park a long scrape in its own group. The user can Move group to new window
# (or another screen). New tabs from this session follow that group.
playwriter session new --tab-group scrape --tab-group-color grey

# Split concurrent agents so many open tabs stay readable
playwriter session new --tab-group agent1 --tab-group-color blue
playwriter session new --tab-group agent2 --tab-group-color pink

# Name a group the user can collapse when they don't care about it
playwriter session new --tab-group done --tab-group-color grey

# Rename or recolor later
playwriter session update 1 --tab-group research
playwriter session update 1 --tab-group-color red

# Remote tabs support the same title and color options
playwriter session new --remote <id> --tab-group support
playwriter session update 1 --tab-group review --tab-group-color cyan

--tab-group-color accepts: grey, blue, red, yellow, green, pink, purple, cyan, orange. Without it, color is derived from the name. The default local playwriter group stays green.

Node programs should use connectViaExtension() instead of posting /cli/session/new themselves:

import { connectViaExtension } from 'playwriter'

await using connection = await connectViaExtension({
  tabGroup: 'email-check',
  tabGroupColor: 'grey',
})
// the page closes its tab when the scope ends, also on throw
await using page = await connection.browser.contexts()[0].newPage()
await page.goto('https://example.com')

tabGroupColor is typed as Chrome's tab group colors. await using calls close() at scope end: it disconnects CDP and deletes the session. It does not close tabs: close every tab your script opens with await using page or page.close(). Pass closeTabsOnEnd: true to make close() also close the tabs this connection opened (never other sessions' tabs). Without await using, call await connection.close() in finally.

A tab you do not close stays open, so a later process can reuse it:

// process A
await using a = await connectViaExtension({ tabGroup: 'job' })
await (await a.browser.contexts()[0].newPage()).goto('https://example.com')

// process B, later
await using b = await connectViaExtension({ tabGroup: 'job' })
const page = b.browser.contexts()[0].pages().findLast((p) => p.url() === 'https://example.com/')

Match tabs by their exact url: context.pages() also lists tabs other sessions opened.

Several processes can share one tab for fetch calls inside a signed-in site, and the last one closes it. See Sharing one tab between processes.

If the extension runs in more than one browser or profile, pass selectBrowser. It gets every connected browser and returns the one to use:

await using connection = await connectViaExtension({
  selectBrowser: (browsers) => {
    // [{ id, browser: 'Brave', email: 'me@work.com', activeTabs, playwriterVersion }]
    return browsers.find((b) => b.email === 'me@work.com')
  },
})

id is stable, so you can save it and match on it later. listBrowsers() returns the same list without connecting.

Multiline:

playwriter -s 1 -e $'
const title = await state.page.title();
console.log({ title, url: state.page.url() });
'

Examples

Variables in scope: context, state (persists between calls), cloud (local CLI sessions), require, importModule, native import(), and Node.js globals. Relative imports resolve from the session working directory. There is no default page; examples below assume state.page was set as shown above.

Persist data in state:

playwriter -e "state.users = await state.page.$$eval('.user', els => els.map(e => e.textContent))"
playwriter -e "console.log(state.users)"

Intercept network requests:

playwriter -e "state.requests = []; state.page.on('response', r => { if (r.url().includes('/api/')) state.requests.push(r.url()) })"
playwriter -e "await Promise.all([state.page.waitForResponse(r => r.url().includes('/api/')), state.page.click('button')])"
playwriter -e "console.log(state.requests)"

Set breakpoints and debug:

playwriter -e "state.cdp = await getCDPSession({ page: state.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:

playwriter -e "state.cdp = await getCDPSession({ page: state.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:

playwriter -e "await screenshotWithAccessibilityLabels({ page: state.page })"

Live stream a tab to X Live / Twitch (RTMP, survives CLI exit):

playwriter -s 1 -e "state.page = await context.newPage(); await state.page.goto('https://example.com')"
playwriter stream start -s 1 --rtmp rtmp://va.pscp.tv:80/x/<stream-key>
playwriter stream status -s 1
playwriter stream stop -s 1

MCP Setup

Using the CLI with the skill (step 4 above) is the recommended approach. For direct MCP server configuration, see MCP.md.

Visual Labels

Vimium-style labels for AI agents to identify elements:

await screenshotWithAccessibilityLabels({ page: state.page })
// Returns screenshot + accessibility snapshot with aria-ref selectors
await state.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

PlaywriterPlaywright MCP
BrowserUses your ChromeSeparate managed profile by default
ExtensionsYour existing onesNone by default
Login stateAlready logged inPersistent, but a separate profile
Attach to your ChromeCore design--extension mode
Bot handlingReal browser (disconnect to solve)Managed automation profile
Native video / raw CDPYesTrace-based / not exposed

Note: Playwriter video recording is 100x more efficient than Playwright video recording, which sends base64 images for every frame.

PlaywriterPlaywright CLI
BrowserUses your ChromeNew browser by default
Login stateAlready logged inPersistent profile, separate
ExtensionsYour existing onesNone by default
CaptchasDisconnect extension to solveManaged automation profile
Programmable JSexecute with persistent staterun-code (no cross-call state)
Raw CDP accessFirst-classNot exposed
Video recordingCDP screencast + ffmpeg, page audioTrace / screencast based

vs BrowserMCP

PlaywriterBrowserMCP
Tools1 execute tool12+ dedicated tools
APIFull PlaywrightLimited actions
Context usageLowHigh (tool schemas)
LLM knowledgeAlready knows PlaywrightMust learn tools

vs agent-browser

Playwriteragent-browser
BrowserUses your ChromeFresh Chrome for Testing
API surface1 execute + full Playwright50+ CLI commands, one per action
Actions per turnReal JS (loops, conditions)batch of command strings
Reusable logicImport a .js functionRe-run bash sequences
Skill recorderYesNo
Cloud browsersBuilt-in stealth + proxyPlugin only
Remote control tabYes (Devin, cloud bots)No

vs Antigravity (Jetski)

PlaywriterJetski
Tools1 tool17+ tools
SubagentDirect executionSpawns for each browser task
LatencyLowHigh (agent overhead)

vs Claude Browser Extension

PlaywriterClaude Extension
Agent supportAny MCP clientClaude only
Windows WSLYesNo
Context methodA11y snapshots (5-20KB)Screenshots (100KB+)
Playwright APIFullNo
Debugger/breakpointsYesNo
Live code editingYesNo
Network interceptionFullLimited
Raw CDP accessYesNo

vs Built-in Chrome CDP (--remote-debugging-port)

PlaywriterBuilt-in CDP
SetupClick extension iconRelaunch Chrome with special flags
Your real profileYesBlocked on default profile since Chrome 136
Permission promptNone"Allow remote debugging?" dialog agents can't click
Autonomous agentsFully autonomousBlocked by dialog / throwaway profile
Existing sessionUses your running browserMust relaunch Chrome (lose state)

Chrome's --remote-debugging-port is ignored on your default profile since Chrome 136, so you must use a throwaway --user-data-dir with none of your logins. Connecting an external CDP client also shows an "Allow remote debugging?" dialog an agent cannot click. Playwriter uses an in-Chrome extension instead: no dialog, no flags, your real profile.

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 Control (share your browser with a remote agent)

Let a remote agent (Devin, a cloud bot, a friend's CLI agent) drive your own browser. No playwriter install needed on your machine, only the extension. The agent sees every Playwriter tab and can open new ones, like a local agent.

  1. Click the light-blue Remote control cloud button and confirm that the agent may read and control your browser
  2. A prompt containing a secret tunnel URL is copied to your clipboard — paste it to the agent
  3. The agent runs playwriter session new --remote <id> on its machine
  4. Open Remote ON in any Playwriter tab and click Stop sharing anytime to revoke. The URL dies instantly. Tabs the agent opened are detached and left open.

Opening that same link in any browser shows a live, clickable view of the tab where sharing started, so you can share with a person instead of an agent. The viewer page receives no tunnel id in its initial HTTP request because the id starts in the URL fragment. Its JavaScript then uses the id to connect to the tunnel path (/tunnel/{id}/extension), keeping the id out of DNS and TLS SNI.

YOUR MACHINE (extension only)                        AGENT MACHINE (any box with npx)
┌───────────────────────────┐                       ┌────────────────────────────────┐
│ Chrome + Extension        │   Cloudflare tunnel   │ playwriter CLI + local relay   │
│  Playwriter tabs ◄────────┼───◄ playwriter.dev ◄──┼────── session new --remote     │
└───────────────────────────┘   /remote-control#id  └────────────────────────────────┘

Remote control is not a sandbox. The agent gets the same access as a local Playwriter agent, so share the id only with a person or agent you fully trust. A short denylist blocks only whole-profile cookie APIs and cache clears, so an agent cannot wipe every login by accident. The id contains 128 bits of randomness and is never reusable after revocation.

Sharing also ends when you close or disconnect the tab where you started it.

Use case: you are logged into a website and want an agent to do work in your authenticated session without giving it your password.

Remote Access

Control Chrome on a remote machine over the internet using traforo tunnels:

On host:

npx -y traforo -p 19988 -t my-machine -- npx -y playwriter serve --token <secret>

From remote:

export PLAYWRITER_HOST=https://my-machine-tunnel.traforo.dev
export PLAYWRITER_TOKEN=<secret>
playwriter -s 1 -e 'state.page = await context.newPage(); await state.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

Security

  • Local by default: The normal WebSocket relay stays on localhost:19988. Traffic leaves your machine only when you enable Remote control or configure remote access.
  • Origin validation: Only our extension IDs allowed (browsers can't spoof Origin)
  • Controlled tab scope: Tabs are controlled after an extension click. Playwriter never opens a tab on its own. New tabs are created only when code calls context.newPage().
  • Visible automation: Chrome shows automation banner on controlled tabs
  • No remote access: Malicious websites cannot connect

Telemetry

The local relay sends anonymous usage stats to Strada: event names, a random install id, Playwriter version, OS, session kind, and hourly execute counts. It never sends code, URLs, page content, emails, or hostnames.

Opt out with an env var, then restart the relay:

export PLAYWRITER_TELEMETRY=0   # or DO_NOT_TRACK=1
playwriter serve restart

Playwright API

Connect programmatically (without CLI):

import { chromium } from 'playwright-core'
import { startPlayWriterCDPRelayServer, getCdpUrl } from 'playwriter'

const server = await startPlayWriterCDPRelayServer()
const browser = await chromium.connectOverCDP(getCdpUrl())
const page = await browser.contexts()[0].newPage()

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:

npx -y playwriter serve --host 127.0.0.1
const browser = await chromium.connectOverCDP('http://127.0.0.1:19988')

Troubleshooting

View relay server logs to debug issues:

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:

jq -r '.direction + "\t" + (.message.method // "response")' ~/.playwriter/cdp.jsonl | uniq -c

Support

If Playwriter is useful to you, consider sponsoring the project.

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)

Details

Repository
github.com/remorses/playwriter
Categories
Web & Browser Automation, Automation & Workflows

Related Web & Browser Automation MCP servers

View all
1hyperbrowserai avatarHyperbrowserhyperbrowserai/mcpProvides data scraping, structured data extraction, and web crawling through MCP clients with browser-use agents.761stars
2harimxchoi avatarGoogle Surf Mcpharimxchoi/google-surf-mcpGoogle search via Playwright with a warm Chrome profile. No API key, no proxies.227stars
3achiya-automation avatarSafari Mcpachiya-automation/safari-mcpNative Safari browser automation for AI agents — 80 tools, zero Chrome overhead.111stars
4leonting1010 avatarTapleonting1010/tapBrowser MCP for logged-in tasks. Uses your Chrome — credentials stay local. Zero-token replay.8stars
5swimmwatch avatarCloakBrowser MCPswimmwatch/cloakbrowser-mcpPlaywright MCP-compatible browser automation bridge for CloakBrowser Chromium.7stars
6aishwaryshrivastav avatarVibe Test — Browser Testing Agentaishwaryshrivastav/vibe-testingCode-aware browser testing agent — 13 Playwright tools for AI code editors via MCP4stars