Skip to main content
Glama
marcondesmacaneiro

chrome-profile-debug

README.md
# chrome-profile-debug

Drive **any of your named Chrome profiles** from an MCP client — Claude Code,
Codex, or anything else that speaks MCP over stdio.

Chrome keeps every profile in one browser process. Tools that attach over
`--remote-debugging-port` therefore see *all* of your open profiles at once and
cannot tell them apart, while extension-based tools usually bind to whichever
single profile happens to be paired. Neither lets you say "work in the profile I
call `staging`".

This does. You name each profile once, and every tool call takes that name.

```
list_profiles()                → ["personal", "staging", "prod-readonly"]
new_tab(profile: "staging", url: "https://app.example.com")   → { tabId: 42 }
read_page(profile: "staging", tabId: 42)
```

No listening TCP port. No telemetry. No Chrome Web Store account required.

## How it works

```
  MCP client
      │ stdio
      ▼
  server/index.js ──── Unix domain socket (0600)
      ▲
      │ NDJSON
  host/host.js ─────── one process per profile, spawned by Chrome
      ▲
      │ Chrome native messaging
  extension/ ───────── one instance per profile
      │ chrome.debugger
      ▼
    web page
```

The wire format is specified in [docs/PROTOCOL.md](docs/PROTOCOL.md). The threat
model and its limits are in [docs/SECURITY.md](docs/SECURITY.md).

## Install

Requires Node.js 20+ and a Chromium-family browser.

### 1. Get the code

```bash
git clone https://github.com/marcondesmacaneiro/chrome-profile-debug.git
cd chrome-profile-debug
npm install
```

Keep the directory where it is. An unpacked extension's ID is derived from its
absolute path, and the native messaging manifest pins that ID — moving the
folder breaks the link until you re-run the installer.

### 2. Load the extension into each profile you want to drive

In **each** Chrome profile, separately:

1. Open `chrome://extensions`
2. Turn on **Developer mode**
3. Click **Load unpacked** and select the `extension/` directory

The extension always gets the same ID, whether it was loaded from source or
installed from the Chrome Web Store:

```
clkflehjfcefahjgchgcomflocccphnj
```

That is because `extension/manifest.json` pins the public key. Without it, an
unpacked extension's ID derives from its absolute path and would differ on
every machine.

### 3. Register the native messaging host

Once per machine:

```bash
npm run install-host -- --extension-id clkflehjfcefahjgchgcomflocccphnj
```

This writes a manifest into Chrome's `NativeMessagingHosts` directory. It
contains an absolute path, so it is generated locally and never committed.

The same command covers every profile on the machine: the ID identifies the
extension, not the profile.

### 4. Name each profile

Click the extension icon in a profile and give it a name — `staging`,
`personal`, whatever you will type in your prompts. **A profile with no name
never connects**, which is what makes participation opt-in.

### 5. Point your MCP client at the server

Claude Code:

```bash
claude mcp add chrome-profile-debug -- node /absolute/path/to/chrome-profile-debug/server/index.js
```

Any other client: run `server/index.js` over stdio.

## Tools

`list_profiles` first — everything else takes a `profile` name.

| Tool | Purpose |
|---|---|
| `list_profiles` | Connected, named profiles |
| `list_tabs` | Tabs in that profile |
| `new_tab`, `close_tab`, `activate_tab` | Tab lifecycle |
| `navigate` | Open a URL |
| `read_page` | Accessibility tree with click-ready coordinates |
| `page_text` | Plain text of the page |
| `screenshot` | PNG or JPEG of the viewport |
| `evaluate` | Run JavaScript in the page |
| `click`, `type_text`, `press_key`, `scroll` | Input |
| `read_console` | Console messages, optionally filtered by regex |
| `read_network` | Network requests |

## Troubleshooting

**A profile does not appear in `list_profiles`.** Check, in order: the extension
is loaded in that profile; it has a name in the popup; the native host manifest
lists that profile's extension ID; the MCP server is running.

**`DEBUGGER_BUSY`.** Chrome allows one debugger client per tab. Close DevTools
on that tab.

**`RESTRICTED_URL`.** Extensions cannot touch `chrome://`, `devtools://`, or the
Chrome Web Store. This is a browser restriction with no workaround.

## License

MIT