$ show eyalzh/browser-control-mcp
This repository is an application: an MCP (Model Context Protocol) server paired with a Firefox browser extension that gives AI assistants like Claude Desktop read and management access to a user's real browser. The MCP server exposes nine tools over stdio — open/close/list/reorder/group tabs, search browsing history, extract a page's text and links, find/highlight text in a tab, and capture screenshots. Unlike generic web-automation MCP servers, it deliberately cannot click, modify pages, or run arbitrary scripts; page content access requires per-domain user consent and screenshots require per-tab authorization via Firefox's activeTab mechanism. Architecturally this is an Nx monorepo with three packages: mcp-server (Node 22, @modelcontextprotocol/sdk), firefox-extension (Manifest V2 WebExtension), and common (shared typed message protocol). The server communicates with the AI client over MCP stdio and with the extension over a loopback-only WebSocket (127.0.0.1 and ::1 on port 8089, or 0.0.0.0 in container mode). Every message is HMAC-SHA256 signed with a shared secret the extension generates; the extension connects outbound, so no inbound socket is exposed in the browser. Requests are matched to responses by correlation ID with timeouts, and the extension replies with resources (tabs, history, content, screenshots) over the same channel. The code is security-conscious and well-crafted: the dual-loopback binding handles IPv4/IPv6 localhost resolution differences, a capture-consent module mirrors the browser's activeTab grant lifetime to fail fast with actionable errors, the extension ships no runtime third-party dependencies, and it includes per-tool enable/disable toggles, a domain deny list, and a bounded in-browser audit log. The extension message handler has a solid Jest test suite covering consent revocation, denied domains, and disabled tools; the MCP server itself lacks unit tests. Minor debt: an unused, deprecated 'readline' npm dependency in mcp-server/package.json, an Nx version mismatch between root (22.x) and the subproject (20.x), and a few non-null assertions in the WebSocket layer. The repo is actively maintained with recent commits, a release workflow that builds DXT packages, and current dependency versions (TypeScript 5.8, Jest 30, MCP SDK 1.x, ws 8.x), so technical drift is low.
The LLM identified 4 files worth highlighting for reuse:
Tracks the lifetime of Firefox's per-tab activeTab grant in a local map so screenshot requests fail fast with an actionable message when the user has not authorized the tab, while the browser remains the authoritative enforcer. Handles revocation on navigation/close and per-tab badge feedback. Self-contained (no internal imports), directly reusable in any WebExtension that needs to mirror a browser-granted permission that has no query API.
A request/response WebSocket layer that maps correlation IDs to typed promise resolvers, signs every payload with HMAC-SHA256 using a shared secret, applies per-message timeouts, and binds to both 127.0.0.1 and ::1 to survive IPv4/IPv6 localhost resolution differences (or 0.0.0.0 in container mode). Reusable as a secure local RPC backbone for desktop-app-to-local-server pairs.
Storage-backed configuration for the extension: generated secret, per-tool enable/disable toggles, domain deny list with subdomain matching, multiple WebSocket ports, and a bounded in-browser audit log capped at 100 entries. Demonstrates a clean pattern for user-controllable tool gating plus privacy auditing; the deny-list host matching logic is directly reusable.
Comprehensive Jest test suite for the extension's message handler covering disabled tools, deny lists, missing/revoked screenshot consent, and restore-of-previous-tab behavior. The browser API mocking strategy in __tests__/setup.ts is a reusable pattern for testing WebExtension logic without a real browser, particularly consent state transitions.