chrome-profile-mcp
by sadewadee
README.md
# chrome-profile-mcp
Drive your **real** Chrome profiles (with existing logins, cookies, sessions) from Claude Code via [`@playwright/mcp --extension`](https://github.com/microsoft/playwright-mcp) + the Playwright Chrome Extension relay.
## Why this exists
Upstream `@playwright/mcp --extension` auto-discovers *which* Chrome profile to open — `Default` first, then numeric `Profile N`, preferring `last_used` — with **no knob to pin a specific profile**. If the browser extension is installed in several profiles, the relay tab can land in the wrong one (e.g. Diana's instead of jamu's).
This repo adds one env var, `PLAYWRIGHT_MCP_PROFILE_DIRECTORY`, via a small idempotent patch script, so each MCP server deterministically opens its own profile.
No extension code is modified. No credentials or tokens are stored here.
## How it works
1. `npm ci` installs pinned `@playwright/mcp@0.0.80` (which pulls `playwright-core`).
2. `npm run postinstall` runs `patch-profile-directory.mjs`, which rewrites one line in `node_modules/playwright-core/lib/coreBundle.js` (`createExtensionBrowser`):
```js
// before (upstream)
const profileDirectory = userDataDir ? await findPlaywrightExtensionProfile(userDataDir) : void 0;
// after (patched)
const profileDirectory = process.env.PLAYWRIGHT_MCP_PROFILE_DIRECTORY ?? (userDataDir ? await findPlaywrightExtensionProfile(userDataDir) : void 0); // PATCHED
```
3. Each MCP server runs the vendored CLI with its own token + profile:
```json
{
"mcpServers": {
"mcp-jamu": {
"type": "stdio",
"command": "node",
"args": ["<repo>/node_modules/@playwright/mcp/cli.js", "--extension"],
"env": {
"PLAYWRIGHT_MCP_EXTENSION_TOKEN": "<token-from-that-profile>",
"PLAYWRIGHT_MCP_PROFILE_DIRECTORY": "Profile 5"
}
}
}
}
```
The token must equal that profile's extension `localStorage` `auth-token` (open `chrome-extension://mmlmfjhmonkocbjadbfplnigmagldckm/connect.html` in the target profile to view/regenerate it). Matching token → silent auto-connect; mismatch → `Invalid token provided.`; no token → manual tab picker.
## Setup
```bash
git clone <this-repo> ~/.chrome-mcp/extension-servers # or anywhere
cd ~/.chrome-mcp/extension-servers
npm ci # installs + patches (postinstall)
node patch-profile-directory.mjs # re-run any time node_modules is reinstalled
```
Requirements: Google Chrome (stable channel path), [Playwright Extension](https://chromewebstore.google.com/detail/playwright-extension/mmlmfjhmonkocbjadbfplnigmagldckm) installed **in each target profile**, `@playwright/mcp@0.0.80` (pinned; the patch anchors to this bundle layout and fails loudly otherwise).
## Constraints / gotchas
- **Never set `--user-data-dir`** to the live Chrome home — a second copy fights `SingletonLock`. With defaults (`channel: chrome` → `~/Library/Application Support/Google/Chrome` on macOS) the relay attaches to the running Chrome session.
- **Never run two servers with the same/empty profile pin** — each needs its own relay port + connect tab.
- One profile = one token. Never commit tokens — use `.mcp.json` env (local, gitignored by clients) or your process manager's secret store.
- Patch is version-pinned: bumping `@playwright/mcp` requires re-verifying the anchor strings in `patch-profile-directory.mjs`.
- Tools surface as `browser_*` (`browser_navigate`, `browser_snapshot`, …), not `puppeteer_*`.
## Verify
```bash
# should print: Version 0.0.80
./node_modules/.bin/playwright-mcp --version
# should print the patched line
grep -n PLAYWRIGHT_MCP_PROFILE_DIRECTORY node_modules/playwright-core/lib/coreBundle.js
```
Then point an MCP client at the vendored CLI, call `browser_navigate https://example.com`, and confirm the visit lands in the pinned profile's `History` (copy the live `History` file first — Chrome locks it while running).
## Files
| File | Purpose |
|---|---|
| `package.json` | Exact-pinned `@playwright/mcp@0.0.80` + `postinstall` hook |
| `patch-profile-directory.mjs` | Idempotent patch script (the actual patch) |
| `.mcp.json.example` | Template server entries (tokens redacted) |
| `.gitignore` | Excludes `node_modules/` (patched bundle is regenerated) |
## License
MIT — patch script and docs. Upstream packages retain their own licenses (Apache-2.0).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues