This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Browser Feedback MCP is a Model Context Protocol server that enables visual browser feedback collection directly into Claude Code. Users can point at elements in their browser and send annotated feedback (screenshots, element info, console logs) that Claude can act on.
npm install # Install dependencies
npm start # Run the MCP server (node src/server.js)
npm test # Run tests (vitest)
npm run test:watch # Run tests in watch modeThis is a plain JavaScript (ES modules) project with no TypeScript or build step.
Main files:
-
src/server.js- MCP server combining:- HTTP server (serves widget.js, handles /status endpoint)
- WebSocket server (real-time browser ↔ server communication)
- MCP server (stdio transport for Claude Code integration)
-
src/widget.js- Browser-side widget that:- Injects UI for element selection and feedback submission
- Uses Shadow DOM for style isolation from host page CSS
- Captures console logs, screenshots (via html2canvas if available), and element metadata
- Communicates with server via WebSocket (works offline with local storage fallback)
- Supports export to Markdown file and GitHub Issue (URL-based, no token)
__WEBSOCKET_URL__placeholder is replaced at serve-time with actual WebSocket URL- Exposes
window.__claudeFeedbackDestroy()for clean teardown (used by the browser extension) - Internal DOM access uses
getEl()helper (queries shadow root, not document)
-
extension/- Chrome/Firefox MV3 browser extension:manifest.json- Single manifest for both browsersbackground.js- Service worker tracking per-tab state, badge, and session auto-matchingcontent.js- Injects/removes widget via<script src>tag (with session ID in URL)popup/- Toggle UI with connection status, server URL config, and session picker
Session isolation:
Each Claude Code process generates a unique SESSION_ID (UUID). All feedback storage, WebSocket broadcasts, and MCP tool responses are partitioned by session ID. This prevents feedback from one project appearing in another.
- First process binds the port and becomes the HTTP server owner
- Subsequent processes register via
POST /register-sessionand run in proxy mode - Widget URL includes
?session=<id>which tags the WebSocket connection - Extension auto-matches tabs to sessions by comparing tab origin against detected project URLs
- When multiple sessions exist and auto-match fails, extension popup shows a session picker
GET /sessionsreturns all registered sessions with project metadata- Connections without a session param are placed in the
'unmatched'bucket with a warning — they will not be visible to any MCP session
Data flow (online):
- Widget injected into user's web app (via
install_widgettool, manual script tag, or browser extension) - User selects element and submits feedback
- Widget sends feedback via WebSocket to server (tagged with session ID)
- Server stores feedback in session-scoped queue and resolves session-specific
wait_for_browser_feedbackpromises - Claude receives structured feedback (element info, screenshot, console logs, description)
Data flow (offline):
- Widget works without server connection — annotation stored in
localPendingItems - User exports via Markdown download or GitHub Issue URL from queue panel
| Tool | Purpose |
|---|---|
install_widget |
Auto-inject widget script into HTML file. Supports allowed_hostnames parameter for custom dev domains (e.g., *.local.itkdev.dk) |
uninstall_widget |
Remove widget script from HTML file |
wait_for_browser_feedback |
Block until user submits feedback (default 5min timeout) |
get_pending_feedback |
Get already-submitted feedback without blocking |
get_connection_status |
Check WebSocket client connections |
request_annotation |
Broadcast prompt to connected browsers asking user to annotate |
get_widget_snippet |
Get manual installation script tag |
open_in_browser |
Open project URL in default browser (auto-detects from .env, docker-compose.yml, etc.) |
setup_extension |
Open extension directory and show Chrome/Firefox install instructions |
| Parameter | Type | Default | Description |
|---|---|---|---|
file_path |
string | auto-detect | Path to HTML file to inject widget into |
project_dir |
string | cwd | Project directory to search for HTML files |
dev_only |
boolean | true | Only load widget on allowed hostnames |
allowed_hostnames |
array | see below | List of hostnames/patterns allowed when dev_only is true |
Default allowed_hostnames patterns:
localhost,127.0.0.1- Standard localhost*.local- macOS .local domains*.local.*- Custom local subdomains (e.g.,app.local.example.dk)*.test,*.dev- Common dev TLDs*.ddev.site- DDEV local development
Pattern syntax: Use * as wildcard to match any characters (including dots for multi-segment matches). Examples:
myapp.local.itkdev.dk- Exact match*.local.itkdev.dk- Matches any subdomain of local.itkdev.dk (e.g.,app.local.itkdev.dk)*.local.*- Matches any domain with .local. in it (e.g.,app.local.example.dk,foo.local.bar.baz)
Environment variable FEEDBACK_PORT (default: 9877) controls the HTTP/WebSocket server port.