pigeon
Forwards browser console errors, warnings, and unhandled rejections to Claude Code via MCP, enabling natural language debugging.
README
🐦 Pigeon
<p align="center"> <img src="https://img.shields.io/badge/Pigeon-console%20errors%20%E2%86%92%20Claude%20Code-7C3AED?style=for-the-badge&logo=anthropic&logoColor=white" alt="Pigeon" /> </p>
<p align="center"> <a href="https://github.com/pepperonas/pigeon/actions/workflows/ci.yml"><img src="https://github.com/pepperonas/pigeon/actions/workflows/ci.yml/badge.svg" alt="CI" /></a> <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg?style=flat-square" alt="License: MIT" /></a> <img src="https://img.shields.io/badge/version-0.1.0-blue.svg?style=flat-square" alt="Version" /> <img src="https://img.shields.io/badge/status-v1%20minimal-orange.svg?style=flat-square" alt="Status" /> <img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square" alt="PRs welcome" /> </p>
<p align="center"> <img src="https://img.shields.io/badge/TypeScript-5.x-3178C6?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript" /> <img src="https://img.shields.io/badge/Node.js-18%2B-339933?style=flat-square&logo=node.js&logoColor=white" alt="Node 18+" /> <img src="https://img.shields.io/badge/MCP-stdio-000000?style=flat-square&logo=anthropic&logoColor=white" alt="Model Context Protocol" /> <img src="https://img.shields.io/badge/WebSocket-ws-010101?style=flat-square&logo=socketdotio&logoColor=white" alt="WebSocket" /> </p>
<p align="center"> <img src="https://img.shields.io/badge/Chrome-Manifest%20V3-4285F4?style=flat-square&logo=googlechrome&logoColor=white" alt="Chrome MV3" /> <img src="https://img.shields.io/badge/bundler-esbuild-FFCF00?style=flat-square&logo=esbuild&logoColor=black" alt="esbuild" /> <img src="https://img.shields.io/badge/Claude%20Code-ready-D97757?style=flat-square&logo=claude&logoColor=white" alt="Claude Code" /> <img src="https://img.shields.io/badge/platform-localhost-lightgrey?style=flat-square" alt="localhost" /> </p>
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 page → Extension → WebSocket → Bridge server → MCP (stdio) → Claude Code
A browser extension can't talk to a CLI process directly, hence the chain. The bridge server is a single Node process that wears two hats: a WebSocket receiver for the extension, and an MCP stdio server for Claude Code.
Layout
pigeon/
extension/ # Chrome Manifest V3 extension (TypeScript, esbuild)
server/ # Node bridge: WebSocket server + MCP stdio server (TypeScript)
README.md
What Claude gets
Tools
| Tool | Purpose |
|---|---|
get_recent_errors({ limit?, level?, pageUrl?, since? }) |
Buffered errors, newest first; filter by level (error/warn/network), pageUrl substring, or since timestamp. |
wait_for_next_error({ timeout_ms? }) |
Block until the next error arrives — “reproduce in the browser, then check.” |
get_error_stats() |
Counts per level + newest/oldest timestamps. |
clear_errors() |
Empty the buffer. |
get_error_history({ limit?, level?, since? }) |
When PIGEON_DB is set. Query the on-disk history — spans restarts, beyond the 200-entry buffer. |
reload_tab({ tabId? }) |
Reload a dev tab (active localhost tab by default) — re-trigger an error after a fix. |
eval_in_page({ expression, tabId?, timeout_ms? }) |
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 |
|---|---|
analyze_browser_errors({ limit?, level?, pageUrl? }) |
Embeds the recent errors and asks for root‑cause analysis + concrete fixes. |
fix_latest_error() |
Focuses on the single newest error (with its resolved stack) and proposes a fix. |
Resources
| Resource | Contents |
|---|---|
pigeon://errors |
Live JSON snapshot of the buffer. |
pigeon://errors/{id}/screenshot |
JPEG of the page when an uncaught error fired. |
pigeon://errors/{id}/dom |
outerHTML of the page at error time. |
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 both
Start 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.js
The 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:client
2. 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
four tools and the pigeon://errors resource.
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.
One limitation
The bridge runs inside the Claude Code session and binds port 8765. With multiple Claude
Code sessions open at once, the first owns the port; others still have the tools but get no
browser feed. Fine for one-session-per-project work; for more, run the bridge as a standalone
service.
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. - Start the server with
Configuration
| Env var | Default | Where | Meaning |
|---|---|---|---|
PIGEON_WS_PORT |
8765 |
server | WebSocket port (must match the extension's WS_URL). |
PIGEON_LOG_FILE |
— | server | If set, mirror stderr logs to this file. |
PIGEON_SOURCEMAPS |
1 |
server | Set to 0 to disable source-map resolution of stacks. |
PIGEON_ALLOW_EVAL |
— | server | Set to 1 to expose the eval_in_page tool (also needs the popup toggle). |
PIGEON_DB |
— | server | Path to a JSONL file; enables persistent history + the get_error_history tool. |
Changing the port? Update WS_URL in extension/src/background.ts and rebuild.
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 client/server smoke test
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 chromium
CI (.github/workflows/ci.yml) runs everything on each push: a build-test job (build,
typecheck, unit + source-map + MCP E2E) and a browser-e2e job (real-browser smoke test).
See CLAUDE.md for the architecture and the invariants worth not regressing.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.