Skills I Use: chrome-cdp
Almost everything I hand an agent lives in the terminal. The exception is the browser. A lot of my work ends in a web
app: checking a flow after a change, grabbing screenshots for a pull request, reading what a page actually rendered.
The agent couldn’t see any of it. chrome-cdp is the skill I wrote to fix that.
The problem with a fresh browser
Most browser automation starts a clean browser, and clean means logged out. The apps I care about sit behind SSO and 2FA, so a fresh headless Chrome hits a login wall before it does anything useful. Handing the agent my password isn’t an option.
So the skill skips that problem entirely. It connects to a real Chrome over the Chrome DevTools Protocol (CDP), with my session already in it.
How it works
Three pieces.
chrome-debug, a small shell script. Chrome 136 and later won’t expose a remote debugging port on your normal
profile directory, so the script copies my profile into a separate debug directory, starts Chrome with
--remote-debugging-port, and prints the port. The copy brings the cookies along, so I’m already logged in. It also
swaps the profile picture for a 🐞, so I always know which window belongs to the agent.
cdp.mjs, a driver template. Playwright’s connectOverCDP attaches to the running browser, and a few helpers
cover what comes up every time: pick an open tab, open a background tab, click by text or href, and screenshot the
smallest card that matches some text. The agent copies the template, edits only the actions block, runs it, and
deletes it.
// ---------------- ACTIONS (edit me) ----------------
const page = await backgroundPage("example.com");
await page.goto("https://example.com/settings", { waitUntil: "commit" });
await page.waitForSelector("[role=main]");
console.log("URL:", page.url());
await shotCard(page, [/Notifications/, /Email/], "settings-card.png");
// ---------------------------------------------------
SKILL.md, the workflow and the rules. This is the part that makes it something I trust in my real browser.
The rules
- Reuse before starting. If a debug Chrome is already running, use it. Never start another one.
- Don’t steal focus. New tabs open in the background through CDP’s
Target.createTarget, and the agent never callsbringToFront(). I keep working in my own windows while it works in its tab. - Don’t act for me. It’s my real session, so no submitting, saving, publishing, deleting, buying, or agreeing to anything unless I asked.
- Stable selectors only.
aria-label, roles,data-*attributes, and visible text. Never hashed class names, which change on every build. - Stop at a login wall. If a page wants a login or 2FA, the agent asks me to do it.
- Look at the screenshot. The agent opens every PNG it captures before it says the task is done.
- Disconnect, don’t close. When it finishes, it drops the connection. My Chrome and my tabs stay put.
App references
The skill is generic. Anything specific to one app lives in references/<app>.md: URLs, redirects, reliable
selectors, and what not to touch. It started as a screenshot skill for a single app, and splitting the app details
out is what made it useful everywhere else. Adding an app is one file and one row in a table.
What I use it for
- Screenshots of a UI flow for a pull request.
- Checking a change in the real app, with real data.
- Reading the DOM to see what a page actually rendered, not what I think it rendered.
In my MCP post I said a live browser session is the one place MCP really holds up. This is that same session without a server: a script, a port, and Playwright as a library.