Important
This repo is no longer the recommended path. Use Browser Use. Browser Use 1.0 is where this work continues: your coding agent drives Chrome over raw CDP with the browser-harness helpers, by piping Python to the browser-use CLI or through its MCP server.
uv tool install browser-use
browser-use <<'PY'
new_tab("https://example.com")
print(page_info())
PYMCP server: browser-use --cli-mcp. This repo stays up as the JS/TS CDP bridge, for reference.
The thinnest possible bridge from the LLM to Chrome. No harness, no recipes, no rails — just every CDP method as a typed JS call.
One persistent WebSocket, 56 domains, 652 typed wrappers, zero wrapping of what Chrome already does.
● agent: wants to click a button
│
● no click() helper, no upload_file(), no goto()
│
● agent writes the CDP call itself await session.Input.dispatchMouseEvent({...})
│ await session.DOM.setFileInputFiles({...})
✓ done — same pattern for all 652 methods
The protocol is the API. If Chrome can do it, you can call it.
For new setups use Browser Use (see the top of this page). To run this repo anyway:
npx skills add https://github.com/browser-use/browser-harness-js --skill cdpOr paste this into your agent — it'll install the skill, put the CLI on your PATH, and run a first task:
Run `npx skills add https://github.com/browser-use/browser-harness-js --skill cdp`, then
symlink `browser-harness-js` into a directory on my PATH, then use the cdp skill to drive
my browser: look at all the tabs I have open, group them by topic, and screenshot the most
interesting one.
(The CLI auto-installs bun on first run if it's missing. Set BROWSER_HARNESS_SKIP_BUN_INSTALL=1 to opt out.)
If Chrome asks you to tick a remote-debugging checkbox, do it — that's how the agent attaches:
See interaction-skills/ for recipes on the mechanics that are not obvious from the CDP method list alone.
SKILL.md— day-to-day usage; how to connect, pick a tab, call methods, persist statesdk/browser-harness-js— tiny CLI that auto-spawns the server and forwards snippetssdk/repl.ts— Bun HTTP server holding one persistentSessionsdk/session.ts— theSessionclass: transport, connect, target routing, eventssdk/gen.ts— codegen: readsbrowser_protocol.json+js_protocol.json→ typed wrapperssdk/generated.ts— every CDP method assession.<Domain>.<method>(params)(generated)
No helpers file. No click(), no goto(), no upload_file() — just the protocol, typed.
Every helper is a lie about what CDP already gives you. click(x, y) hides Input.dispatchMouseEvent — which has 14 parameters the LLM might need (button, clickCount, modifiers, pointerType, force, tangentialPressure, …). A harness that exposes three of them quietly limits what the agent can do.
- Types are the docs.
session.Page.navigate(triggers autocomplete with the exact params — same JSDoc as the CDP reference. - No version drift. The SDK is regenerated from the upstream protocol JSON; new Chrome methods appear as soon as you swap the JSON.
- No "helper doesn't handle my case" detours. If CDP can do it, the agent can call it — directly, typed, today.
The only "helpers" you'll find are things CDP itself is missing:
listPageTargets()— filterschrome:///devtools://out ofTarget.getTargetsresolveWsUrl({wsUrl|port|profileDir})— readsDevToolsActivePortfor Chrome 144+session.use(targetId)/session.waitFor(method, pred, timeout)— the two routing primitives you genuinely need
PRs welcome. The best way to help: contribute a new interaction skill under interaction-skills/ when you figure out the CDP recipe for something non-obvious (a dropdown framework, a shadow-DOM trap, a network-wait pattern).
- Keep recipes in pure CDP —
session.Domain.method(...), not wrapped helpers. - Lead with the shortest method call that works; add the workaround or trap afterwards.
- Small and focused beats comprehensive. One mechanic per file.
- Bug fixes, codegen improvements, and
session.tsrefinements are equally welcome.

