diff options
Diffstat (limited to 'mcp/README.md')
| -rw-r--r-- | mcp/README.md | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/mcp/README.md b/mcp/README.md new file mode 100644 index 0000000..d9c6c52 --- /dev/null +++ b/mcp/README.md @@ -0,0 +1,254 @@ +# pi-chrome-mcp + +**Your everyday browser, driven by any MCP client.** + +Claude Code, Claude Desktop and other MCP clients get tools that read, click, type in and +screenshot the browser you are already signed into — your real profile, your sessions, your +logins — instead of a fresh automation browser that is logged into nothing. + +The tools themselves are [pi-chrome](https://github.com/tianrendong/pi-chrome)'s, loaded as a +dependency rather than copied. pi-chrome gives them to the [pi](https://pi.dev) agent; this server +gives the same tools to everyone else. + +## What you get + +21 tools: page snapshots with stable element ids, click, type, fill, scroll, keys, navigation, tab +management, console and network inspection, screenshots (returned inline, so the model sees them), +file uploads, and JavaScript evaluation. + +Plus, over plain pi-chrome: + +- **A session per line of work.** Every call takes a `session` name you choose. Each name gets its + own tab and tab group, so parallel subagents stop overwriting each other's page. +- **A shared server, optionally.** `--http` serves every client from one process instead of one per + client session. + +## Requirements + +- **Node 20.6 or newer**, with npm. (20.6 is where `import.meta.resolve` works without a flag.) +- **git**, and network access to this repo and to the npm registry. +- **Chrome, or another Chromium browser** — Brave, Edge, Arc, Vivaldi — in which you can load an + unpacked extension. A work profile under extension policy often cannot; check before starting. +- **An MCP client**: Claude Code, Claude Desktop, or anything else that speaks MCP. +- **macOS or Linux** for `run.sh`. On Windows, run `node server.mjs` directly with the same + environment variables. +- **A human, once:** loading an unpacked extension is a browser dialog, so an agent cannot do it. + +## Setup + +**1. Install.** The repo is public; the clone needs no credentials. + +```bash +git clone https://git.soarez.net/sz/pi-chrome-mcp.git +cd pi-chrome-mcp +./run.sh --install-only # fetches dependencies, then prints the extension path +``` + +**2. Load the companion extension** (this is the human step). In Chrome, open +`chrome://extensions`, turn on **Developer mode**, click **Load unpacked**, and select the +`extension` folder that the previous command created — it prints the full path. The folder is not +in git: it is copied out of the installed pi-chrome. + +Load it once, whatever you drive it from. If pi already loaded pi-chrome's extension on this +machine, that copy serves this server too; see [Using it from pi](#using-it-from-pi). + +It is about 2000 lines of plain JavaScript with no dependencies, and worth reading before you load +it. It comes from pi-chrome; `run.sh` copies it out of `node_modules` so that updating dependencies +cannot delete the folder Chrome is loading from. + +Chrome shows "Pi Chrome Connector started debugging this browser" while a tool is attached. That +banner is Chrome's, and expected. + +**3. Register the server with Claude Code:** + +```bash +claude mcp add-json --scope user my-chrome '{"type":"stdio","command":"'"$PWD"'/run.sh"}' +``` + +Two notes. `--scope user` matters: without it the server is registered for the current directory +only, so it would exist in this repo and nowhere else. And the name is yours to choose — `my-chrome` +rather than `chrome` keeps it distinct from Anthropic's own Claude in Chrome tools, so you and the +model can say which you mean. (If your path contains a quote or a backslash, write that JSON by hand +rather than splicing `$PWD`.) + +**4. Check it, in two steps.** + +`claude mcp list` shows `my-chrome` connected once the server starts — but that only proves the +server runs, not that the browser is attached. For that, with a client connected: + +```bash +curl -s http://127.0.0.1:17318/status # "connected": true means the extension is polling +``` + +Then, in a **new** Claude Code session (servers start per session, so the one you registered from +will not have the tools), ask: + +> Using my-chrome, open example.com with session "main", and tell me the page title. + +Expect a permission prompt per tool call, a new tab in a tab group named `Pi Session: …`, and the +title back. Your own tab is not touched: the first call opens a tab of its own. + +**Tired of the prompts?** Allow the tools you trust rather than turning permissions off, for +example in `~/.claude/settings.json`: + +```json +{ "permissions": { "allow": ["mcp__my-chrome__chrome_snapshot", "mcp__my-chrome__chrome_navigate"] } } +``` + +Allowing the whole server (`mcp__my-chrome`) also allows `chrome_evaluate`, which runs any +JavaScript as you. Consider a `deny` entry for that one; deny rules hold even in bypass mode. + +### Claude Desktop + +Add it to `claude_desktop_config.json` with an absolute path, and restart Desktop: + +```json +{ + "mcpServers": { + "chrome": { "command": "/absolute/path/to/pi-chrome-mcp/run.sh" } + } +} +``` + +Desktop starts servers with almost no environment, so `run.sh` looks for node in the usual install +locations (Homebrew, nvm, volta, n). If yours is somewhere else, put it on the PATH Desktop sees. + +## Using it from pi + +The [pi](https://pi.dev) agent reaches this same server, so both your agents share one definition +of the browser, one extension and one set of tools. + +**1. Give pi an MCP client.** + +```bash +pi install npm:pi-mcp-adapter +``` + +**2. Register this server** in `~/.pi/agent/mcp.json`, with the absolute path to `run.sh`: + +```json +{ + "mcpServers": { + "my-chrome": { + "command": "/absolute/path/to/pi-chrome-mcp/run.sh", + "approveTools": true + } + } +} +``` + +**3. Add the session gate.** Copy `pi/browser-gate.ts` from this repo into +`~/.pi/agent/extensions/`. The first browser call in a pi session asks once — for the session, for +15 minutes, or deny — and `/browser authorize`, `/browser revoke` and `/browser` manage it from +there. For headless runs (`pi -p`), `PI_BROWSER_AUTHORIZE=indefinite` grants it up front; without +it, a headless call is denied. + +Why the gate: pi has no permission prompts of its own, and the adapter's own approval remembers a +grant only for identical arguments, which for a browser means a prompt on nearly every call. Keep +`approveTools: true` as well — if the gate ever fails to load, the adapter asks per call and fails +closed headless, rather than running unchecked. + +That is all pi needs: no `pi install npm:pi-chrome`, and no `/chrome authorize`. + +**Both agents at once.** The server starts one process per client session, and whichever starts +first owns the local bridge port; the others connect through it. A pi session and a Claude Code +session drive the same browser, each in its own tabs, because every call names its session. + +### The direct alternative + +pi-chrome is a pi extension first, so `pi install npm:pi-chrome` does give pi the tools without any +of the above. The cost is two definitions of the browser on one machine, pi's own `/chrome +authorize` every session, and no shared session naming. If you go that way, still load the +extension only once: pi's `/chrome onboard` offers to load its own copy, and two copies in one +browser both poll the bridge, so commands and tabs go to whichever answers first. + +## How it works + +The companion extension polls a small HTTP bridge on `127.0.0.1:17318` for commands, and runs them +in your browser through `chrome.debugger`. That bridge is part of pi-chrome, and this server loads +pi-chrome's code and hosts it in the same process. So the chain is: + +``` +MCP client --MCP--> this server --loopback--> companion extension --> your tabs +``` + +Nothing listens on the network unless you ask for `--http`. + +## Troubleshooting + +- **"the Chrome extension is not polling".** The extension is not loaded, is disabled, or its + browser is closed. Check `chrome://extensions`, and that the browser is running. +- **Is the bridge alive?** With a client connected, `curl -s http://127.0.0.1:17318/status` returns + JSON; `"connected": true` means the extension is polling it. +- **Port 17318 already in use.** Another copy of this server, or a pi session, already owns the + bridge; that is fine, they share it through it. Some unrelated program on that port is not fine: + the extension hardcodes 17318, so `PI_CHROME_BRIDGE_PORT` only helps if you patch the extension + to match. +- **After an update, reload the extension.** `run.sh` says when it has changed; Chrome keeps + running the old copy until you press reload at `chrome://extensions`. +- **Tools missing in a client.** Restart the client: MCP servers are started once per session. +- **Sitting next to Claude in Chrome?** Anthropic's own extension offers similar tools. Give this + server a distinctive name (the registration name, or `MCP_SERVER_NAME`) so a model does not + confuse the two, and say which one you mean. + +## Options + +| Setting | What it does | +|---|---| +| `--http HOST:PORT` | Serve every client over streamable HTTP at `http://HOST:PORT/mcp` instead of stdio. Needs a token. | +| `--no-auth` | Deliberately run `--http` with no token. Anything that reaches the port can then drive your browser. | +| `MCP_TOKEN` | Bearer token for `--http`. | +| `MCP_TOKEN_COMMAND` | A command whose output is that token, for a secret store. | +| `MCP_SERVER_NAME` | What clients call this server, default `chrome`. Also names its temp and state files. | +| `MCP_INSTRUCTIONS_FILE` | A file whose contents lead the instructions the model reads. Use it to say which browser this is, or how your machine differs. | +| `PI_CHROME_PATH` | Load pi-chrome's `index.ts` from somewhere else, e.g. a fork or a checkout. | +| `PI_CHROME_BRIDGE_PORT` | The bridge port, default 17318. The packaged extension polls 17318, so changing this means patching it too. | +| `MCP_IDLE_CLEANUP_MS` | How long an HTTP client's tabs stay open after its last call, default an hour. | +| `MCP_STATE_FILE` | Where `--http` records open sessions so a restart can hand them back. | + +pi-chrome reads a few of its own, which matter only when `PI_CHROME_PATH` points at a fork that +supports them: `PI_CHROME_REMOTE` (drive a browser on another host through a broker), +`PI_CHROME_OTHER_BROWSER_TOOLS` (what its prompt calls the other browser's tools) and +`PI_CHROME_AUTHORIZE`. This server sets `PI_CHROME_AUTHORIZE` itself, since the client's prompts are +the gate. + +## Safety + +This drives a browser that is signed in as you, so treat it accordingly: + +- **Your MCP client's permission prompts are the gate.** pi-chrome's own `/chrome authorize` step + has no equivalent here; the server assumes the client asks. In Claude Code, that means every tool + call prompts unless you allow it. Bypass mode skips those prompts, and only a deny rule still + holds. +- **`chrome_evaluate` runs arbitrary JavaScript as you**, and `chrome_wait_for` can too. They can + read tokens a page holds and call its APIs with your session. +- **Chrome protects its own pages.** Extension pages, including password managers and wallets, and + `chrome://` settings cannot be read or clicked by an extension, this one included. +- **Web pages are data, not instructions.** Anything an agent reads may try to redirect it; that + risk is the reason to keep the prompts on. +- **Over `--http`, use a token** and bind an address you trust. Without one, anything that can + reach the port drives your browser. + +## Updating + +pi-chrome is pinned in `package.json`. To move to a newer one, change the version, run +`./run.sh --install-only`, and reload the extension at `chrome://extensions` if it reports that the +extension changed. + +## Uninstall + +```bash +claude mcp remove --scope user my-chrome # and delete the entry from Claude Desktop's config +rm -rf /path/to/pi-chrome-mcp # the repo, its node_modules and ./extension +``` + +Then remove "Pi Chrome Connector" at `chrome://extensions`. + +## Credits + +The browser tools, the companion extension and the bridge are pi-chrome: +[the npm package `pi-chrome`](https://www.npmjs.com/package/pi-chrome), published by tianrendong of +Earendil Inc., source at [github.com/tianrendong/pi-chrome](https://github.com/tianrendong/pi-chrome), +MIT licensed. It is pinned by version in `package.json` and by hash in `package-lock.json`, so an +install fetches exactly what was tested. This repository adds the MCP server around it and is MIT +licensed too. |
