ckarnell

pw-pool

Community ckarnell
Updated

One browser per agent session for the Playwright MCP

pw-pool

One browser per agent session for the Playwright MCP.

@playwright/mcp assumes one server and one browser. If you run two agent sessions on one machine andthey either fail on the profile lock or, with a shared browser, work in the same tab space andnavigate each other's pages. pw-pool gives every session its own Chrome and remembers which oneis whose.

Profiles

Chrome keeps everything about a "person" in a profile directory (--user-data-dir): cookies,local storage, saved passwords, open tabs. That is what keeps you signed in between runs. Only oneChrome can use a profile at a time. pw-pool creates one profile per session, keeps it between runs,and can seed a new one from a template — a copy of the login files of a profile you are alreadysigned in with — so a new session starts signed in without sharing a browser with anyone.

Install

Needs Node 22+ and macOS or Linux.

git clone https://github.com/ckarnell/pw-pool && cd pw-pool
npm install            # pins @playwright/mcp and patches it
node bin/pw-pool.js install    # checks the setup; offers to download Chrome for Testing if missing

Or globally, which puts pw-mcp and pw-pool on your PATH: npm install -g github:ckarnell/pw-pool.

Then use pw-mcp as the Playwright MCP command. Claude Code (~/.claude.json or a project.mcp.json):

"playwright": { "type": "stdio", "command": "pw-mcp" }

(or "command": "node", "args": ["/path/to/pw-pool/bin/pw-mcp.js"] for a clone that is not on PATH).

Other MCP flags (--caps, --output-dir, …) can be added to args; they pass through.--headless is applied to the pool's launch. --cdp-endpoint, --user-data-dir, --isolated and--browser are dropped with a warning, because the pool chooses the browser.

By default browsers are Playwright's Chrome for Testing. To use the Chrome already installed on themachine: pw-pool config set channel '"chrome"' (also chrome-beta, chrome-canary, msedge), orconfig.chrome for an explicit path.

Optional, to start every session signed in:

pw-pool template save main --from ~/path/to/a/signed-in/user-data-dir
pw-pool config set defaultTemplate '"main"'

Switching while sessions are open

You can change the MCP config at any time; nothing running is affected. An MCP server is startedonce per session, so sessions that are already open keep their old server and browser until theyrestart. Sessions that start (or restart, for example claude --resume) after the change usepw-pool. Order that works:

  1. Save a template from the browser you use today and set it as the default (above), so the newbrowsers are signed in.
  2. Change the MCP entry to pw-mcp.
  3. Nothing else. Old sessions continue; new ones get their own browser.

To go back, restore the old MCP entry. Browsers that pw-pool started are reaped after the idle TTL,or at once with pw-pool stop all. A browser you ran before (for example a shared one on a fixedCDP port) is not touched by pw-pool and can keep running beside it.

How it works

session A ─▶ pw-mcp ─▶ registry ─▶ Chrome :9300, profiles/A/ ◀─ @playwright/mcp --cdp-endpoint
session B ─▶ pw-mcp ─▶ registry ─▶ Chrome :9301, profiles/B/ ◀─ @playwright/mcp --cdp-endpoint
  • pw-mcp replaces npx @playwright/mcp as the MCP server command. It finds out which session iscalling, takes that session's browser from the pool (launching one if needed), and runs thebundled @playwright/mcp against it over CDP. Stdio passes straight through.
  • When the MCP exits, the browser stays up. A resumed session gets the same browser, tabs and all.
  • Idle browsers are stopped after 12 hours (tabs saved, profile kept). A later start relaunches thebrowser on the same profile and reopens the tabs. Unused profiles are deleted after 30 days.
  • Nothing raises a window: browsers start with no window, and tabs are opened in the background.The bundled MCP carries a two-line patch for the same reason (see Focus).

No daemon. State is a JSON registry under ~/.pw-pool/, guarded by a lock.

Which session is which

pw-mcp needs a stable key per session. In order:

  1. --key / PW_POOL_KEY — explicit. Any harness can set this. PW_POOL_NAME labels the window.
  2. CLAUDE_CODE_SESSION_ID — Claude Code (2.1.239+) sets it in the MCP server's environment.
  3. ~/.claude/sessions/<parent pid>.json — Claude Code writes its session id, name and cwd there.
  4. The parent pid — fallback; the browser is deleted when the lease ends.

Same key, same browser. claude --resume keeps the session id, so it gets its browser back.

Templates

pw-pool template save <name> --from <dir> copies the login files of a profile (cookies, localstorage, IndexedDB, saved passwords, preferences — a few MB; no caches). A new session's profile isseeded from --template <name>, PW_POOL_TEMPLATE, or config.defaultTemplate, once, when it iscreated. After that each profile evolves on its own. --fresh forces an empty profile.

Templates and profiles contain live credentials. Keep ~/.pw-pool/ out of repositories. A template isa point-in-time copy: save it again after signing in to something new.

CLI

pw-pool install [--yes]            first-time setup; asks before downloading Chrome
pw-pool ls                         registered browsers: key, name, port, pid, status, tabs, leases
pw-pool cdp [key] [--ensure]       CDP endpoint of a session's browser (default: the calling session)
pw-pool tabs [key]
pw-pool gc [--force] [--dry-run]   reap stale leases, idle browsers, old profiles
pw-pool stop <key|all> [--rm]      stop a browser (tabs saved); --rm also deletes its profile
pw-pool template save <name> [--from <dir>] | ls | rm <name>
pw-pool config [get <key> | set <key> <json>]
pw-pool doctor

<key> is a full key, a unique prefix, or a session name. pw-pool cdp --ensure lets scripts drivethe same browser as their session's MCP. Every pw-mcp start runs gc; for machines where sessionsare rare, run pw-pool gc from cron or launchd.

Config lives in ~/.pw-pool/config.json (pw-pool config): portRange [9300, 9399],idleTtlHours 12, profileTtlDays 30, defaultTemplate, sourceProfile, chrome, channel, headless,sandbox (off, like Playwright's chromiumSandbox), windowCascade, windowSize,extraChromeArgs, launchTimeoutMs. PW_POOL_HOME moves the wholestate directory; PW_POOL_HEADLESS=1 runs browsers headless (servers, containers).

Focus

On macOS, two things activate a Chrome app and take focus from the person using the machine: awindow created at startup, and a tab created in the foreground. pw-pool launches Chrome with--no-startup-window and opens tabs with CDP's background: true. @playwright/mcp has no optionfor this, so scripts/patch-focus.js changes two lines in the bundled copy (browser_tabs new →background tab, browser_tabs select → no bringToFront). The patch is applied on npm install;pw-pool doctor verifies it; PW_MCP_FOREGROUND_TABS=1 restores the original behaviour.

One case is outside the patch's reach: when a page itself opens a popup (window.open or atarget="_blank" link on a click), macOS activates the browser to show it, the same as any Chrome.Browsers are headed by default, matching @playwright/mcp. If that focus-stealing matters on ashared machine, run headless: pw-pool config set headless true, PW_POOL_HEADLESS=1, or per sessionpw-mcp --headless (and --headed to force headed when the default is headless). Headless rendersidentically for snapshots and screenshots.

Troubleshooting

  • The MCP disconnects ("Connection closed") right after a browser_evaluate. The result waslarger than the client's per-message limit (16 MB in Claude Code), so the client closed theconnection. This is not specific to pw-pool. The client restarts the server within seconds andpw-mcp reattaches to the same browser, tabs included; call the tool again and return smallervalues. Claude Code keeps the server's log under ~/Library/Caches/claude-cli-nodejs/<project>/mcp-logs-playwright/.
  • "Chrome exited during startup" or "did not answer": the error quotes the end of~/.pw-pool/logs/<key>.chrome.log. Common causes: no display on Linux (use headless or Xvfb),a binary that cannot run (pw-pool doctor).
  • A browser seems to belong to nobody: pw-pool ls shows leases; ! marks a holder that hasexited. pw-pool gc clears those; pw-pool stop <key> stops a browser you are sure about.

Development

npm test               # unit tests (no browser needed)
npm run test:e2e       # real browsers, throwaway pool home: isolation, reattach, concurrency, recovery, templates
npm run test:docker    # the same on Linux in a container

The @playwright/mcp version is pinned. To bump it, change the version, run npm install, and fixscripts/patch-focus.js if the install fails (the bundle changed shape).

Releasing

Publishing uses npm trusted publishing (OIDC from GitHub Actions) — no tokens. One-time setup onnpmjs.com: the package's Settings → Trusted Publisher → this repo's publish.yml workflow. After that,release by tagging: npm version patch && git push --follow-tags. The workflow runs the tests andnpm publish --provenance. (The first ever publish, before the package exists, is done once locallywith npm publish --access public --auth-type=web.)

License

MIT

MCP Server · Populars

MCP Server · New

    weed33834

    🛡️ AgentSeed

    AgentSeed - anti-hallucination guardrails for AI coding agents: hybrid Skill + MCP plugin (Agent Plugins 1.0.0) that forces spec-driven development and verifies code before it is marked done.

    Community weed33834
    geolens-io

    GeoLens

    Self-hosted geospatial data catalog with semantic search (pgvector), OGC/STAC APIs, and map builder. Built on FastAPI, PostGIS, React, and MapLibre.

    Community geolens-io
    leonardosepulvedat

    MCP n8n Server

    Complete n8n API integration for Claude Desktop and Cursor - 100 workflow templates with intelligent matching

    Community leonardosepulvedat
    maximhq

    Bifrost AI Gateway

    The Fastest LLM Gateway with built in OTel observability and MCP gateway

    Community maximhq
    crisnahine

    rails-ai-context

    45 MCP tools that give AI coding agents ground truth about your Rails app: schema, models, routes, controllers, views, jobs, conventions. Works with Claude Code, Cursor, GitHub Copilot, OpenCode and Codex CLI. MCP or CLI, in-Gemfile or standalone, and it still answers when the app can't boot.

    Community crisnahine