2026-07-15

Chrome DevTools MCP: The Server Was Alive, Chrome Was Not

A Chrome DevTools MCP 1.6.0 fixture shows why server health is not browser health, and how to bind snapshots, console and network evidence into a reproducible debugging packet.

Chrome DevTools MCP: The Server Was Alive, Chrome Was Not cover illustration

The MCP server was alive. Chrome was not. In a local Chrome DevTools MCP 1.6.0 run, the protocol handshake completed and the server returned all 39 of its tools. The first browser call then failed with Target closed. That split result is the most useful fact in this article: a green MCP connection is not evidence that a coding agent can observe a page.

I kept the failed attempt instead of editing it out of the story. After a bounded recovery for this root-owned disposable CI fixture, the same probe opened a synthetic checkout page, resized it to 390 by 844 pixels, captured the semantic page snapshot, triggered one failed request, and read both console and network records. No application was patched. The artifact tests the debugging loop itself: can another engineer reconstruct what the browser saw, what action occurred, and why the run failed?

Package chrome-devtools-mcp@1.6.0, pinned for the run

Environment Node 22.23.1 and Chrome 148.0.7778.215

Protocol requested and negotiated 2025-06-18

Fixture signal one warning on load, one failed fetch after submit, one handled application error

Evidence boundary one local headless fixture, not a performance, memory, security, or interoperability benchmark

Failure modes of a green server

MCP initialization answers a narrow question: can the client and server agree on a protocol and exchange messages? Tool enumeration answers another: what contract is being advertised? Neither call needs a working inspected page. The official README makes the same lifecycle distinction in a quieter way: Chrome starts only when a tool requires a browser.

That is why the first run was not contradictory. The probe negotiated the protocol with server version 1.6.0, and tools/list exposed 39 names. The subsequent new_page call crossed a new boundary—starting Chrome—and returned Protocol error (Target.setDiscoverTargets): Target closed. The project’s troubleshooting guide defines that family of error as Chrome failing to start or exiting. Retrying protocol initialization would have tested the part that had already worked.

The test environment runs as root inside a disposable CI container. Chrome refuses its normal launch there unless the environment supplies an appropriate sandbox arrangement. For this one retained fixture, I added --chrome-arg=--no-sandbox and --chrome-arg=--disable-setuid-sandbox, reran the exact probe, and recorded those flags in the result. That is a recovery note, not a setup recommendation.

Do not cargo-cult the recovery. On a workstation or long-lived agent host, keep Chrome’s sandbox. If an MCP client’s own sandbox prevents Chrome from starting, the official troubleshooting guide recommends another design: launch Chrome in an appropriate environment and connect with --browser-url. Moving the browser boundary is safer than deleting every boundary around it.

A shared or scheduled worker should also pin the package under test. The convenient starter configuration uses @latest; a reproduced incident needs a version. This was the relevant launch shape after removing the CI-only flags:

{
  "mcpServers": {
    "chrome-devtools": {
      "command": "npx",
      "args": [
        "-y",
        "chrome-devtools-mcp@1.6.0",
        "--headless",
        "--isolated",
        "--no-usage-statistics",
        "--no-performance-crux",
        "--memoryDebugging=true"
      ]
    }
  }
}

--isolated matters here because it creates a temporary user-data directory and removes it after the browser closes. It prevents a scheduled test from quietly inheriting cookies and tabs from a previous incident. It does not make a sensitive account safe to expose to the MCP client.

Six calls make one reviewable incident

The successful rerun was deliberately small. The fixture had a heading, a submit button, a live status field, a warning emitted at load, and a fetch to https://example.invalid/shipping after the click. The request was supposed to fail. The question was whether the browser evidence would preserve that designed causal chain.

Three luminous browser evidence layers—page structure, console events, and network flow—aligned around one interaction
A useful evidence packet binds different observation channels to the same page, viewport, action, and run—not merely to the same bug report.

1. Open the case

new_page selected the synthetic checkout fixture and returned the page list.

2. Bind the viewport

resize_page set 390 by 844 before any diagnosis.

3. Preserve pre-action state

take_snapshot recorded the heading, button, and idle status.

4. Trigger one action

evaluate_script clicked submit and immediately returned status working.

5. Read console evidence

list_console_messages returned one warning and two errors after the request settled.

6. Read request evidence

list_network_requests returned the single failed fetch and its transport reason.

The order is part of the result. The snapshot says the page was idle before the action. The script result says it was working immediately after the click. The later console entry says the application handled a TypeError. Without that sequence, a transcript can contain all the right facts and still attribute them to the wrong state.

Console and network disagree usefully

The fixture emitted one application warning: fixture-ready. After submit, Chrome recorded Failed to load resource: net::ERR_NAME_NOT_RESOLVED, and the application’s catch block recorded shipping-failed TypeError. The network list contained one request: the expected shipping URL with net::ERR_NAME_NOT_RESOLVED.

Those are not duplicate observations. The network record identifies the request and transport outcome. The browser-generated console error shows how Chrome surfaced the failure. The application error shows that its own handler ran and reduced the exception to a TypeError. A network-only review could miss a broken catch path. A console-only review could leave the failed URL and request identity implicit.

ClaimBest evidenceWhat it does not prove
The intended control existed before the actionSemantic snapshot with selected page and pre-action statusThat the handler was attached correctly
The requested viewport was activeRuntime readback of [390,844]That every responsive breakpoint is sound
The application attempted the shipping fetchNetwork request URL and failure resultThat the response handler rendered the right state
The catch path executedApplication-authored console errorThat the user received a useful recovery message
The whole bug is fixedA matched after-run plus an adjacent regression checkAnything, if only the before-run exists

This is the practical difference between browser control and browser debugging. Clicking is an action. A claim about the click requires state before it, runtime evidence after it, and a rule for deciding whether the result is acceptable.

Validation means repeating the case, not the click

A coding agent can make a plausible patch and then accidentally test a different case: another tab, a warm cache, a desktop viewport, a different package version, or a later page state. The after-run may look healthier because the experiment drifted.

Matched browser verification experiment with a turbulent failed state on the left and a stable clean state on the right
Before and after evidence is persuasive only when one reference line—version, page, viewport, action, and filter—passes through both runs.

I would bind an after-run to at least seven fields: package version, Chrome version, target origin, selected page identity, viewport and emulation, reproduction steps, and evidence filters. Then I would repeat the same snapshot point, the same action, and the same console and network queries. Timestamps or monotonic sequence numbers make the packet easier to audit when an asynchronous error arrives after the action result.

The acceptance rule should be stated before editing. For this fixture, a successful repair would not mean “no TypeError.” It would mean the request goes to the intended test endpoint, the expected request count is one, the response has an accepted status, the status field reaches the intended terminal value, and no new application error appears. Removing the catch block could silence one message while making the page worse.

Only after that matched run passes should the agent broaden once: one adjacent viewport, one neighboring route, or one appropriate Lighthouse or performance check. Broad collection before a hypothesis tends to produce a large folder and a weak diagnosis. Broadening after the core case guards against the most obvious regression.

Page identity is evidence

Version 1.6.0 included several fixes that sound like housekeeping until a coding agent is juggling tabs. The release keeps a selected page when it is still open instead of falling back to the first page, keeps page IDs unique across browser reconnects, resolves IDs only among listed pages, and reports when fallback replacement occurs. It also fixed first-page pagination for console and network listings.

These details affect whether an evidence packet describes the intended page. Record the page list and selected identity at the start. Record it again after a navigation, reconnect, dialog, or target close. If the server reports a fallback, stop and rebind the case before interpreting old console messages as evidence about the new page.

Concurrent agents make that obligation sharper. The project documents --experimentalPageIdRouting for a server shared across agents, and --isolated for independent sessions that need separate temporary profiles. Neither flag assigns ownership by itself. The orchestration layer still needs to decide which worker owns which target and to prevent two workers from diagnosing the same tab under different assumptions.

The tempting escape hatch is not a sandbox

Our probe did not advertise the MCP roots capability. The server printed a useful warning: file-writing tools would be restricted to the operating system’s temporary directory. It also mentioned --allow-unrestricted-paths as a way to restore the previous broad behavior. The warning is an admission record, not an invitation.

The project’s security policy is explicit about the boundary. MCP roots can constrain file access for operations that honor them, but they are optional and the temporary directory remains accessible. The maintainers recommend an operating-system sandbox when full filesystem isolation matters. Similarly, allowed and blocked URL patterns are guardrails for attached Chrome targets, not a complete network sandbox.

That yields a less convenient but more honest setup:

  • Use a dedicated browser profile or an isolated temporary profile; do not expose personal tabs.
  • Keep production credentials out of the test unless the incident specifically requires a supervised authenticated case.
  • Negotiate the narrow roots the workflow needs and retain the server’s boundary warning in the run record.
  • Use OS or VM isolation for filesystem and network guarantees that the MCP server does not claim to provide.
  • Keep --allow-unrestricted-paths off unless a trusted local client has a documented need and an external sandbox supplies the missing boundary.

Browser visibility also does not grant business authority. A tool can inspect a purchase flow without being allowed to submit payment. It can diagnose an admin page without being allowed to change permissions. The evidence packet should name those stop lines before the first click.

Memory tools deserve a different case file

With --memoryDebugging=true, the 1.6.0 server advertised eleven heap tools in this run, including snapshot comparison, duplicate strings, dominators, retainers, and retaining paths. The release also added aggregate filters plus object counts and total sizes in snapshot details.

I did not take a heap snapshot, so this article does not claim a memory result. That negative statement matters. Tool availability is not evidence that a leak exists, and one larger snapshot is not evidence of a leak either. A defensible memory case needs matched warm-up, navigation count, snapshot points, garbage-collection policy, and a retaining path that explains monotonic growth across repeated cycles. It should be a separate packet because its timing and resource controls are different from a failed fetch.

The same restraint applies to Lighthouse and performance traces. They are useful when the acceptance rule concerns accessibility, SEO, or runtime cost. Running every diagnostic surface after every click dilutes the incident record and may send a trace URL to CrUX unless that integration is disabled. Collect the artifact that can falsify the current hypothesis.

Checklist for the pull request

A reviewer should not have to replay an agent transcript to learn what changed. The smallest useful packet is compact enough to read and specific enough to reproduce:

Case identity

Target, package and Chrome versions, selected page, viewport, profile mode, and run timestamp.

Observed failure

Exact reproduction step plus the snapshot, console item, request, trace insight, or retaining path that supports it.

One hypothesis

A falsifiable link from the evidence to a named source or lifecycle path—not a list of possible fixes.

Focused change

Files changed, relevant code-level test, diff summary, and behavior deliberately left untouched.

Matched verification

The same measurement before and after, including request count and user-visible terminal state.

Boundary notes

Credentials, roots, URL policy, sandbox arrangement, external side effects, and approvals that were absent or held.

The retained probe for this article can defend a modest claim: Chrome DevTools MCP 1.6.0 exposed the expected diagnostic surface, and after one recorded browser-start recovery it bound semantic state, runtime action, console output, and network failure to one selected mobile-sized page. It cannot defend a claim about a repaired application because none was repaired.

That is the browser debugging loop worth automating. Make the environment failure visible. Bind evidence to one case. Change one responsible unit. Repeat the same observation. If the packet cannot tell another engineer which page failed and why, more autonomous clicking will not rescue it.


Primary sources: the official Chrome DevTools MCP 1.6.0 documentation, v1.6.0 release notes, security policy, and troubleshooting guide. The local protocol fixture and its result are retained with this remediation batch.