pigeon
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@pigeonshow me the latest console errors"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
🐦 Pigeon
Forward browser console errors straight to Claude Code — no more copy‑pasting
stack traces. Pigeon captures console.error/console.warn, uncaught exceptions,
unhandled promise rejections, and failed network requests (fetch/XHR) from your dev
pages and exposes them to Claude Code over the Model Context Protocol (MCP). Minified
stack traces are resolved back to original source via your dev server's source maps.
Browser → Extension → WebSocket ┐
▼
Bridge daemon ──control channel──► MCP proxy (per session) → Claude Code
(one, shared) MCP proxy (per session) → Claude CodeA browser extension can't talk to a CLI process directly, hence the chain. A single
bridge daemon owns the WebSocket the extension connects to plus the error buffer; it's
started automatically and shared by every session. Each Claude Code session runs a thin
MCP proxy that forwards tool calls to the daemon over a local control channel — so
multiple sessions and projects can use Pigeon at once (scope each to its dev server
with get_recent_errors({ pageUrl })).
Layout
pigeon/
extension/ # Chrome Manifest V3 extension (TypeScript, esbuild)
server/ # Bridge daemon (WebSocket + buffer) + per-session MCP proxy (TypeScript)
README.mdRelated MCP server: chrome-devtools-mcp-fork
What Claude gets
Tools
Tool | Purpose |
| Buffered errors, newest first; filter by |
| Block until the next error arrives — “reproduce in the browser, then check.” |
| Counts per level + newest/oldest timestamps. |
| Empty the buffer. |
| When |
| Reload a dev tab (active localhost tab by default) — re-trigger an error after a fix. |
| Gated. Run JS in the page's MAIN world and return the result. Off unless explicitly enabled (see below). |
Prompts (appear as slash-commands in Claude Code's / menu)
Prompt | Purpose |
| Embeds the recent errors and asks for root‑cause analysis + concrete fixes. |
| Focuses on the single newest error (with its resolved stack) and proposes a fix. |
Resources
Resource | Contents |
| Live JSON snapshot of the buffer. |
| JPEG of the page when an uncaught error fired. |
|
|
On uncaught errors / unhandled rejections, Pigeon also captures a screenshot
(rate-limited, best-effort — the error's tab must be the visible one) and a DOM snapshot.
Entries gain hasScreenshot/hasDom flags plus screenshotUri/domUri. Toggle this off
in the popup ("Snapshots on errors"); console/network events never carry snapshots.
Each captured error carries level, message, stack, source, line, col,
pageUrl, origin, timestamp, plus tabId/tabTitle (which tab it came from) and,
for network events, status. When a source map is available, a resolvedStack field is
added with the original file:line:col frames.
The server keeps a ring buffer of the latest 200 errors and deduplicates
identical message+stack pairs seen within 2 seconds (collapsed into one entry with a
count).
Setup
1. Build & start the bridge server
cd pigeon
npm run install:all # installs server/ and extension/ deps
npm run build # builds bothStart the bridge. Normally Claude Code launches it for you (see step 3) — but you can run it standalone for testing:
npm start # = node server/dist/index.jsThe bridge listens on
ws://127.0.0.1:8765for the extension and speaks MCP over stdio. All logs go to stderr (or$PIGEON_LOG_FILE) — never stdout, which is reserved for the MCP JSON‑RPC protocol.
Smoke‑test without the extension:
npm test # spawns the server, pushes a fake error, asserts the MCP tools
# or, against an already-running server:
npm --prefix server run test:client2. Load the extension in Chrome
Open
chrome://extensions.Enable Developer mode (top right).
Click Load unpacked and select
pigeon/extension/dist.The 🐦 icon appears. Click it: the popup shows connection status, the number of buffered errors, and three toggles — forwarding on/off, Snapshots on errors, and Allow remote eval ⚠️ (for
eval_in_page, off by default).
The extension only activates on http://localhost/* and http://127.0.0.1/* (your dev
servers). It connects to the bridge automatically; if the bridge isn't running yet, it
reconnects with exponential backoff and buffers errors in the meantime.
Rebuild after changes with
npm run dev:extension(watch) ornpm --prefix extension run build, then hit Reload on the extension card.
3. Register the MCP server with Claude Code
Use the absolute path to the built entry point. From the pigeon/ directory:
claude mcp add pigeon -- node "$(pwd)/server/dist/index.js"Add -s user to register it globally (available in every project, not just this one) —
recommended if you do web dev across several repos:
claude mcp add -s user pigeon -- node "$(pwd)/server/dist/index.js"Or add it to a .mcp.json in your project:
{
"mcpServers": {
"pigeon": {
"command": "node",
"args": ["/Users/martin/claude/pigeon/server/dist/index.js"]
}
}
}To enable the optional features, pass the env vars at registration. With the CLI:
claude mcp add pigeon \
--env PIGEON_DB="$HOME/.pigeon/history.jsonl" \
--env PIGEON_ALLOW_EVAL=1 \
-- node "$(pwd)/server/dist/index.js"…or in .mcp.json, add an "env" block alongside command/args:
"env": { "PIGEON_DB": "/abs/path/history.jsonl", "PIGEON_ALLOW_EVAL": "1" }Then, inside Claude Code, verify with /mcp — you should see pigeon connected with its
tools and the pigeon://errors resource. (How many tools depends on gating: the four
buffer tools plus reload_tab are always there; get_error_history appears with
PIGEON_DB set and eval_in_page with PIGEON_ALLOW_EVAL=1.)
Using Pigeon in your Claude Code workflow
Once registered (ideally -s user, so it's there in every project) and the extension shows
Connected, run your dev server on localhost and work as usual. Verify with /mcp
(pigeon connected) and the popup's green dot.
Recipes
Reactive — “what's broken?” → “What errors are in the browser right now?” Claude calls
get_recent_errorsand reads the message + source-mapped stack (original file, not minified).Repro-driven — the core loop. “I'll reproduce it, wait for the error.” Claude calls
wait_for_next_error; you trigger it in the browser; it streams in and Claude fixes it. After the fix: “reload the tab” →reload_tab→ wait again. That's edit → reload → verify without leaving the CLI.Slash-commands. The prompts appear in the
/menu:/analyze_browser_errors(group all current errors by root cause) and/fix_latest_error(focus the newest one).Visual / state bugs. Uncaught errors carry a screenshot + DOM snapshot — “look at the screenshot from the last error” (
pigeon://errors/{id}/screenshotand/dom).Inspect live (opt-in). With eval enabled (see Browser control below): “evaluate
window.__store.getState()in the page” →eval_in_page.Recurring errors. With
PIGEON_DBset: “has this error happened before?” →get_error_history(spans restarts).
Make it smoother
Let Claude reach for Pigeon on its own — add to a project's CLAUDE.md:
## Debugging
For runtime errors in the browser, use the `pigeon` MCP tools
(`get_recent_errors`, `wait_for_next_error`) instead of asking me to paste console output.Skip permission prompts for the read-only tools — in settings.json:
{ "permissions": { "allow": [
"mcp__pigeon__get_recent_errors",
"mcp__pigeon__get_error_stats",
"mcp__pigeon__wait_for_next_error",
"mcp__pigeon__get_error_history"
] } }Leave reload_tab, eval_in_page, and clear_errors to prompt.
Multiple sessions & projects
This works out of the box. A single bridge daemon is auto-started on first use and shared
by every Claude Code session — open as many as you like across different projects. It picks
free ports automatically (no conflict even if 8765/8766 are taken) and advertises them via
~/.pigeon/runtime.json. All localhost tabs feed the same buffer; scope each session to its
own dev server with the pageUrl filter.
What "scope with pageUrl" means. Because every project's tabs feed one shared buffer, you
narrow a session to its own errors by filtering on the page URL — a case-insensitive substring
match against each error's URL. You don't type the { pageUrl: … } syntax; you just ask in plain
language and Claude sets the filter:
“What errors are coming from viacamp?” / “Show the errors from localhost:3000.” → Claude calls
get_recent_errors({ pageUrl: "3000" })
Notes:
The filter value is whatever is in the tab's address bar that uniquely identifies it — usually the port (
"3000","5173") or"localhost:3000".If only one project is running, you don't need the filter at all — the buffer only holds that project's errors.
Drop the proactive snippet from Make it smoother into each project's
CLAUDE.mdand the session scopes itself automatically.
The daemon keeps running in the background after sessions close (it owns the browser feed) —
normally just leave it. To stop it: pkill -f dist/bridge.js. The first session's env
(PIGEON_DB, PIGEON_ALLOW_EVAL) configures the daemon, so set those consistently in your
user-scope registration; later sessions reuse the already-running daemon as-is.
Dashboard & CLI
The daemon comes with two ways to see and manage what's going on across all your sessions.
pigeon CLI
A small terminal tool for the shared daemon (no browser needed):
node server/dist/cli.js status # or: npm --prefix server run cli -- statusCommand | Shows / does |
| Daemon health (pid, version, uptime), ports, whether the extension is connected, eval/history flags, buffer counts, and the connected Claude Code sessions — each with its project name and the dev-server it's scoped to. |
| PASS/WARN/FAIL checks with actionable hints (Node version, build present, daemon up, extension connected, ports reachable, eval gating). The first thing to run when something feels off. |
| Shut the daemon down gracefully (sessions just respawn it on next use). |
| Open the web dashboard in your browser. |
pigeon status when nothing is running tells you so (it never spawns a daemon). Tip: register the
bin once with npm --prefix server link (or add server/dist/cli.js to your PATH) to type
pigeon directly.
Web dashboard
The daemon serves a live dashboard on http://127.0.0.1:8767 (first free port from there; see it in
pigeon status or the daemon log). It shows daemon health + ports, the connected sessions (which
project, since when, which dev-server they're scoped to), and an error feed (level-coloured, click to
expand the source-mapped stack + screenshot). The feed has two tabs: Live (the in-memory buffer)
and History (the persisted JSONL — only when PIGEON_DB is set; it outlives restarts and the
buffer). Buttons clear the buffer or stop the daemon.
The dashboard never pollutes its own data: its page marks itself so the extension skips capturing it (otherwise, running on localhost, it would log its own API traffic into the buffer it displays).
It's on by default; disable with PIGEON_DASHBOARD=0. Security: it binds 127.0.0.1 only,
validates the Host header (anti-DNS-rebinding), and gates every API call on a per-daemon token kept
in the mode-600 ~/.pigeon/runtime.json — a random web page can't read the token, so it can't drive
the dashboard. It shows the eval-gating state but never toggles it (that stays the explicit
double opt-in below).
Browser control & security
Pigeon can also drive the browser, so Claude can reproduce a bug rather than only read it:
reload_tab— always available; reloads the target tab.eval_in_page— runs arbitrary JavaScript in the page. This is powerful and dangerous, so it is off by default behind a double opt-in — both must be true:Start the server with
PIGEON_ALLOW_EVAL=1(otherwise the tool isn't even exposed).Turn on "Allow remote eval ⚠️" in the extension popup (otherwise the extension refuses every eval command).
It only targets
localhost/127.0.0.1tabs. Leave both off unless you actively want Claude to execute code in your dev page.
Configuration
Env var | Default | Where | Meaning |
|
| daemon | Base extension WebSocket port. The daemon binds the first free port from here; the extension scans the next 16. |
|
| daemon | Base control-channel port (first free from here). Proxies discover the actual port via the runtime file. |
|
| daemon | Base web-dashboard HTTP port (first free from here). |
|
| daemon | Set to |
| — | both | If set, mirror stderr logs to this file (handy to see daemon logs). |
|
| both | Where the daemon writes its discovery file ( |
|
| server | Set to |
| — | server | Set to |
| — | server | Path to a JSONL file; enables persistent history + the |
Ports are chosen automatically: the daemon takes the first free port at/after each base and
records it in ~/.pigeon/runtime.json; proxies read that file and the extension scans the
range — so Pigeon runs out of the box even if 8765/8766 are already taken. You only need
PIGEON_WS_PORT if you want a different base (then also bump WS_BASE in
extension/src/background.ts, since the extension scans from there).
What's captured
Console:
console.error/console.warn(wrapped, then passed through unchanged). The hooks are installed by aMAIN-world content script atdocument_start, so they run before any page script and catch even synchronous errors during initial load.Uncaught exceptions (
windowerror) and unhandled promise rejections.Failed network requests:
fetchandXMLHttpRequestresponses with status ≥ 400 or a transport failure (status0). Intentionalaborts are ignored. Original semantics are preserved — Pigeon never swallows a response or rejection.
Notes & limits
Source maps are fetched from the dev server on demand and cached briefly (5 s, so hot-reloads stay accurate). Resolution is best-effort: no map → the raw stack is kept.
Only
localhost/127.0.0.1are matched, by design (your dev servers).The MV3 service worker sleeps after ~30s idle; Pigeon reconnects on wake (incoming messages and a 30s
alarmsheartbeat) and persists the pending queue inchrome.storage.session, so errors aren't lost across an eviction.One browser, one bridge: the buffer is shared across all matched tabs (filter with
get_recent_errors({ pageUrl })).History: with
PIGEON_DB=/path/to/history.jsonl, new errors are appended as JSONL and reloaded on startup. The file is append-only and excludes screenshots/DOM (those stay in-memory only) — rotate or delete it yourself.Lighthouse / performance metrics remain out of scope for now.
Development
# server
npm --prefix server run dev # tsc --watch
npm --prefix server run test:e2e # full MCP proxy/daemon smoke test
npm --prefix server run test:multi # two sessions sharing one daemon + pageUrl scoping + session identity
npm --prefix server run test:ports # daemon shifts ports when 8765/8766 are taken
npm --prefix server run test:dashboard # dashboard HTTP API: token gating, /api/state, clear
npm --prefix server run test:sourcemap # source-map resolution test
# extension
npm --prefix extension run dev # esbuild --watch
npm --prefix extension run typecheck # tsc --noEmit
npm --prefix extension run test:unit # pure serialization unit tests
npm --prefix extension run test:browser # real-browser smoke test (loads the extension)test:browser loads the built extension into Chrome for Testing (system Google Chrome
blocks unpacked extensions). Install it once:
cd extension && node node_modules/playwright-core/cli.js install chromiumCI (.github/workflows/ci.yml) runs everything on each push: a build-test job (build,
typecheck, unit + source-map + MCP E2E + multi-session + port-fallback + dashboard) and a
browser-e2e job (real-browser smoke test).
See CLAUDE.md for the architecture and the invariants worth not regressing.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/pepperonas/pigeon'
If you have feedback or need assistance with the MCP directory API, please join our Discord server