ItaiZeilig/pawbrowse

Let Claude Code drive your real, logged-in Chrome. MV3 extension + zero-dep MCP server, element-table perception over CDP. The agent is the policy: no second model, no API keys.

JavaScript

6

89 commits

updated Sep 27, 2026

See the code

See what people are saying

SourceMessageScoreDate

PawBrowse - Browser automation for AI agents that's ~2x faster (reads the page as a table, not screenshots) - open source (r/SideProject)

The pain: most browser agents drive off screenshots, so every action is perceive-then-act = two round-trips, and each round-trip is a full model inference. On a real task that doubles your tool calls, your latency, and your token bill. PawBrowse fixes that. It's an open-source Chrome extension + a…

1

Sep 27, 2026

README

PawBrowse — Claude Code drives your real Chrome

PawBrowse

Let your AI coding agent drive the real Chrome you already use — logged in, no keys, no second AI.

npm Chrome Web Store CI MIT zero dependencies for Claude Code MCP

A real Google Flights run at 1× speed: Zürich to London, the cheapest nonstop flight opened in 6.8 seconds, each targeted element highlighted with its ref

A real run on live Google Flights at 1× speed — every frame is the original screencast, and the result is checked from the page itself. MP4

PawBrowse gives your coding agent hands and eyes in the browser you're already signed into. It's a small Chrome extension + a one-file MCP server (zero dependencies). Your agent — Claude Code, Cursor, VS Code, Claude Desktop, anything that speaks MCP — can see and click your actual tabs: your profile, your logins, your open pages. No remote-debug port, no relaunch, no second AI model, no API key. The agent you already trust is the only brain in the loop.

The same power as the first-party Claude-in-Chrome extension — but yours, open source, auditable, and about 2× fewer round-trips because it reads pages as a table instead of screenshotting them.


Try it — about 30 seconds

Two clicks and one line. 👇

1. Add the extension to Chrome

Add to Chrome

One click on the Chrome Web Store — that's the "hands and eyes" in your browser.

2. Connect your agent

Add to Cursor Install in VS Code Add to Claude Desktop

  • Claude Desktop — click the badge, then double-click the downloaded pawbrowse.mcpb (or drag it into Settings → Extensions) and hit Install. No command, no config.

  • Cursor / VS Code — click the badge and approve.

  • Claude Code — paste one line:

    claude mcp add --scope user pawbrowse -- npx -y pawbrowse@latest
    

3. Restart your client and just ask

"use pawbrowse: what's my browser status?"

You should see extension_connected: true, and the extension badge turns green ●. 🎉 You're driving.

Needs Node.js ≥ 18. Nothing to clone or build — the button/line just runs npx -y pawbrowse@latest. Prefer to build from source or contribute? See CONTRIBUTING.md.

What can I ask it?

You never call the tools yourself — you talk to your agent in plain English, and it drives whatever tab you point it at:

  • "Open news.ycombinator.com and give me the top 5 story titles."
  • "On this tab, search for 'open source license' and open the first result."
  • "Fill the signup form with my name and email — but don't submit."
  • "Go to my GitHub notifications and tell me what's new."

It works on the tab you already have open and are logged into — no separate window, no re-login. Name a tab and it uses that one; otherwise it uses the active tab.

How it works — it reads pages as a table, not screenshots

Every time it looks, PawBrowse hands your agent a compact, numbered list of the clickable things in view — with real accessible names, current values, and state flags — instead of a screenshot:

Web browser - Wikipedia  —  https://en.wikipedia.org/wiki/Web_browser
scroll 0/6361  ·  83 controls
e2   fill    "Search Wikipedia"
e6   click   "Log in"
e10  click   "2 History"
e13  click ▾ "Toggle Browser market subsection"
e9   click✓  "Remember me"
e3   select  "Country"  opts{US | UK | ...}

Flags after the kind: ✓/· checked/unchecked · ▾/▸ expanded/collapsed · ◉ selected.

Each ref (e10) is a stable handle to a real element, and every action returns the fresh table. So your agent acts in one round-trip — no "screenshot, think, screenshot again." That's the whole speed story (benchmark below), and it's cheaper in tokens too.

Under the hood it's boringly simple: a Chrome MV3 extension drives your tabs through Chrome's built-in chrome.debugger (CDP — no debug port, no relaunch), and a one-file MCP server bridges it to your agent over localhost. Open as many editors/agents as you like — the first one starts a shared broker, and each session gets its own 🐾 tab group so nothing fights over the port or a tab.

Claude Code ──stdio (MCP)──▶ mcp/server.mjs ─┐
Cursor      ──stdio (MCP)──▶ mcp/server.mjs ─┼─IPC─▶ broker ──ws://127.0.0.1:10577──▶ Chrome extension ──CDP──▶ your real tabs
VS Code     ──stdio (MCP)──▶ mcp/server.mjs ─┘                                              │
                                                                    each session ⇒ its own 🐾 tab group

Why it's fast

Side-by-side benchmark on live Booking.com: the same agent books a 4-star Lisbon hotel through PawBrowse in 5 agent round-trips and through Claude in Chrome in 10, so PawBrowse finishes while Claude in Chrome is halfway

Same task on live Booking.com (book a 4-star Lisbon hotel — 5 actions), same agent (Claude), same real Chrome, both filmed live. The clock counts agent round-trips: one tool call = one round-trip, at the same per-call latency for both. MP4

PawBrowseClaude in Chrome
Round-trips for the 5 actions5 — 1 per action10 — 2 per action (read, then act)
Screenshots / reads to see the page0 — every act returns the fresh table5 — one before each action
Relative agent time1×~2×

Why: the agent loop is round-trip-bound — each tool call is a full model inference. PawBrowse's act returns the next screen already perceived, so an action is one round-trip; a perceive-then-act driver needs two. Same task, same model → same per-call latency, so the round-trip count is the gap: 5 vs 10 → ~2×.

Honest caveats: the clock is round-trips × a fixed, equal per-call latency — it isolates the structural difference and drops network noise, it's not a stopwatch. One run each; an illustration, not a statistic. Reproduce: scripts/demo/peek-record.mjs films each tab, scripts/demo/render_race.py renders it.

How it compares

Claude-in-ChromePawBrowse
Drives your real, logged-in Chrome✅✅ (chrome.debugger, no port)
Decision modelClaudeClaude — no second model, no key
Perceptionscreenshots + a11y treecompact element table
Round-trips per action2 (perceive → act)1 (stable refs)
Page data to a third partynono
Per-site permission gateyes (allowlist)no
Open source / self-owned❌✅ MIT, zero-dep
Works with any MCP client❌✅

The tools

You won't call these directly — your agent does — but here's the whole surface:

ToolWhat it does
browser_statusConnection + attached-tab diagnostics. Call first if anything's off.
browser_tabsList open tabs (id, title, url, active).
browser_navigate{ url, tabId? } → element table after load.
browser_observe{ tabId? } → the element table.
browser_read{ tabId?, max_chars? } → the page's readable prose (articles, docs, rules).
browser_act{ ops: [...], tabId? } → runs ops in order, returns a fresh table + a "page changed?" signal.
browser_assert{ contains? | url_includes? | ref_visible?, tabId? } → prove an outcome (pass/fail).

Ops for browser_act: {op:"click",ref:"e12"} · {op:"click_text",text:"..."} (for custom widgets/menus not in the table) · {op:"type",ref:"e7",text:"..."} · {op:"select",ref:"e8",value:"..."} · {op:"key",key:"Enter"} · {op:"scroll",dy:600} · {op:"wait",ms:500}.

Built to be trustworthy

Two questions everyone has: does it break? and where does my data go?

It's hardened. Two multi-agent code audits plus live testing on real sites went into these:

  • Hit-tested clicks — it re-resolves each element live and checks the center isn't covered before clicking, so it never hits a stale, moved, or occluded target.
  • Semantic freshness guard — an element's role + name is fingerprinted, and a silently relabeled target is rejected ("observe again") instead of mis-clicked.
  • Robust typing — select-all + insertText (works with React/controlled inputs); typed comboboxes wait for autocomplete to render.
  • Background-tab safe — uses setTimeout-based waits (not requestAnimationFrame, which Chrome pauses in background tabs), so driving a tab you aren't looking at doesn't hang.
  • No double-execution & an unwedgeable queue — overlapping calls can't race the debugger, and one hung command can't block the rest.

It stays on your machine. No model, no API key, no telemetry.

  • Page content it reads goes only to the local agent you run — never to any third-party server. password, file, and hidden inputs are excluded and never exposed (other visible fields are in the table, so treat what's on screen as visible to your agent).
  • The bridge binds to 127.0.0.1, rejects non-chrome-extension:// origins, and trusts only the current extension socket. It assumes other software on your machine is trusted — the same as most localhost dev tools; a per-pair token is planned hardening.
  • The extension declares debugger (plus tabs, storage, alarms) and no host permissions. The only thing stored is your bridge port number.
  • Fully auditable: the server is one zero-dependency file, the extension is plain JS.

Full details: SECURITY.md · PRIVACY.md. Found a vulnerability? Please don't open a public issue — see SECURITY.md.

Honest limits

  • Attaching shows Chrome's "PawBrowse is debugging this browser" banner — expected.
  • One debugger client per tab: a tab with DevTools open (or driven by another extension) can't be attached — switch tabs or close DevTools.
  • chrome://, the Chrome Web Store, and other browser pages can't be driven (Chrome blocks automation there).
  • One active client at a time — the bridge is a single localhost port, so drive from one client at a time (Claude Code or Claude Desktop). A second client says the port's in use rather than failing hard; set a different PAWBROWSE_PORT if you need both.
  • Enumerates shadow DOM + same-origin iframes. Controls inside open shadow roots and same-origin iframes are in the table and clickable. Not yet: cross-origin iframes (the browser blocks them), canvas, and file uploads.

Troubleshooting

SymptomFix
Badge never turns greenThe server isn't running — make sure you fully restarted your client after claude mcp add (a /mcp reconnect alone won't relaunch it).
"No extension connected"Reload the extension at chrome://extensions, then re-run browser_status.
"Another debugger is already attached"That tab has DevTools open or another extension driving it — close DevTools or switch tabs.
A chrome:// / Web Store page won't driveThose are browser pages Chrome blocks from automation — use a normal web page.
Changed the portSet the same port in the extension's Options and in --env PAWBROWSE_PORT=….

Requires Node ≥ 18 (≥ 22 to run the test suite). Works on Chrome, Edge, and Brave.

Contributing

PRs welcome! See CONTRIBUTING.md for dev setup and tests (npm test), and the Code of Conduct. Questions? SUPPORT.md.

Credits

Built with Claude Code. Some page-perception and action-execution techniques are adapted from browser-use/jev-ultrafast (MIT); this credit is kept as required by that project's license.

License

MIT © PawBrowse contributors.

browser-automation
cdp
chrome-extension
claude
claude-code
mcp

Contributors

ItaiZeilig

89 commits

ItaiZeilig/pawbrowse

Let Claude Code drive your real, logged-in Chrome. MV3 extension + zero-dep MCP server, element-table perception over CDP. The agent is the policy: no second model, no API keys.

JavaScript

6

89 commits

updated Sep 27, 2026

See the code

See what people are saying

SourceMessageScoreDate

PawBrowse - Browser automation for AI agents that's ~2x faster (reads the page as a table, not screenshots) - open source (r/SideProject)

The pain: most browser agents drive off screenshots, so every action is perceive-then-act = two round-trips, and each round-trip is a full model inference. On a real task that doubles your tool calls, your latency, and your token bill. PawBrowse fixes that. It's an open-source Chrome extension + a…

1

Sep 27, 2026

README

PawBrowse — Claude Code drives your real Chrome

PawBrowse

Let your AI coding agent drive the real Chrome you already use — logged in, no keys, no second AI.

npm Chrome Web Store CI MIT zero dependencies for Claude Code MCP

A real Google Flights run at 1× speed: Zürich to London, the cheapest nonstop flight opened in 6.8 seconds, each targeted element highlighted with its ref

A real run on live Google Flights at 1× speed — every frame is the original screencast, and the result is checked from the page itself. MP4

PawBrowse gives your coding agent hands and eyes in the browser you're already signed into. It's a small Chrome extension + a one-file MCP server (zero dependencies). Your agent — Claude Code, Cursor, VS Code, Claude Desktop, anything that speaks MCP — can see and click your actual tabs: your profile, your logins, your open pages. No remote-debug port, no relaunch, no second AI model, no API key. The agent you already trust is the only brain in the loop.

The same power as the first-party Claude-in-Chrome extension — but yours, open source, auditable, and about 2× fewer round-trips because it reads pages as a table instead of screenshotting them.


Try it — about 30 seconds

Two clicks and one line. 👇

1. Add the extension to Chrome

Add to Chrome

One click on the Chrome Web Store — that's the "hands and eyes" in your browser.

2. Connect your agent

Add to Cursor Install in VS Code Add to Claude Desktop

  • Claude Desktop — click the badge, then double-click the downloaded pawbrowse.mcpb (or drag it into Settings → Extensions) and hit Install. No command, no config.

  • Cursor / VS Code — click the badge and approve.

  • Claude Code — paste one line:

    claude mcp add --scope user pawbrowse -- npx -y pawbrowse@latest
    

3. Restart your client and just ask

"use pawbrowse: what's my browser status?"

You should see extension_connected: true, and the extension badge turns green ●. 🎉 You're driving.

Needs Node.js ≥ 18. Nothing to clone or build — the button/line just runs npx -y pawbrowse@latest. Prefer to build from source or contribute? See CONTRIBUTING.md.

What can I ask it?

You never call the tools yourself — you talk to your agent in plain English, and it drives whatever tab you point it at:

  • "Open news.ycombinator.com and give me the top 5 story titles."
  • "On this tab, search for 'open source license' and open the first result."
  • "Fill the signup form with my name and email — but don't submit."
  • "Go to my GitHub notifications and tell me what's new."

It works on the tab you already have open and are logged into — no separate window, no re-login. Name a tab and it uses that one; otherwise it uses the active tab.

How it works — it reads pages as a table, not screenshots

Every time it looks, PawBrowse hands your agent a compact, numbered list of the clickable things in view — with real accessible names, current values, and state flags — instead of a screenshot:

Web browser - Wikipedia  —  https://en.wikipedia.org/wiki/Web_browser
scroll 0/6361  ·  83 controls
e2   fill    "Search Wikipedia"
e6   click   "Log in"
e10  click   "2 History"
e13  click ▾ "Toggle Browser market subsection"
e9   click✓  "Remember me"
e3   select  "Country"  opts{US | UK | ...}

Flags after the kind: ✓/· checked/unchecked · ▾/▸ expanded/collapsed · ◉ selected.

Each ref (e10) is a stable handle to a real element, and every action returns the fresh table. So your agent acts in one round-trip — no "screenshot, think, screenshot again." That's the whole speed story (benchmark below), and it's cheaper in tokens too.

Under the hood it's boringly simple: a Chrome MV3 extension drives your tabs through Chrome's built-in chrome.debugger (CDP — no debug port, no relaunch), and a one-file MCP server bridges it to your agent over localhost. Open as many editors/agents as you like — the first one starts a shared broker, and each session gets its own 🐾 tab group so nothing fights over the port or a tab.

Claude Code ──stdio (MCP)──▶ mcp/server.mjs ─┐
Cursor      ──stdio (MCP)──▶ mcp/server.mjs ─┼─IPC─▶ broker ──ws://127.0.0.1:10577──▶ Chrome extension ──CDP──▶ your real tabs
VS Code     ──stdio (MCP)──▶ mcp/server.mjs ─┘                                              │
                                                                    each session ⇒ its own 🐾 tab group

Why it's fast

Side-by-side benchmark on live Booking.com: the same agent books a 4-star Lisbon hotel through PawBrowse in 5 agent round-trips and through Claude in Chrome in 10, so PawBrowse finishes while Claude in Chrome is halfway

Same task on live Booking.com (book a 4-star Lisbon hotel — 5 actions), same agent (Claude), same real Chrome, both filmed live. The clock counts agent round-trips: one tool call = one round-trip, at the same per-call latency for both. MP4

PawBrowseClaude in Chrome
Round-trips for the 5 actions5 — 1 per action10 — 2 per action (read, then act)
Screenshots / reads to see the page0 — every act returns the fresh table5 — one before each action
Relative agent time1×~2×

Why: the agent loop is round-trip-bound — each tool call is a full model inference. PawBrowse's act returns the next screen already perceived, so an action is one round-trip; a perceive-then-act driver needs two. Same task, same model → same per-call latency, so the round-trip count is the gap: 5 vs 10 → ~2×.

Honest caveats: the clock is round-trips × a fixed, equal per-call latency — it isolates the structural difference and drops network noise, it's not a stopwatch. One run each; an illustration, not a statistic. Reproduce: scripts/demo/peek-record.mjs films each tab, scripts/demo/render_race.py renders it.

How it compares

Claude-in-ChromePawBrowse
Drives your real, logged-in Chrome✅✅ (chrome.debugger, no port)
Decision modelClaudeClaude — no second model, no key
Perceptionscreenshots + a11y treecompact element table
Round-trips per action2 (perceive → act)1 (stable refs)
Page data to a third partynono
Per-site permission gateyes (allowlist)no
Open source / self-owned❌✅ MIT, zero-dep
Works with any MCP client❌✅

The tools

You won't call these directly — your agent does — but here's the whole surface:

ToolWhat it does
browser_statusConnection + attached-tab diagnostics. Call first if anything's off.
browser_tabsList open tabs (id, title, url, active).
browser_navigate{ url, tabId? } → element table after load.
browser_observe{ tabId? } → the element table.
browser_read{ tabId?, max_chars? } → the page's readable prose (articles, docs, rules).
browser_act{ ops: [...], tabId? } → runs ops in order, returns a fresh table + a "page changed?" signal.
browser_assert{ contains? | url_includes? | ref_visible?, tabId? } → prove an outcome (pass/fail).

Ops for browser_act: {op:"click",ref:"e12"} · {op:"click_text",text:"..."} (for custom widgets/menus not in the table) · {op:"type",ref:"e7",text:"..."} · {op:"select",ref:"e8",value:"..."} · {op:"key",key:"Enter"} · {op:"scroll",dy:600} · {op:"wait",ms:500}.

Built to be trustworthy

Two questions everyone has: does it break? and where does my data go?

It's hardened. Two multi-agent code audits plus live testing on real sites went into these:

  • Hit-tested clicks — it re-resolves each element live and checks the center isn't covered before clicking, so it never hits a stale, moved, or occluded target.
  • Semantic freshness guard — an element's role + name is fingerprinted, and a silently relabeled target is rejected ("observe again") instead of mis-clicked.
  • Robust typing — select-all + insertText (works with React/controlled inputs); typed comboboxes wait for autocomplete to render.
  • Background-tab safe — uses setTimeout-based waits (not requestAnimationFrame, which Chrome pauses in background tabs), so driving a tab you aren't looking at doesn't hang.
  • No double-execution & an unwedgeable queue — overlapping calls can't race the debugger, and one hung command can't block the rest.

It stays on your machine. No model, no API key, no telemetry.

  • Page content it reads goes only to the local agent you run — never to any third-party server. password, file, and hidden inputs are excluded and never exposed (other visible fields are in the table, so treat what's on screen as visible to your agent).
  • The bridge binds to 127.0.0.1, rejects non-chrome-extension:// origins, and trusts only the current extension socket. It assumes other software on your machine is trusted — the same as most localhost dev tools; a per-pair token is planned hardening.
  • The extension declares debugger (plus tabs, storage, alarms) and no host permissions. The only thing stored is your bridge port number.
  • Fully auditable: the server is one zero-dependency file, the extension is plain JS.

Full details: SECURITY.md · PRIVACY.md. Found a vulnerability? Please don't open a public issue — see SECURITY.md.

Honest limits

  • Attaching shows Chrome's "PawBrowse is debugging this browser" banner — expected.
  • One debugger client per tab: a tab with DevTools open (or driven by another extension) can't be attached — switch tabs or close DevTools.
  • chrome://, the Chrome Web Store, and other browser pages can't be driven (Chrome blocks automation there).
  • One active client at a time — the bridge is a single localhost port, so drive from one client at a time (Claude Code or Claude Desktop). A second client says the port's in use rather than failing hard; set a different PAWBROWSE_PORT if you need both.
  • Enumerates shadow DOM + same-origin iframes. Controls inside open shadow roots and same-origin iframes are in the table and clickable. Not yet: cross-origin iframes (the browser blocks them), canvas, and file uploads.

Troubleshooting

SymptomFix
Badge never turns greenThe server isn't running — make sure you fully restarted your client after claude mcp add (a /mcp reconnect alone won't relaunch it).
"No extension connected"Reload the extension at chrome://extensions, then re-run browser_status.
"Another debugger is already attached"That tab has DevTools open or another extension driving it — close DevTools or switch tabs.
A chrome:// / Web Store page won't driveThose are browser pages Chrome blocks from automation — use a normal web page.
Changed the portSet the same port in the extension's Options and in --env PAWBROWSE_PORT=….

Requires Node ≥ 18 (≥ 22 to run the test suite). Works on Chrome, Edge, and Brave.

Contributing

PRs welcome! See CONTRIBUTING.md for dev setup and tests (npm test), and the Code of Conduct. Questions? SUPPORT.md.

Credits

Built with Claude Code. Some page-perception and action-execution techniques are adapted from browser-use/jev-ultrafast (MIT); this credit is kept as required by that project's license.

License

MIT © PawBrowse contributors.

browser-automation
cdp
chrome-extension
claude
claude-code
mcp

Contributors

ItaiZeilig

89 commits

Languages

JavaScript

83.5%

HTML

10.0%

Python

6.3%