Local Browser MCP
Enables controlling an existing Brave browser through a local Chrome extension, providing tools to manage tabs, navigate, read page content, take screenshots, interact with elements, and execute JavaScript.
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., "@Local Browser MCPList my open tabs"
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.
Local Browser MCP
An independent, local-only MCP server and Chrome extension for controlling your existing browser tabs. The control plane stays on the same computer and requires no account, hosted service, routing identifier, access token, or remote relay.
This directory is the complete project. Copy it anywhere; it does not import, link to, or execute files from its parent repository.
It preserves the live @vibebrowser/mcp@0.3.6 22-tool MCP contract exactly —
order, names, titles, descriptions, input schemas, defaults, and annotations —
so agents, prompts, and skills written against that contract work unchanged.
Two local convenience tools remain beyond that compatibility boundary.
How it works
MCP client ── stdio ──> Node.js server ── ws://127.0.0.1:<port> ──> Chrome extensionMCP is exposed only through the child process's standard input/output.
The extension bridge binds explicitly to IPv4 loopback.
The extension connects only to
ws://127.0.0.1:<port>— the port comes fromconfig.jsor, in an unlocked development build, the options page.Server and extension complete a fail-closed handshake (profile name and protocol version) before any tool traffic flows.
There is no remotely reachable server mode.
"Local-only" describes the control plane. Chrome can still visit websites at your request, so ordinary page navigation can naturally use the internet. The project itself makes no relay, account, analytics, update, or telemetry request.
Related MCP server: Chrome MCP Bridge
Requirements
Node.js 18 or newer
npm
Chrome, Chromium, Brave, or another Chromium browser supporting
chrome.debuggerAn MCP client that can launch a stdio server
For
npm testonly: a Chromium binary that honors--load-extension(see Testing)
Install
From this directory:
npm install
npm run build
npm test
npm run doctorThe first installation downloads normal npm build/runtime dependencies. Runtime browser control remains local after installation.
Load the extension
Open
chrome://extensions.Enable Developer mode.
Select Load unpacked.
Choose this project's
extensiondirectory (or a stamped profile directory fromdist/profiles/<name>/— see Fleet deployment).
Chrome displays a debugger-control banner while the extension is attached to a tab. This is expected because browser actions use Chrome's supported debugger API.
The base development extension is intentionally unlocked: its options page
(right-click the toolbar icon → Options) may override the default port/profile.
Packaged fleet copies are stamped with locked=true; stored overrides are
ignored and the options controls are disabled, so a deployed profile cannot be
retargeted. The toolbar popup shows the effective route and connection state.
Configure your MCP client
Replace /path/to/local-browser-mcp with the absolute path to this directory:
{
"mcpServers": {
"local-browser": {
"command": "node",
"args": [
"/path/to/local-browser-mcp/server/dist/cli.js",
"--require-extension",
"--extension-connect-timeout", "90000"
]
}
}
}For Codex CLI:
codex mcp add local-browser -- node /path/to/local-browser-mcp/server/dist/cli.js --require-extensionCLI flags:
Flag | Default | Meaning |
|
| Loopback port the bridge listens on |
|
| Vibe-compatible alias for |
| none | Expected extension profile; mismatches are rejected (close 4403) |
| off | Fail startup unless the full tool contract is published |
|
| How long |
| off | Verbose stderr logging |
An optional leading start subcommand is accepted. The port can also come from
BROWSER_MCP_EXTENSION_PORT, HERMES_VIBE_EXTENSION_PORT, or
VIBE_MCP_EXTENSION_PORT (in that precedence order), and the profile from
BROWSER_MCP_PROFILE.
With --require-extension, the server refuses to enter the "success with zero
tools" state: it waits up to the connect timeout for the extension to connect
and advertise the exact Vibe-compatible contract, then either serves MCP or exits 1
with a clear stderr diagnosis (no extension, wrong profile, or tool drift). If
the extension later disconnects, an equal-length grace timer runs before the
server exits rather than serving an empty tool list. Without the flag, startup
is tolerant: tools/list waits briefly and late tools are announced via
notifications/tools/list_changed.
Startup order does not matter. The extension retries its loopback connection with bounded exponential backoff; handshake rejections (wrong profile or protocol) are fatal and stop reconnection until reconfigured.
Hermes profile adapter
Hermes profiles that previously used profile-vibe-mcp need to preserve one
additional responsibility: that wrapper owns the dedicated browser lease. Use
scripts/hermes-profile-browser-mcp as the MCP command so Browser MCP replaces
the Vibe process without bypassing browser launch, renewal, or release:
mcp_servers:
vibe: # keep this key to preserve the mcp_vibe_* tool namespace
command: /path/to/browser-mcp/scripts/hermes-profile-browser-mcp
args: []
env:
BROWSER_MCP_ROOT: /path/to/browser-mcp
BROWSER_MCP_BROWSER_LAUNCHER: /path/to/.hermes/bin/default-browser-native
BROWSER_MCP_STATE_DIR: /path/to/.hermes/profiles/default/cache/browser-mcp
BROWSER_MCP_EXTENSION_PORT: "21122"
BROWSER_MCP_PROFILE: default
BROWSER_MCP_CONNECT_TIMEOUT_MS: "90000"
connect_timeout: 120.0The extension artifact must be stamped with the same profile and extension port. The old Vibe agent port is intentionally absent: Browser MCP speaks MCP directly over stdio and owns only the profile's loopback extension port.
For a staged cutover, install the locked artifact and activate the matching
Hermes command together. If the extension is loaded while no process owns its
locked port, Chrome may retain a handled WebSocket connection-refused record at
the constructor line; that is an inactive route, not a malformed WebSocket.
Run doctor on the artifact, then verify the adapter through an MCP
tools/list call before retiring the old profile route.
Browser tools
Every compatible tool also accepts optional pageStateFormat with value
markdown or accessibility_tree. When supplied, the result appends state
captured from the exact target page after the operation.
The 22 contract tools, in the advertised order:
Tool | Required params | Optional params | Notes |
|
|
| Exact page; URL navigation defaults to 45s readiness |
| — | — | Deterministic page-ID listing with active markers |
| — |
| Background by default; readiness defaults on |
|
|
| Focuses exact tab/window and establishes visibility |
|
| — | Refuses to close the final remaining page |
|
|
| Real pointer input; link may open a background tab |
|
| — | Native setter plus input/change events |
|
| — | Ordered array of |
|
| top-level fields or | Inline base64 only; no host filesystem path |
|
|
| Types into the previously focused control |
|
| — |
|
|
|
| Non-empty text array; resolves when any item appears |
|
|
| Glob ( |
| — |
| Document readiness plus DOM-mutation quiet window |
|
|
| Polls caller-supplied JavaScript until truthy |
|
|
| Function declaration; string arguments; promises awaited |
|
|
| Optional snapshot index focus, then key/chord delivery |
|
|
| Holds pointer over snapshot index |
|
|
| Selector, uid/index, or |
|
|
| Viewport emulation |
|
|
| Cost-aware JPEG (PNG at quality 90) image block |
| — |
| Markdown/accessibility/ARIA with stable uids |
Local extras beyond the established contract (kept from the prototype, clearly annotated in tools/list):
Tool | Required params | Optional params | Notes |
| — |
| Visible |
|
|
| Evaluates a bare JS expression |
The profile-local protected credential broker also accepts an internal
renderer-opaque fallback on press_key with keys="CMD+V": pass both
xRatio and yRatio as finite viewport ratios strictly between 0 and 1,
derived from the center of the exact field in a fresh Browser MCP screenshot.
It is mutually exclusive with index, requires a one-use broker-staged
credential, and keeps both the click and secret typing inside the browser
bridge. It is not part of the advertised Vibe compatibility contract.
The uid workflow
Call take_snapshot before interacting with elements. It returns compact
references such as @e12; pass those as uid to click/fill/upload, or as the
numeric index where the Vibe schema uses an index. Drag accepts a uid/index,
CSS selector, or coordinates. Uids are valid only for the latest snapshot
of that tab — taking a new snapshot renumbers them, and navigation invalidates
them. If a uid goes stale the tool fails closed with
Element @eN is stale; call take_snapshot again (after attempting exactly one
relocation by role and accessible name).
Readiness vs. waiting
Navigation-flavored tools establish bounded readiness: new_page and
navigate_page use 45 seconds by default, while switch_to_page uses 15.
The extension polls tab status until the page completes loading. On timeout it
succeed, appending the warning suffix
(page did not reach readyState=complete within Ns; it may still be loading).
The wait_for* tools hard-error on timeout with Timed out after Nms waiting for …. Defaults match Vibe: 10 seconds for text/DOM quiet and 15
seconds for URL/condition. Long waits are kept alive end-to-end by progress
frames that extend the server-side call timer.
Reliability
Each tool call has an end-to-end request ID and a per-tool bounded timeout (wait tools get their requested timeout plus margin).
Pending calls are replayed if the extension replaces its socket.
The extension caches completed responses and deduplicates replayed calls (at-most-once execution).
Stale socket-close events cannot invalidate a newer connection, and a rejected handshake never displaces an established one.
Heartbeats detect half-open connections.
CDP sessions are reused and reacquired after transport failure.
Stale accessibility references are relocated once by role and accessible name.
Reconnection backs off from one to thirty seconds; a
chrome.alarmskicker survives service-worker suspension.
See ARCHITECTURE.md for the full call lifecycle and handshake state machine.
Fleet deployment
Run many isolated browser instances on one machine by giving each Chrome profile its own extension copy (unique port + profile name) and each MCP server the matching flags.
Package stamped extensions
npm run package -- --profile alpha:19901 --profile beta:19902
# or from a JSON manifest: npm run package -- --profiles fleet.json
# tests and other isolated builds can use: --output-dir /tmp/browser-mcp-distThis produces, deterministically (byte-identical zips for identical sources — store method, fixed 1980 timestamps, sorted entries):
dist/local-browser-extension-<version>.zip(+.sha256) unstamped base
dist/profiles/<name>/ stamped unpacked dir
dist/local-browser-extension-<version>-<name>.zip(+.sha256) stamped zip
dist/artifacts.json versions, ports, content hashesThe deployment test packages into a private temporary output directory, so
running npm test cannot delete or replace the active dist/profiles/*
deployment artifacts.
A 12-instance fleet is one port per profile, e.g.:
Profile | Port | Profile | Port |
fleet-0 | 19900 | fleet-6 | 19906 |
fleet-1 | 19901 | fleet-7 | 19907 |
fleet-2 | 19902 | fleet-8 | 19908 |
fleet-3 | 19903 | fleet-9 | 19909 |
fleet-4 | 19904 | fleet-10 | 19910 |
fleet-5 | 19905 | fleet-11 | 19911 |
For each instance: load dist/profiles/<name>/ unpacked into that Chrome
profile, and launch the server with
--extension-port <port> --profile <name> --require-extension. The handshake
rejects cross-wired connections (an extension stamped fleet-1 connecting to
the fleet-0 server is closed with code 4403 and stops retrying), so a
misconfigured instance fails loudly instead of controlling the wrong browser.
The profile-isolation test in server/test/ proves 12 concurrent instances
stay independent.
Verify an installed copy
npm run doctor -- --extension-dir dist/profiles/alpha
# or any deployed copy of a stamped directoryDoctor hashes installed allowlisted files before parsing configuration and never imports or executes installed JavaScript. It reports whether the effective route is immutable; profile artifacts must be locked and match their packaged name/port metadata. Modified or stale installs fail closed.
Upgrade and rollback
Bump the version in all five places (root
package.json,server/package.json,extension/manifest.json,server/src/contract.ts, andpackage-lock.json) — the packager, doctor, and tests all refuse to run with mismatched versions.npm test && npm run package -- --profiles fleet.json.Replace each deployed directory with the new
dist/profiles/<name>/, then reload the extension inchrome://extensions(or restart Chrome).Verify each with
npm run doctor -- --extension-dir <deployed path>.Rollback is the same procedure with the previous version's artifacts; zips are deterministic, so the
.sha256sidecars identify exactly what is deployed anywhere.
Security model
The control socket accepts loopback connections only. Other processes running as the same local user could still attempt to connect; local-only does not mean mutually authenticated. The profile handshake is a deployment-correctness check, not an authentication boundary.
Profile matching is strict equality, including the unset case: a server with no
--profileaccepts only extensions with no stamped profile (null === null), and any mismatch fails closed.The extension requires powerful
debugger,tabs, and<all_urls>access to control arbitrary tabs. Review the small, unbundledextension/background.jsbefore installing it.evaluate_script,wait_for_condition, andevaluaterun caller-supplied JavaScript in the selected page. Remove their entries fromextension/tools.js+server/src/contract.tsand their switch cases if your threat model does not permit arbitrary page code.upload_fileaccepts owner-supplied inline base64 only. Its schema and executor reject arbitrary host filesystem paths.One server process owns each port. Configure one MCP host process per port/profile pair.
Testing
npm test # build + all eight suites (below)
npm run test:browser # just the real-browser suiteThe suite, in order:
contract-sync — both production copies deep-match the independent live Vibe 0.3.6 fixture (order, text, schemas, defaults, and annotations), with synchronized package/manifest/lockfile versions.
e2e — the real bridge against a scripted fake extension: handshake happy path, close codes 4400/4403/4426, progress-extends-timeout, replay across reconnects,
waitForContract.mcp-contract — launches the real stdio CLI using
start --portand proves its actual tools/list matches the live fixture.profile-isolation — 12 concurrent bridge+extension pairs stay fully isolated; a cross-wired profile is rejected without disturbing the others; 12 parallel calls route correctly.
local-only-audit — source-level regex audit that no relay, fetch, XHR, beacon, or non-loopback WebSocket code exists in any shipped file.
deployment — packages locked profile artifacts twice, proves identical hashes, and verifies the inert hash-first doctor path.
hermes-wrapper — proves the lease-aware adapter launches, records, and releases the dedicated browser; passes the exact locked port/profile to the stdio server; and rejects unsafe configuration.
browser-e2e — loads a stamped extension into a real headless Chromium and exercises the full 24-tool surface against local test pages, including readiness, uids, key chords, uploads, waits, and screenshots.
The browser suite is hard-required: npm test fails with a clear error if
no suitable Chrome binary is found. Note that branded Google Chrome 137+
ignores --load-extension, so the test prefers Chrome for Testing / Chromium
builds (Puppeteer and Playwright caches are auto-detected). Point CHROME_BIN
at a binary to override:
CHROME_BIN="$HOME/.cache/puppeteer/chrome/<ver>/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing" npm testIf a macOS Chrome-for-Testing build ignores unpacked extensions in headless
mode, run the same real-browser suite with BROWSER_E2E_HEADED=1. The test
still uses a fresh isolated profile and the stamped loopback-only extension.
Manual smoke test
Load a stamped dir (e.g.
dist/profiles/alpha) in Chrome.node server/dist/cli.js --require-extension --profile alpha --extension-port 19901under your MCP client: tools/list shows 24 tools.Quit Chrome and start the server again: it exits 1 with a clear
--require-extension failederror instead of serving zero tools.
Project layout
extension/ Unbundled Manifest V3 Chrome extension
extension/tools.js The 24 tool definitions (pure data, Node-importable)
extension/config.js Development defaults or locked stamped fleet route
server/src/contract.ts Vibe-compatible production contract + validation
server/src/ TypeScript MCP server and loopback bridge
server/test/fixtures/ Independent captured Vibe 0.3.6 tools/list fixture
server/test/ Eight test suites (see Testing)
scripts/package-extension.mjs Deterministic zip packager + profile stamping
scripts/doctor.mjs Installation and deployment verification
scripts/hermes-profile-browser-mcp Lease-aware Hermes MCP command adapter
scripts/clean.mjs Removes generated outputCommands
npm run build # Compile the server
npm test # Build + all eight test suites (requires a Chromium binary)
npm run doctor # Check environment, versions, contract; --extension-dir verifies a deploy
npm run package # Deterministic extension zips; --profile name:port stamps copies
npm run clean # Remove server/dist and distLicense
Apache-2.0. See LICENSE. Legal attribution in the license is retained for provenance; it is not part of the product name or user-facing identity.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
No tool schema history has been recorded yet.
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.
Related MCP Connectors
Hosted real Google Chrome MCP with per-user persistent state. Navigate, click, type, screenshot.
Browser MCP for logged-in tasks. Uses your Chrome — credentials stay local. Zero-token replay.
Stealth web browser for agents: search, fetch, click, download and type in persistent MCP sessions.
Access Kernel's cloud-based browsers and app actions via MCP (remote HTTP + OAuth).
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables MCP-compatible agents to securely control the user's already authenticated Chrome browser via explicit tab authorization and DOM-based actions.1Apache 2.0
- FlicenseNot gradedqualityCmaintenanceEnables AI assistants to control and interact with a Chrome browser via MCP, providing tools for navigation, screenshots, clicking, form filling, content extraction, and tab management.-
- AlicenseNot gradedqualityAmaintenanceEnables local opencode agents to control a live Chrome browser via MCP tools, including tab management, JavaScript execution, clicking, form filling, page reading, screenshots, and console log retrieval.MIT
- FlicenseNot gradedqualityBmaintenanceEnables local-first management of tabs across multiple Chromium browsers through MCP, providing tab organization, search, personal context, and browser actions over a localhost-only interface.-
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- 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/getcabal/browser-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server