Skip to main content
Glama
README.md
# Chrome MCP

Connect AI coding assistants (Cursor, Claude Code, and any MCP-compatible client) to your **live Google Chrome** browser over the Model Context Protocol. Your agent can list tabs, navigate, snapshot the page, click, type, screenshot, and evaluate JavaScript in your real profile — cookies, logins, and extensions intact.

Unlike headless-browser MCPs, this drives the Chrome you already use, via a Manifest V3 extension and `chrome.debugger` (CDP). Multiple MCP clients (e.g. Cursor **and** Claude Code) share one browser connection through a per-machine Connection Hub.

## Architecture

```
Cursor / Claude Code ──stdio MCP──▶ chrome-mcp (per session)
                                        │ IPC (Unix socket / Windows named pipe)
                                        ▼
                                   Connection Hub (chrome-mcp-hub, one per machine)
                                        │ WebSocket ws://127.0.0.1:17321
                                        ▼
                                   Chrome Extension (MV3 service worker)
                                        │ chrome.debugger (CDP)
                                        ▼
                                   Your Google Chrome
```

- **`packages/shared`** — wire protocol v1, error codes, IPC transport, config, policy.
- **`packages/hub`** — `chrome-mcp-hub`: holds the single WebSocket to the extension and multiplexes MCP sessions.
- **`packages/mcp-server`** — `chrome-mcp`: the stdio MCP server that registers the 10 browser tools.
- **`apps/chrome-extension`** — the MV3 extension (CDP adapter + pairing popup).

## Tools

`browser_list_tabs`, `browser_activate_tab`, `browser_navigate`, `browser_snapshot`, `browser_click`, `browser_type`, `browser_screenshot`, `browser_evaluate`, `browser_wait_for`, `browser_subscribe`.

## Quickstart

### 1. Build

```bash
pnpm install
pnpm build
```

> **exFAT / removable drives:** pnpm symlinks fail on exFAT. This repo's `.npmrc`
> already sets `node-linker=hoisted`, `inject-workspace-packages=true`, and
> `package-import-method=copy`. `pnpm build` runs `scripts/sync-workspace-deps.mjs`
> to copy the built `@chrome-mcp/shared` into `node_modules` (no symlinks).

### 2. Load the Chrome extension

1. Open `chrome://extensions`.
2. Enable **Developer mode** (top right).
3. Click **Load unpacked** and select `apps/chrome-extension/dist`.

### 3. Start the hub

```bash
node packages/hub/dist/cli.js start
# or, once published:  npx chrome-mcp-hub start
```

It prints a **pairing token** and listens on `ws://127.0.0.1:17321` + the platform IPC socket. Leave it running.

### 4. Configure your MCP client

Copy the matching example from [`examples/`](./examples):

- **Claude Code** → `.mcp.json` (project) — see `examples/claude-code.mcp.json`
- **Cursor** → `.cursor/mcp.json` — see `examples/cursor.mcp.json`
- **Windows** → use the `cmd /c` wrapper — see `examples/windows-cmd-wrapper.json`
- **Local (pre-publish)** → point at the built CLI — see `examples/local-dev.mcp.json`

### 5. Pair the extension

1. Click the **Chrome MCP** extension icon.
2. Paste the pairing token from the hub output.
3. Check **Approve this connection** and click **Save & Connect**.
4. The status dot turns green when connected.

Your agent can now control Chrome. Try: *"list my tabs, navigate to http://localhost:3000, and take a snapshot."*

## Security

- **Localhost only.** All listeners bind to `127.0.0.1`. Nothing is exposed externally.
- **Pairing required** by default; the extension must present the hub's token (rejected with close code 4001 otherwise).
- **URL allowlist**, enforced on *both* the server and the extension. Defaults permit only `localhost` / `127.0.0.1`. `file://`, `chrome://`, and `chrome-extension://` are always blocked unless explicitly allowlisted.
- **New-origin prompt.** When an agent hits an unlisted origin, the popup offers **Allow / Deny** (configurable via `promptForNewOrigins`).
- **Incognito tabs** are excluded by default.

Configure via `~/.chrome-mcp/config.json` (hot-reloaded within ~2s) or the extension popup. Example:

```json
{
  "policy": {
    "allowedUrlPatterns": ["http://localhost:*", "https://*.mycompany.dev/*"],
    "enforceAllowlist": true,
    "promptForNewOrigins": true
  }
}
```

## Troubleshooting

| Symptom | Cause | Fix |
|---|---|---|
| Every tool returns `NOT_CONNECTED` | Hub not running, or extension not paired | Run `chrome-mcp-hub start`; open the popup and confirm the status dot is green |
| `DEBUGGER_ATTACH_FAILED` on a tab | DevTools is open on that tab (only one debugger per tab) | Close DevTools on the tab, or target a different tab |
| Extension shows "Disconnected" and won't reconnect | Wrong/rotated pairing token | Copy the current token from the hub output into the popup and re-approve |
| `INVALID_REF` when clicking/typing | The page changed or navigated; refs are ephemeral | Re-run `browser_snapshot` to get fresh refs |
| `POLICY_DENIED` with a URL | Origin not in the allowlist | Approve it in the popup, or add a pattern to `~/.chrome-mcp/config.json` |
| `spawn ENOENT` on Windows when the client starts the server | npx not resolved directly | Use the `cmd /c npx` wrapper (`examples/windows-cmd-wrapper.json`) |
| MCP client shows protocol/parse errors | Something wrote to stdout | The server logs only to stderr by design; check for a stray `console.log` in custom forks |
| Hub `status` says NOT RUNNING but you started it | Stale socket after a crash (POSIX) | Delete `/tmp/chrome-mcp-hub.sock` and restart the hub |

## Development

```bash
pnpm test        # run the full Vitest suite (shared, hub, mcp-server, extension)
pnpm typecheck   # tsc --noEmit across packages
pnpm lint        # ESLint 9 (bans stdout writes in the MCP server)
```

## Scope (MVP)

Chrome stable only; stdio MCP transport only; `chrome.debugger` (no Puppeteer/Playwright at runtime); sideloaded extension. HTTP/SSE transport, cross-origin iframe targeting, and Chrome Web Store publishing are deferred. See `openspec/changes/chrome-mcp/` for the full spec and `TDD.md` for the technical design.