Sample Verified Review
chrome-devtools-mcp 1.12.0
This is a real review of Google (ChromeDevTools)'s browser-automation MCP server, published in full so you can see exactly what a maintainer gets for $99. The maintainers did not commission it. Every finding names the file it was read from at the reviewed commit.
Package chrome-devtools-mcp@1.12.0 (npm)
Source commit af28344, Apache-2.0
Reviewed November 10, 2026
Reviewer CuratedMCP
Meets our bar. Verified for v1.12.0.
Risk level recorded on the listing: Network egress, with JavaScript execution in pages and file writes. Three settings deserve a deliberate choice before first use: the persistent browser profile, attaching to a running Chrome, and usage statistics.
Capabilities recorded on the listing
- •
web_fetch: loads any URL through a real Chrome, unless you set --allowedUrlPattern or --blockedUrlPattern - •
js_exec_in_page: runs caller-provided JavaScript inside pages (evaluate_script), can be switched off - •
file_read: uploads files from local paths (upload_file), loads scripts and unpacked extensions from disk - •
file_write: screenshots, traces and network request or response bodies, limited to the MCP client's roots or the temp directory by default - •
credential_access: returns Cookie and Set-Cookie headers and bodies verbatim; attached to a running Chrome it acts inside your logged-in sessions - •
telemetry_egress: usage statistics to Google (play.googleapis.com), on by default, documented opt-out
1. Install path
What a user actually gets when they run npx [email protected].
Zero runtime dependencies, no install scripts Good
The published tarball (378 files, 15 MB) declares no dependencies; Puppeteer, Lighthouse and the other libraries are bundled into build/. There are no preinstall, install or postinstall hooks, so nothing runs at install time. Resolving the package with npm produced one package and npm audit reported zero vulnerabilities.
- package.json in the tarball: dependencies {} and no install hooks (the repo's prepare script does not run for consumers)
- npm audit --omit=dev on a lockfile resolved for [email protected]: 0 vulnerabilities
Bundling moves third-party code out of npm's view Worth knowing
Because dependencies are vendored into the bundle, your own npm audit will never flag a vulnerable Puppeteer or Lighthouse version inside this package. Rely on the project's release cadence instead. The bundle ships a THIRD_PARTY_NOTICES file and the repo tests it.
- scripts: bundle, test:notices in the repo's package.json
- build/ in the tarball
Daily update check calls the npm registry Worth knowing
On start, at most once every 24 hours, the server spawns a detached process that runs npm config get registry and fetches <registry>/chrome-devtools-mcp/latest. It writes a small timestamp file under your home directory to rate-limit itself. The README documents this and the opt-out.
- src/utils/check-for-updates.ts: 24-hour mtime check, spawn with detached: true
- src/bin/check-latest-version.ts: execSync('npm config get registry'), fetch(`${registry}/chrome-devtools-mcp/latest`)
- Opt-out: CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS (README, Update checks)
2. Credentials and secrets
How the server handles keys, cookies, sessions and anything else it could leak.
No secrets of its own; the browser profile is the secret Your decision
The server takes no API keys. What it holds is a Chrome profile. By default it launches Chrome with a persistent profile at $HOME/.cache/chrome-devtools-mcp/chrome-profile, so logins and cookies survive between sessions and are available to whichever agent starts it next. --isolated uses a temporary profile that is deleted on exit. Decide which you want before the first run.
- src/BrowserManager.ts #launch(): userDataDir defaults to path.join(os.homedir(), '.cache', 'chrome-devtools-mcp', 'chrome-profile') unless --isolated or --userDataDir
- src/config/browser-options.ts: isolated (default false), userDataDir
Attaching to your running Chrome hands the agent your live sessions Your decision
--browserUrl, --wsEndpoint and --autoConnect connect to an already running Chrome instead of launching one. That is the documented way to debug a real session, and it means every tool call runs inside your logged-in accounts. Treat it as giving the model your browser.
- src/config/browser-options.ts: autoConnect ('automatically connects to a browser (Chrome 144+) running locally'), browserUrl, wsEndpoint
- src/BrowserManager.ts #connect()
Network tools return cookies and bodies verbatim Worth knowing
get_network_request is designed to inspect headers, and its own description says so: request headers including Cookie, response headers including Set-Cookie. Request and response bodies can be returned inline or written to a file you name. There is no redaction, which is correct for a debugging tool and worth knowing when the model's context is logged anywhere.
- src/tools/network.ts: get_network_request description; requestFilePath and responseFilePath parameters
Extra headers, including bearer tokens, come from your config Worth knowing
The CLI accepts extraHttpHeaders as a JSON string, with an Authorization bearer token given as the example. Anything you put there sits in plain text in your MCP client config, the same as any other server's environment block.
- src/config/cli-options.ts: extraHttpHeaders
3. What it can do
61 tools. 28 are annotated read-only, 36 are not. These are the ones that change state outside the page.
Arbitrary JavaScript in any page, with a documented off switch Worth knowing
evaluate_script runs caller-provided JavaScript in the target page, inline or from a local file, and navigate_page accepts an initScript. --no-javascript-evaluation disables these entry points and blocks javascript:, data: and vbscript: URLs. The flag's own help is candid that this is not a sandbox: scripts on pages still run, and --experimentalDevtools reopens a path through the DevTools console.
- src/tools/script.ts: evaluate_script, readOnlyHint: false
- src/config/mcp-options.ts: javascriptEvaluation (default true) and its description
File writes are fenced to the client's roots Good
Screenshots, traces and saved bodies go to a path the model names, but the server resolves every path against the MCP client's negotiated roots, falling back to the temp directory when the client negotiates none. Writing outside them is refused unless the operator starts the server with --allowUnrestrictedPaths (default false). Symlinks are resolved before the check.
- src/McpContext.ts: root resolution with fs.realpath, 'Access denied' on paths outside roots
- src/config/mcp-options.ts: allowUnrestrictedPaths (default false)
Local file reads and extension loading are explicit tools Worth knowing
upload_file sends files from local paths into a page's file input. install_extension loads an unpacked extension from an absolute local path (not from a URL). Both are listed in SECURITY.md as intentional features rather than bugs.
- src/tools/input.ts: upload_file (filePaths)
- src/tools/extensions.ts: install_extension ('Absolute path to the unpacked extension folder')
- SECURITY.md, Scope
URL allow and block lists exist Good
--blockedUrlPattern and --allowedUrlPattern restrict where the browser may go, including subresources, using the URL Pattern standard. The allow list needs Chrome 149 or newer. For an agent that should only touch your staging site, this is the control to set.
- src/config/puppeteer-options.ts: blockedUrlPattern, allowedUrlPattern (reloadable)
Usage statistics are on by default, and the payload is small Your decision
Telemetry goes to Google's Clearcut endpoint. We read what it sends: tool name, a per-process random session id (rotated, never written to disk), app version, OS type, error codes, and tool parameters reduced to shape only: strings become a bucketed length, arrays a count, enums and booleans their value; uid, reqid and msgid are never sent. No URLs, selectors, page content or persistent machine identifier. The local state file holds two timestamps. The flag help and README disclose the collection and the opt-outs; the first-run notice goes to stderr, which many MCP clients hide, so most users will not see it.
- src/telemetry/watchdog/ClearcutSender.ts: https://play.googleapis.com/log?format=json_proto; session_id = crypto.randomUUID()
- src/telemetry/transformation.ts: sanitizeParams, PARAM_BLOCKLIST = {uid, reqid, msgid}, bucketize(string length)
- src/telemetry/persistence.ts: LocalState { lastActive, lastToolCall } in telemetry_state.json
- src/config/mcp-options.ts: usageStatistics default true; README 'Usage statistics'; opt-outs --no-usage-statistics, CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS, CI
4. Tool descriptions
Do the descriptions the model reads match what the code does, and do they try to steer the model?
Descriptions match behaviour Good
We compared the descriptions of the tools that touch files, the network, extensions and scripts with their handlers. Each says what the code does, including the uncomfortable parts (cookies in get_network_request, local paths in upload_file). No hidden instructions, no external URLs the model is told to visit, no claims the code does not back up.
- src/tools/*.ts, all 61 description strings scanned for directive language
One steering phrase, harmless Worth knowing
fill_form tells the model to ALWAYS prefer it over repeated fill or click calls. That is efficiency advice about the server's own tools, not an instruction about the user's data, and we do not count it against the server.
- src/tools/input.ts: fill_form description
Prompt injection is named, and placed on the client Worth knowing
SECURITY.md states that web content is returned to the model as-is and that the calling agent is responsible for validating tool calls and parameters. That is the honest position for a browser tool. It also means this server should be paired with a client that treats page text as untrusted.
- SECURITY.md, Scope and Trusted host environment
If you install it
- Run it with --isolated unless you specifically want logins to persist, and never with --browserUrl or --autoConnect on a profile you are signed into for work.
- Set --allowedUrlPattern (Chrome 149+) or --blockedUrlPattern when the agent only needs your own sites.
- Decide on telemetry explicitly: add --no-usage-statistics or set CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS=1 if your policy requires it. The data sent is minimal, but the default is on.
- Keep the default path restriction (do not pass --allowUnrestrictedPaths) and let your MCP client negotiate roots.
- If your client logs model context, remember that get_network_request output can contain session cookies.
What we would suggest to the maintainers
- Surface the usage-statistics notice somewhere MCP clients show, since stderr is hidden in most of them.
- Mention the persistent default profile path next to --isolated in the README's quick start, so users choose knowingly.
- Note in the README that network tools return Cookie and Set-Cookie headers and bodies unredacted.
None of these change the verdict. They are the kind of notes a paying maintainer gets in the findings email, and can fix before we take a second look at no extra charge.
Scope and method
- What we read: the published npm tarball for 1.12.0 and the repository at the commit it was built from (af28344), focusing on src/bin, src/config, src/BrowserManager.ts, src/McpContext.ts, src/telemetry, src/tools, SECURITY.md and README.md.
- What we ran: npm's resolver and audit against the published version, without executing the package. We did not run the server against a live browser, fuzz inputs, or review the bundled third-party code line by line.
- What this is: our informational opinion of this version at the time of review, not a security audit, penetration test, warranty or endorsement. A later version can behave differently.
- Time spent: about three hours of reading, which is what the fee buys.
- Terms: maintainer terms.
Want this for your server?
Same four checks, same written findings, within 3 business days. $99 one-time, $49 with Maintainer Pro. If it meets the bar, your listing shows verified with its risk level and capabilities.