MCP Browser Extension

Security

Deny-all by default. Domain allowlist, mutation and eval gates, redaction, the audit log, and the pairing token.

Deny-all safe mode. With no policy configured: empty domain allowlist, eval off, downloads off, mutating tools off. Opt in explicitly:

chrome-mcp --allow-domain example.com --enable-mutations
chrome-mcp --policy ./policy.json          # see policy.example.json
chrome-mcp --unsafe-all-domains            # loud footgun
chrome-mcp --enable-observers              # console/network/dialog capture (patches page globals)
chrome-mcp --redact                        # scrub secret-shaped strings out of page reads

Allowing a site without a restart. When a call is refused because its site is not allowed, the extension's toolbar icon shows a ?. Click it: the popup lists the blocked site with an Allow button, shows whether the site you are on is allowed (with its own Allow), and lists the allowed sites. The Options page (Settings in the popup) also has a box to allow any other site. An allowed site applies at once to every session sharing that Chrome, and is saved to ~/.chrome-mcp/allowed-sites.json so it survives restarts. Sites allowed there can be removed there; sites from --allow-domain stay until the flags change. The catch-all * can only come from --unsafe-all-domains. The agent cannot approve a site itself: MCP Browser Extension never reads or drives the extension's own pages. Run with --no-site-grants to keep the allowlist exactly what the flags say.

What comes back is gated too. The allowlist decides which pages may be read; it says nothing about what is on them. A logged-in page routinely renders a session token into a script tag or an API key onto a settings screen.

  • Password field values are always suppressed — in get_html, and in snapshot, where the field still appears (so you can type into it) flagged secret: true with no value. No flag, no opt-in: nobody wants those characters.
  • --redact additionally scrubs secret-shaped strings — JWTs, AWS/GitHub/Slack/ Google keys, Bearer headers, private-key blocks — out of get_text, get_html, read_as_markdown and eval. It is opt-in because a pattern will eventually fire on something you actually wanted. --redact-pattern <regex> adds your own (and implies --redact); an invalid one fails at startup rather than silently never matching.
  • Redaction runs before the output cap, so a truncated read cannot leak what a full one would have hidden.

Every call is recorded to the task's history.jsonl with the URL it touched, the policy verdict (allowed/denied), how long it took, how many bytes came back, and how many secrets were scrubbed — so "what did the agent do in my browser" has an answer after the fact.

The per-boot 256-bit token in ~/.chrome-mcp/handshake.json (mode 0600) is the only trust boundary; it is never written to stdout/stderr. On POSIX the mode is re-verified after every write and the server fails closed if the file ends up group/other-accessible. Windows has no such bits — chmod there only toggles the read-only attribute — so the check is skipped and the token's confidentiality rests on the per-user ACL of %USERPROFILE%\.chrome-mcp.

Updates

You don't have to do anything to stay current. The setup snippets above use @latest, and on top of that an installed copy (npx or a global install) checks npm when it starts: if a newer version is published, it starts that version in its place, with the same arguments, so an MCP config written months ago still runs the newest release. The check gives up after 1.5 s when you are offline, and a copy run from a git checkout never updates itself. The new server also refreshes the unpacked extension in ~/chrome-mcp-extension, which reloads itself; the Chrome Web Store copy updates through Chrome as usual. Turn it off with --no-auto-update or CHROME_MCP_AUTO_UPDATE=0.

Telemetry

The MCP Browser Extension server (not the extension) sends anonymous usage statistics to PostHog, so the project can see how many installs are active, which versions and platforms are in use, and which tools fail most. A notice is printed the first time it runs.

What is sent: a random install id (kept in ~/.chrome-mcp/telemetry.json), the server version, OS, CPU architecture and Node major version, whether the session owns the bridge port or shares it, how many browsers are paired, the paired extension's version and whether it is the store or an unpacked copy, and per-tool call and error counts with error codes. The first batch goes out a minute after the first tool call, then every 10 minutes. For PostHog's MCP Analytics view, each tool call is also sent as one $mcp_tool_call event carrying only the tool name and its group (reading, navigation...), its duration, whether it failed and the error code, the MCP client's name and version (such as claude-code), the MCP protocol revision, the AI model when the client names it in its own request metadata (Codex does), and a random session id that changes after 30 idle minutes. When a client connects, one event records that handshake and the names of the tools offered. Nothing an agent sends or reads: no arguments, results, error messages or intents. Batched every 15 seconds. One pair_check event a minute after startup says whether a browser paired and, if not, which of a few fixed reasons applies (no_extension, token_mismatch, version_mismatch, profile_mismatch), so setup problems can be fixed where they happen.

What is never sent: URLs, domains, tool arguments, page content, screenshots, cookies, profile names, tokens, file paths, or anything you type. Events are personless and GeoIP lookup is disabled.

The browser extension sends nothing — it only ever talks to 127.0.0.1.

Turn it off with any of:

CHROME_MCP_TELEMETRY=0     # or false / off
DO_NOT_TRACK=1
--no-telemetry             # server flag

Learn more

The full documentation, setup guides for each MCP client and the security model live on the website: MCP Browser Extension docs.

Policy file

Pass --policy ./policy.json to configure everything in one place. The example shipped with the package:

{
  "allowDomains": ["example.com", "*.wikipedia.org"],
  "allowEval": false,
  "allowDownloads": false,
  "allowAllTabs": false,
  "enableMutations": true
}

On this page