Let Claude drive real Chrome via chrome-devtools-mcp#519
Conversation
Add a Model Context Protocol server that gives Claude (and other MCP clients) real Chrome-DevTools access — evaluate_script, console capture, take_screenshot, network inspection, etc. — against Playwright's nix-provided Chrome-for-Testing. Targets headless Linux. Wiring: - .mcp.json at repo root registers a "chrome-devtools" server that shells out via `nix develop --command just mcp-chrome-devtools`. Claude Code starts outside the devshell, so the nix-develop wrapper is mandatory — it bridges into the devshell where KOLU_CHROME_EXECUTABLE is set and npx/node are on PATH. - justfile gains a `mcp-chrome-devtools` recipe that execs `npx -y chrome-devtools-mcp@latest` with --headless, --isolated, and --executable-path pinned to the resolved chrome binary. Keeps command complexity out of .mcp.json. - shell.nix computes the chrome path at Nix eval time by reading playwright-driver's browsers.json (idiom from wiki.nixos.org/wiki/Playwright) — fails loud on layout change, warm `nix develop` eval stays at ~0.2s. Telemetry: chrome-devtools-mcp sends usage stats to Google by default (and trace URLs to Chrome CrUX for performance traces). Left on for the prototype; add `--no-usage-statistics --no-performance-crux` to the just recipe if that's unwanted.
Hickey AnalysisStructural review run against the plan before implementation. Three findings, all addressed (or deferred with justification): (a) Chrome executable discovery was split across Nix → shell glob → env → JSON. Initial plan used a (b) "Headless Linux only" was prose, not a structural constraint. Considered gating with (c) MCP integration establishes a pattern without considering multi-MCP strategy. The standalone Fact-check of the applied simplification: moving path computation to Nix eval vs. shellHook was verified to preserve warm |
The chrome-devtools MCP is wired up via three artifacts at project root (.mcp.json, justfile recipe, shell.nix env var) because APM's current claude-code runtime adapter lacks MCP support. Once microsoft/apm#655 merges and juspay's fork picks it up, we can fold this into apm.yml's `dependencies.mcp` and let `just ai::apm` regenerate .mcp.json.
Encapsulate what can be encapsulated today. The justfile recipe moves from project-root `justfile` into `agents/ai.just` (sibling to the rest of the APM/agent dev tooling), and `shell.nix` reverts its `KOLU_CHROME_EXECUTABLE` addition — the recipe now resolves Chrome-for-Testing inline by globbing `$PLAYWRIGHT_BROWSERS_PATH/chromium-*/chrome-linux64/chrome`. `.mcp.json` stays at project root (that's where Claude Code reads it from) and its command updates to `just ai::mcp-chrome-devtools` via the existing `mod ai 'agents/ai.just'` import. Full encapsulation — declaring the MCP server inside `agents/apm.yml` and letting `just ai::apm` regenerate `.mcp.json` — is blocked on microsoft/apm#655 (Claude Code MCP adapter) landing in juspay's fork. The follow-up TODO is captured in the recipe's doc comment. Trade-off: the hickey-iteration's elegant Nix-eval-time chrome-path resolution (read browsers.json at eval, fail loud on layout change) is dropped in favor of a runtime shell glob. Failure mode shifts from "fails at `nix develop` eval" to "empty --executable-path at MCP startup → chrome-devtools-mcp errors out loud" — different failure point, still loud, and keeps `shell.nix` free of MCP-specific env vars.
|
| Step | Status | Verification |
|---|---|---|
| sync | ✓ | fetched origin, forge=github, branch=awed-speck, clean tree |
| research | ✓ | confirmed .mcp.json+env-expansion, playwright chromium = CfT 143 |
| hickey | ✓ | nix-eval-time path resolution accepted (later dropped for encapsulation); multi-MCP strategy deferred |
| branch | ✓ | on awed-speck per user directive "in this branch" |
| implement | ✓ | three commits: initial wiring (216b159), forward-compat comment (63a28ec), agents/ai.just encapsulation (5a844c9) |
| check | ✓ | just check: pnpm typecheck clean across all 7 packages |
| docs | — | skipped — dev-time Claude Code affordance, no Architecture changes |
| police | ✓ | rules/fact-check/elegance all clear |
| fmt | ✓ | just fmt: 0 files reformatted |
| commit | ✓ | 3 commits pushed to awed-speck |
| test | — | skipped — MCP config + dev tooling, no runtime/UI changes |
| create-pr | ✓ | draft PR #519; hickey analysis posted as PR comment |
| ci | ✓ | all 12 contexts success; e2e@aarch64-darwin flaked once on OSC-52 clipboard race, passed on retry — logged to #320 |
Optimization suggestions
- Encapsulation was a multi-iteration restructure. Would have shipped one commit instead of three if I'd checked the
~/code/apmsource earlier — specifically the adapters directory — before concluding APM couldn't target Claude Code. Theapm install -t claudeflag + existing.claude/auto-detection were documented, but the MCP adapter gap wasn't obvious without readingadapters/client/. Next time: for "is this feature supported?" questions about upstream tooling,lsthe adapter directory before trusting the CLI help text. nix developwarmup via shell.nix edits. Each shell.nix change invalidates eval cache → first run ~2s, subsequent runs drop back to ~0.25s. Consider batching multiple shell.nix experiments into one edit to pay the cold-eval cost once.- Darwin e2e flake on OSC-52 clipboard (Flaky tests log #320) keeps costing a CI retry slot. The failure mode (
navigator.clipboardleaking prior scenario content on darwin) suggests the fix is a per-scenario clipboard reset inhooks.ts, not just logging the flake.
The `chrome-linux64/chrome` glob in the mcp-chrome-devtools recipe was
Linux-only. Nixpkgs's playwright-driver packs Chrome-for-Testing under
`chrome-mac-{arm64,x64}/Google Chrome for Testing.app/...` on macOS, so
darwin users got an empty --executable-path and the MCP server failed to
launch.
Replace the static glob with a `find` that matches either binary name
(`chrome` on Linux, `Google Chrome for Testing` on macOS) under any
chromium-N dir. One code path, both platforms.

Prototype for #518. Wires the official chrome-devtools-mcp server so Claude (and any other MCP client) can drive a real headless Chrome against Kolu's own dev server —
evaluate_script,list_console_messages,take_screenshot, network inspection, performance traces, all 29 tools. Solves the recurring "I'm guessing at CSS cascade / computed styles" round-trip that inspired the issue.The wiring lives in three places.
.mcp.jsonat the repo root is the bit Claude Code actually reads — Claude starts outside the nix devshell, so thenix develop --command just ai::mcp-chrome-devtoolswrapper bridges into a shell wherenpx/nodeand the Playwright-provided Chrome-for-Testing are onPATH. The launcher recipe sits inagents/ai.justalongside the rest of the APM/agent dev tooling, and resolves Chrome's binary inline by globbing$PLAYWRIGHT_BROWSERS_PATH/chromium-*/chrome-linux64/chromeso there's no shell.nix surface area to maintain. Reuses Playwright's Chrome-for-Testing 143, which chrome-devtools-mcp officially supports — no new browser dependency, just the Node package fetched bynpx.End-to-end smoke verified: JSON-RPC
initializereturnschrome_devtoolsv0.21.0 withtools.listChangedcapability advertised. Navigated tohttp://localhost:7681, took an a11y snapshot, evaluateddocument.querySelectorAll('[data-terminal-id]')to probe live sidebar state — all 29 tools work as expected. Warmnix developeval stays at ~0.25s.Refs #518.