Skip to content
Conduit

mcp server

herdr-browser

Watch your coding agents browse: a Herdr pane showing the Chrome that Claude Code, Codex, Cursor, Droid and Antigravity drive through chrome-devtools-mcp or Playwright MCP. Drop-in, no new agent tools

salamituns/herdr-browsercommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

This project does not publish an install method we can turn into a config. Follow the README instead.

README

synced Oct 26, 2026

herdr-browser

Watch your coding agents use the browser, inside Herdr.

Claude Code, Codex, Cursor, Droid and Antigravity drive Chrome through chrome-devtools-mcp or @playwright/mcp, but that Chrome is usually invisible. This plugin shows it in a Herdr pane next to the agent, live, and lets you click and type into the same page. Your agents keep the browser tools they already use: nothing new to install in the agent, nothing new for it to learn.

agent pane (workspace w8)                     browser pane (w8)
   └─ mcp.js / playwright-mcp.js ────┐   ┌── main.js (follows new tabs)
                                     ▼   ▼
                     headless Chrome for w8 (one per workspace)
                     started on first use, stopped when the workspace closes
  • A drop-in for chrome-devtools-mcp and @playwright/mcp. Point your existing MCP entry at the matching plugin launcher (src/mcp.js or src/playwright-mcp.js). Inside Herdr it drives the workspace's visible Chrome; anywhere else it runs the plain MCP server, unchanged.
  • The pane opens itself next to the agent the first time it uses a browser tool, and switches to each tab the agent opens.
  • One Chrome per Herdr workspace, with its own cookies and logins, started on the first browser call.
  • Take over at any time: mouse, keyboard, scrolling and the URL bar go to the same page. Phone emulation by the agent is shown at its real proportions.

v0.3.1, macOS and Linux. Looking for a general-purpose browser in the terminal instead? See Related projects.

Let your agent set it up

In an agent running inside a Herdr pane, paste:

Set up the herdr-browser Herdr plugin for me by following
https://raw.githubusercontent.com/salamituns/herdr-browser/main/agent-guide.md

agent-guide.md has the agent check the requirements, install the plugin, repoint your existing chrome-devtools and playwright entries (backing up each config file first) and verify that a pane opens. It asks before restarting agent sessions. To do it by hand, read on.

Requirements

  • Herdr ≥ 0.9.0
  • Node.js ≥ 22
  • Google Chrome. Set HERDR_BROWSER_CHROME to use a different binary.
  • An outer terminal with full Kitty graphics support: Ghostty (recommended), Kitty or WezTerm.

Warp: the image displays, but it flickers constantly. Herdr re-sends image placements on every UI redraw, and Warp repaints the image each time. Ghostty and Kitty don't. Run Herdr in Ghostty for this workflow. Your sessions persist, so you can attach the same session from both terminals.

Install

herdr plugin install salamituns/herdr-browser

There's no build step. To hack on it, clone the repo and run herdr plugin link <path> instead.

Check that your terminal can show images:

herdr plugin pane open --plugin devtuns.browser --entrypoint probe

If you see a red/green gradient, it works.

Connect your agents

Each launcher is a drop-in replacement for the MCP server it wraps. Inside a Herdr pane (HERDR_WORKSPACE_ID is set) it attaches to that workspace's visible Chrome; anywhere else (an IDE, a plain terminal, CI) it runs the plain server with your arguments, unchanged. Replace your existing entry rather than adding a second browser MCP: one tool set, and the agent can't pick the invisible browser by mistake.

chrome-devtools-mcp (src/mcp.js)

# Claude Code
claude mcp add --scope user chrome-devtools -- \
  node ~/.local/state/herdr/plugins/devtuns.browser/current/src/mcp.js

# Antigravity CLI (re-adding an existing name updates it)
agy mcp add chrome-devtools-mcp "$(command -v node)" \
  ~/.local/state/herdr/plugins/devtuns.browser/current/src/mcp.js

# Codex CLI
codex mcp add chrome-devtools -- "$(command -v node)" \
  ~/.local/state/herdr/plugins/devtuns.browser/current/src/mcp.js

Codex lists MCP tools as deferred (in ALL_TOOLS) rather than up front, and may answer that the browser tools are unavailable without looking. A line in ~/.codex/AGENTS.md fixes that, for example: "For anything in a browser, use the chrome-devtools MCP tools; they may be deferred, so look them up in ALL_TOOLS."

Cursor: set your chrome-devtools entry in ~/.cursor/mcp.json to this (use the full path), then approve it once for the CLI with cursor-agent mcp enable chrome-devtools:

"chrome-devtools": {
  "command": "node",
  "args": ["/Users/you/.local/state/herdr/plugins/devtuns.browser/current/src/mcp.js"]
}

Droid: set the chrome-devtools entry under mcpServers in ~/.factory/mcp.json. Droid needs full paths for both node (from command -v node) and mcp.js:

"chrome-devtools": {
  "type": "stdio",
  "command": "/opt/homebrew/bin/node",
  "args": ["/Users/you/.local/state/herdr/plugins/devtuns.browser/current/src/mcp.js"],
  "disabled": false
}

Droid treats MCP tool calls as high-risk, so in droid exec they need --auto high. Interactive sessions ask for approval as usual.

Keep any arguments you had (--headless, --isolated, --channel, ...). They apply outside Herdr; inside Herdr, flags that choose or launch a browser are dropped because the workspace's browser is used. To force the standalone behavior inside Herdr, set HERDR_BROWSER_MCP=standalone. If you uninstall the plugin, point the entry back at npx -y chrome-devtools-mcp@latest.

@playwright/mcp (src/playwright-mcp.js)

Same idea, different file. Point an existing playwright entry at .../current/src/playwright-mcp.js instead of npx @playwright/mcp@latest. Inside Herdr the launcher passes --cdp-endpoint to the workspace Chrome and drops flags that would launch or isolate a different browser (--browser, --isolated, --headless, --user-data-dir, --extension, --device, …). --isolated in particular would open a new browser context the pane does not follow. Keep those flags for when the same entry runs outside Herdr.

# Claude Code (use the scope your entry was in, e.g. --scope local)
claude mcp add --scope user playwright -- \
  node ~/.local/state/herdr/plugins/devtuns.browser/current/src/playwright-mcp.js

# Codex CLI (if the entry has [mcp_servers.playwright.tools.*] approval
# settings, edit command/args in ~/.codex/config.toml instead: remove drops them)
codex mcp add playwright -- "$(command -v node)" \
  ~/.local/state/herdr/plugins/devtuns.browser/current/src/playwright-mcp.js

Cursor and Droid (~/.cursor/mcp.json, ~/.factory/mcp.json):

"playwright": {
  "command": "node",
  "args": ["/Users/you/.local/state/herdr/plugins/devtuns.browser/current/src/playwright-mcp.js"]
}

current is a link that the plugin keeps pointed at its active installation (Herdr installs each update into a new directory), so this survives herdr plugin update. The link is created when the Herdr server starts or the first time you open a browser pane. If $XDG_STATE_HOME is set, the path starts there instead of ~/.local/state.

Restart running agent sessions to pick it up. Any MCP-capable agent works the same way: point its stdio MCP config at that mcp.js. Inside Herdr, usage statistics and CrUX lookups are turned off, so your local URLs aren't sent anywhere.

Use

Open the browser pane:

herdr plugin pane open --plugin devtuns.browser --entrypoint view \
  --env HERDR_BROWSER_URL=http://localhost:3000

You usually don't have to: the first time an agent uses a browser tool, a browser pane opens next to the agent's pane (unless that tab already has one), so you can watch it work. HERDR_BROWSER_AUTO_OPEN=0 turns this off.

Or Ctrl-click any localhost URL in a Herdr pane. Without a URL it opens http://localhost:3000 (or whatever page the workspace's Chrome is already on).

KeyAction
click / scroll / typeSent to the page
^L or click the top barEdit the URL (3000 → http://localhost:3000)
^RReload
^SCycle render mode: auto (default: half resolution while moving, full when still) → sharp (always full) → fast (always half)
^QClose the pane (Chrome keeps running for the workspace's agents)

The status bar shows the URL and tab count. When an agent emulates a device (say a 390×844 phone), the page is shown at those proportions, centered, and the status bar shows the size. Run with HERDR_BROWSER_DEBUG=1 to also show the frame rate, throughput, render mode, cell size and CDP port.

Plugin actions: devtuns.browser.open, devtuns.browser.stop (stops the workspace's Chrome). Chrome also stops automatically on workspace.closed.

How it works

FileRole
herdr-plugin.tomlPanes, actions, the localhost link handler and the workspace.closed hook
src/main.jsThe pane: screencast → Kitty graphics, input → CDP, tab following, layout
src/input.jsSGR mouse parsing, cell → page coordinates, wheel decoding and coalescing, keys
src/kitty.jsKitty graphics encoding (chunked PNG, double-buffered image ids)
src/chrome.jsStart or reuse the workspace's detached headless Chrome
src/agent-mcp.jsShared launcher: workspace resolution, lazy CDP proxy, pane auto-open, stdio forwarding
src/mcp.jsAgent entrypoint and drop-in for chrome-devtools-mcp: attach to the workspace Chrome, open a pane on the first tool call
src/playwright-mcp.jsSame drop-in for @playwright/mcp, attaching with --cdp-endpoint
src/stop.jsStop a workspace's Chrome (event hook and action)
src/paths.jsShared state paths (~/.local/state/herdr/plugins/devtuns.browser) and the current install link
src/startup.jsStartup hook: refresh the current link
src/cdp.jsMinimal CDP client over Node's built-in WebSocket
src/probe.jsKitty graphics capability check

Design notes:

  • Pixel-exact rendering. The image is sized to the pane's real pixels, using the cell size from CSI 16 t and in-band resize reports (DECSET 2048). Chrome's device scale factor keeps CSS cells about 8px wide. Ghostty reports physical pixels and Warp logical ones; both are handled. Chrome is launched with --force-device-scale-factor=2, because headless capture ignores the emulated scale factor and would otherwise send 1× frames that the terminal enlarges (blurry). HERDR_BROWSER_DEBUG=1 shows frame: (actual) next to want: (pane pixels), so a mismatch is easy to spot.
  • Adaptive resolution. At full Retina size, Chrome's PNG encoding is the bottleneck (about 15–19 fps), not the terminal. In auto mode the pane streams at half resolution while the page moves (about 26–29 fps) and replaces it with one full-resolution capture once the page has been still for 150 ms.
  • Frames go by file, not through the PTY. Each frame is written to a temp file and sent as a Kitty t=t path (about 60 bytes instead of about 1 MB of base64). Herdr reads the file and deletes it. A startup probe falls back to inline base64 if the terminal refuses (HERDR_BROWSER_TRANSPORT=inline|file forces one).
  • Latest frame wins. If the terminal falls behind, intermediate frames are dropped rather than queued, so what you see never lags behind the page.
  • Double buffering. Frames alternate between two Kitty image ids and the new one is placed before the old one is deleted, so there's never an empty pane.
  • One tab, several panes. Every browser pane in a workspace shows the same Chrome tab, and the viewport follows whichever pane set it last. A pane takes it back when you focus it, but only from another pane (they record what they set), never from an agent that is emulating a device.
  • Chrome starts on first use. Every agent session starts its MCP server, but chrome-devtools-mcp only contacts the browser on its first tool call, so the launcher hands it a local proxy address that starts the workspace's Chrome on that first request. Agents that never browse never start a Chrome. A lock in the profile keeps the pane and an agent from launching two at once.
  • Errors don't close the pane. Uncaught errors go to ~/.local/state/herdr/plugins/devtuns.browser/pane.log and the pane keeps running.
  • Wheel decoding. Trackpads mix horizontal ticks into vertical swipes. Ticks are decoded by direction and coalesced to one event per frame.

Known limits

  • Every change, even a blinking text cursor, still sends a whole frame (no partial updates yet).
  • If one Herdr session is attached from two terminals (say Warp and Ghostty), the cell size follows the one you last typed in, and the pane renders for that terminal. Detach the one you're not using.
  • An agent pane moved to another workspace keeps using its original workspace's browser, because the MCP server reads HERDR_WORKSPACE_ID at startup.
  • Resizing the pane (or switching render mode) resets an agent's emulated viewport to the pane's size.
  • Interactive Codex starts MCP servers from a shared daemon, so the launcher finds the agent's workspace by its working directory. Two Codex sessions in one directory are told apart by which one is working when the first browser call comes in.

Related projects

License

MIT. See LICENSE.

↑↓ navigate openesc close