npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

better-browser-mcp

v0.1.2

Published

CLI tool + Chrome Extension that exposes browser automation as a stdio MCP server

Readme

Better Browser MCP

Better Browser MCP is a local Model Context Protocol server for controlling a live Chromium-family browser. It ships as an npm CLI plus a bundled Manifest V3 extension. The CLI speaks MCP over stdio, the extension performs browser work, and the bridge can use Chrome native messaging when registered or the localhost WebSocket bridge for development fallback.

Use it when an MCP host needs browser tabs, navigation, snapshots, interaction, table extraction, DOM search, network logs, dialogs, and JavaScript evaluation against a real browser session.

Quick Start

Run the MCP server from npm and let it launch a browser with the bundled extension:

npx -y [email protected] serve --launch-browser --install-browser --url https://example.com

For an MCP host, configure the package as a stdio server:

{
  "mcpServers": {
    "better-browser-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "[email protected]",
        "serve",
        "--launch-browser",
        "--install-browser"
      ]
    }
  }
}

--launch-browser starts Chrome for Testing, Chromium, or Microsoft Edge with the packaged extension loaded. --install-browser downloads Chrome for Testing if no supported browser is found.

Requirements

  • Node.js 18 or newer.
  • npm or an MCP host that can run npx.
  • Chrome for Testing, Chromium, Microsoft Edge, or another compatible Chromium-family browser.

Chrome for Testing or Chromium is preferred for automated runs because some branded Google Chrome builds ignore command-line unpacked-extension loading. Better Browser MCP can download Chrome for Testing on demand.

Package Commands

Installed package:

npx -y [email protected] serve
npx -y [email protected] serve --launch-browser --install-browser
npx -y [email protected] install-browser
npx -y [email protected] setup-browser --url https://example.com

Local source checkout:

cd tools/better-browser-mcp
npm install
npm run build
npm run build:extension
node dist/cli.js serve --launch-browser --url https://example.com

For shell convenience during local development:

npm link
bb serve --launch-browser --url https://example.com

MCP Host Configuration

The published package is the recommended MCP host target:

{
  "mcpServers": {
    "better-browser-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "[email protected]",
        "serve"
      ]
    }
  }
}

For local development, point the host at the built CLI with an absolute path:

{
  "mcpServers": {
    "better-browser-mcp": {
      "command": "node",
      "args": [
        "<absolute-path-to-repo>/tools/better-browser-mcp/dist/cli.js",
        "serve"
      ]
    }
  }
}

When an MCP host launches serve with stdin piped, the CLI starts the MCP stdio transport and the local browser bridge in the same process. Logs go to stderr so stdout remains reserved for MCP messages.

Browser Launching

Recommended one-command browser bootstrap:

bb serve --launch-browser --install-browser --url https://example.com

Useful launch options:

bb serve --launch-browser \
  --install-browser \
  --browser-channel stable \
  --browser-cache-dir "<browser-cache-dir>" \
  --profile-dir "<browser-profile-dir>" \
  --url https://example.com \
  --remote-debugging-port 9223 \
  --startup-timeout 30000

Use --browser-path "<path-to-chrome-or-edge>" to launch a specific executable. That explicit path takes precedence over downloaded Chrome for Testing and normal browser discovery. Use --chrome-for-testing to force the managed Chrome for Testing path even when another browser exists.

The launcher uses a dedicated profile and loads dist/extension with --load-extension. If you only want to prepare a browser separately, run:

bb install-browser
bb setup-browser --url https://example.com

Manual Extension Loading

Manual loading is useful for development or when you already have a browser session:

  1. Build the extension with npm run build:extension.
  2. Open chrome://extensions.
  3. Enable Developer mode.
  4. Click Load unpacked.
  5. Select tools/better-browser-mcp/dist/extension.
  6. Start bb serve.
  7. Open the Better Browser MCP extension popup and click Connect if needed.

The default local pairing token is better-browser-mcp-local-v1. To use a custom token, start the server with BB_MCP_PAIRING_TOKEN and enter the same value in the extension popup.

Native Messaging Setup

Native messaging is the production-oriented bridge path for extension builds whose IDs are explicitly allowed by the native host manifest. User-scope registration is supported for Chrome, Chromium, and Edge on Windows, macOS, and Linux. System-scope registration is diagnostic-only in this release.

Build the package locally, then validate the current registration state without writing OS registration data:

cd tools/better-browser-mcp
npm install
npm run build
npm run build:extension
node dist/cli.js native-host validate --browser chrome --json

Install a user-scope registration only after you know the extension ID to allow:

node dist/cli.js native-host install --browser chrome --dev-extension-id <32-character-extension-id>
node dist/cli.js native-host diagnostics --browser chrome
node dist/cli.js native-host uninstall --browser chrome

During maintainer testing, --allow-placeholder-origins can write the documented placeholder origins, but that state is not production-finalized. Release builds must use exact chrome-extension://<id>/ origins from the signed extension identity. The current manifest does not include a finalized release key, so do not treat placeholder dev or release IDs as shipped production IDs.

bb native-host run reads Chrome native messaging frames from stdin, bridges validated hub browser commands to the extension native port, forwards extension replies back to the hub, and writes only framed native messages to stdout. Logs and diagnostics go to stderr so MCP stdio output is not polluted.

The extension prefers native messaging in auto mode when chrome.runtime.connectNative() is available and a compatible host accepts the handshake. If native messaging is unavailable or rejected in auto mode, the extension falls back to the localhost WebSocket bridge. Users can continue to use bb serve --launch-browser and manual WebSocket pairing for development.

MCP Tools

| Tool | Purpose | | --- | --- | | browser_launch | Launches Chrome for Testing, Chromium, or Edge with the bundled extension loaded. | | browser_diagnostics | Returns hub, extension, launch, active-tab, pending-request, and last-error diagnostics. | | browser_open_tab | Opens a tab. | | browser_open_fresh_tab | Opens and focuses a recovery tab without touching the currently blocked tab. | | browser_close_tab | Closes a tab. | | browser_list_tabs | Lists tabs with IDs, URLs, titles, and active state. | | browser_focus_tab | Focuses a tab and its window. | | browser_navigate | Navigates a tab and can wait for load completion. | | browser_snapshot | Returns text, summary, DOM, accessibility, or saved screenshot output. | | browser_click | Clicks the first element matching a CSS selector. | | browser_type | Sets input text and dispatches DOM events. | | browser_scroll | Scrolls by coordinates or direction and amount. | | browser_select | Selects an option by value or visible text. | | browser_evaluate | Runs JavaScript in the page through CDP Runtime.evaluate. | | browser_wait_for | Waits for a selector or network-idle condition. | | browser_extract | Extracts structured data from selector matches. | | browser_extract_table | Extracts visible native HTML tables and ARIA/grid-like tables. | | browser_dialogs | Inspects active JavaScript dialogs known to the extension. | | browser_handle_dialog | Accepts or dismisses an active JavaScript dialog. | | browser_network_log | Returns recent captured network entries for a tab. | | browser_search_dom | Searches page text, selectors, or regex matches with result caps. | | browser_read_cursor | Reads a bounded follow-up page from an opaque cursor returned by a large browser output. |

Long-running tools accept an optional timeout in milliseconds. The default is 30000.

Tool results return a short human-readable content block plus machine-readable structuredContent:

{
  "ok": true,
  "requestId": "tabs-1",
  "action": "listTabs",
  "timestamp": "2026-04-25T00:00:00.000Z",
  "data": []
}

Recoverable browser, connection, artifact, cursor, and limit failures return isError: true with structured error codes such as disconnected, auth_failed, incompatible_version, timeout, payload_too_large, rate_limited, concurrency_limited, artifact_not_found, artifact_expired, cursor_not_found, and cursor_expired.

CLI Usage

Run bb serve in one terminal, connect the extension, then use commands from another terminal:

bb status
bb install-browser
bb setup-browser --url https://example.com
bb tabs
bb open https://example.com
bb fresh-tab about:blank
bb navigate <tabId> https://example.com/docs
bb snap <tabId> text
bb snap <tabId> summary
bb snap <tabId> accessibility
bb snap <tabId> dom --max-chars 12000
bb snap <tabId> screenshot jpeg
bb click <tabId> "button[type=submit]"
bb type <tabId> "input[name=q]" "search text"
bb select <tabId> "select[name=country]" "Singapore"
bb scroll <tabId> down 800
bb eval <tabId> "document.title"
bb eval <tabId> --file ./scripts/page-check.js
bb extract-table <tabId> --all
bb dialogs <tabId>
bb dialog <tabId> accept --prompt-text "approved"
bb search <tabId> "login" text --max-results 20
bb network-log <tabId> 20

Commands that wait on browser work accept --timeout <ms>:

bb navigate <tabId> https://example.com/docs --timeout 60000
bb snap <tabId> summary --max-chars 12000 --timeout 45000
bb eval <tabId> "document.title" --timeout 10000

Snapshot Modes

| Mode | Output | | --- | --- | | text | Visible page text capped by maxChars. | | summary | Readable headings, paragraphs, lists, tables, captions, and article text. | | accessibility | Pruned accessibility tree with node and character caps. | | dom | Clean visible HTML with scripts, styles, SVG, hidden nodes, and noisy attributes removed. | | screenshot | PNG or JPEG visible-tab screenshot. Explicit saveTo writes a file; omitted saveTo stores the screenshot as an MCP artifact resource. |

Large text, DOM, accessibility, network, DOM-search, and table outputs are capped. Small results stay inline where practical; large results include artifact and, when pageable, cursor metadata.

Artifact Resources And Cursors

Large browser outputs are stored in a bounded in-process artifact store instead of forcing oversized inline payloads. Artifact URIs use this scheme:

bb-artifact://browser/{kind}/{id}

Supported artifact kinds are text, dom, accessibility, screenshot, network, search, and table. Metadata includes URI, MIME type, byte size, creation time, expiration time, source tab or URL when available, and truncation state.

Default limits:

| Setting | Default | Environment override | | --- | ---: | --- | | Artifact count | 100 | BB_MCP_ARTIFACT_MAX_COUNT | | Artifact total bytes | 32 MiB | BB_MCP_ARTIFACT_MAX_TOTAL_BYTES | | Artifact TTL | 10 minutes | BB_MCP_ARTIFACT_TTL_MS | | Cursor page size | 50 items | BB_MCP_CURSOR_DEFAULT_PAGE_SIZE | | Cursor max page size | 500 items | BB_MCP_CURSOR_MAX_PAGE_SIZE | | Cursor TTL | 10 minutes | BB_MCP_CURSOR_TTL_MS |

Use browser_read_cursor with the returned opaque cursor to fetch the next page:

{
  "cursor": "cursor-1-...",
  "pageSize": 100
}

Expired or missing artifacts and cursors return recoverable structured errors.

Runtime Limits And Diagnostics

Better Browser MCP enforces release-grade limits before browser work is dispatched:

| Limit | Default | | --- | ---: | | Session requests | 300 per 60 seconds | | Pending requests | 25 | | Per-tab concurrency | 3 | | Tool input payload | 1 MiB | | Native browser-to-host frame | 16 MiB | | Native host-to-browser frame | 1 MiB | | WebSocket message | 16 MiB |

High-risk operations such as launch, evaluate, snapshot, network log, DOM search, extraction, interaction commands, artifact reads, and cursor reads also have per-tool rate and concurrency limits. Rate-limit responses include retry-after metadata.

Diagnostics persist to ~/.better-browser-mcp/diagnostics.json by default, or to BB_MCP_DIAGNOSTICS_PATH when set. bb diagnostics and the browser_diagnostics MCP tool report native and WebSocket transport state, browser launch metadata, active tab summary, pending and recent requests, recent errors, rate-limit events, artifact-store metrics, and diagnostics-file recovery state. Secrets, credentials, pairing tokens, request bodies, scripts, screenshots, DOM/HTML, and credential paths are redacted before persistence and output.

Credentials And Secret Placeholders

For local testing against signed-in sites, Better Browser MCP resolves explicit placeholders in the form {{secret:NAME}}. Credential names must match [A-Za-z_][A-Za-z0-9_]*.

Set a vault passphrase before creating or reading an encrypted vault:

$env:BB_MCP_CREDENTIALS_KEY = Read-Host "Vault passphrase"

Manage credentials with bb credentials:

Get-Content .\local-app-username.txt | bb credentials add APP_USERNAME --stdin
bb credentials add APP_PASSWORD --value-file .\local-app-password.txt
bb credentials update APP_PASSWORD --value-file .\rotated-app-password.txt
bb credentials list
bb credentials remove APP_PASSWORD
bb credentials import .\local-credentials.env --overwrite

add fails when the credential already exists unless you pass --replace. import reads normal NAME=value .env entries, ignores blank lines and comments, merges by default, fails on existing names, overwrites matching names with --overwrite, and replaces the vault with only imported names when --replace is used. Command output reports names, counts, vault paths, and storage sources only; it does not print credential values. Prefer --stdin or --value-file over --value so shell history does not capture a secret.

The encrypted vault uses AES-256-GCM with a PBKDF2-derived key. The passphrase is not stored. Vault writes use a same-directory temporary file, an atomic rename where the filesystem permits it, and best-effort 0600 permissions.

Storage selection:

  • --vault <path> selects an explicit encrypted vault and overrides all other vault paths.
  • BB_MCP_CREDENTIALS_VAULT selects a vault when --vault is not provided.
  • BB_MCP_CREDENTIALS_TEST_DIR selects <directory>/vault.json for tests and one-off smoke checks when no explicit vault is set.
  • The default managed vault is user-local: %LOCALAPPDATA%\better-browser-mcp\credentials\vault.json on Windows when LOCALAPPDATA is set, $XDG_DATA_HOME/better-browser-mcp/credentials/vault.json when XDG_DATA_HOME is set, otherwise ~/.local/share/better-browser-mcp/credentials/vault.json.
  • Existing package-local credentials/vault.json is still readable as a legacy fallback, but new managed writes do not use it by default.

At runtime, placeholders are resolved from the encrypted vault first, then from process environment variables such as APP_USERNAME when the name is not in the vault. Start the server with the same passphrase:

$env:BB_MCP_CREDENTIALS_KEY = Read-Host "Vault passphrase"
bb serve --launch-browser --url https://example.com

Example MCP argument:

{
  "tabId": 123,
  "selector": "input[name=username]",
  "text": "{{secret:APP_USERNAME}}"
}

Placeholders are resolved inside the MCP server before sending browser requests to the extension. The CLI also resolves placeholders for text-bearing commands such as bb type, bb select, bb eval, and bb dialog ... accept --prompt-text.

Troubleshooting:

  • Credentials vault exists, but BB_MCP_CREDENTIALS_KEY is not set.: set BB_MCP_CREDENTIALS_KEY before reading the vault.
  • Could not decrypt credentials vault.: check that the passphrase and selected vault path match.
  • Credential name must match [A-Za-z_][A-Za-z0-9_]*.: rename the credential to match the {{secret:NAME}} placeholder rules.
  • Credentials already exist: rerun import with --overwrite to merge over matching names or --replace to replace the vault contents.

For compatibility with older local workflows, scripts/encrypt-credentials.mjs can still encrypt a staging .env file into a v1 vault:

node scripts\encrypt-credentials.mjs .\local-credentials.env .\credentials\vault.json

Use bb credentials for normal add, update, remove, list, and import workflows. Keep local .env, generated vault.json, temp vaults, diagnostics, and package tarballs out of commits.

Architecture

Better Browser MCP is split into these runtime pieces:

| Component | Location | Role | | --- | --- | --- | | CLI and MCP server | src/cli.ts, src/tools/*.ts | Runs serve, registers MCP tools, writes logs to stderr, and speaks MCP over stdio. | | Browser launcher | src/browser-installer.ts, src/browser-launcher.ts | Finds or downloads a Chromium-family browser, creates a dedicated profile, and loads the bundled extension. | | WebSocket hub | src/ws-hub.ts, src/protocol.ts | Binds to 127.0.0.1:3333, validates handshakes, authenticates the local pairing token, and routes requests. | | Native host runtime | src/native-host-runtime.ts, src/native-host-cli.ts | Reads and writes Chrome native messaging frames, validates extension origins and protocol version, routes hub browser commands through the extension native port, and returns structured native errors. | | Artifact and cursor store | src/artifacts.ts | Stores bounded large outputs as MCP resources and paginates large result sets with opaque cursors. | | Runtime limits and diagnostics | src/runtime-limits.ts, src/runtime-diagnostics.ts | Enforces rate, concurrency, and payload limits and persists redacted diagnostics. | | Chrome extension | extension/background.ts, extension/bridge.ts, extension/popup/* | Chooses native or WebSocket transport, runs Chrome API/CDP/page-script work, and returns results. |

MCP request flow:

  1. The MCP host launches better-browser-mcp serve over stdio.
  2. The server starts the local WebSocket hub and optionally launches a browser with the extension loaded.
  3. The extension attempts native messaging in auto mode when available; after native handshake, the native host links that port to the local hub so MCP requests route over native frames. If native messaging is unavailable or rejected, the extension connects to ws://127.0.0.1:3333 with a versioned hello handshake and pairing token.
  4. MCP tool handlers send tab, snapshot, interaction, network, dialog, or extraction requests through the hub.
  5. The extension executes the browser operation and returns { id, result } or { id, error }.
  6. The MCP tool returns content, structuredContent, and isError when applicable.

The bridge is intentionally local-first. It binds WebSocket fallback to loopback, uses a local pairing token, validates Origin headers when Chrome provides them, rejects incompatible protocol versions, caps message sizes, rejects unallowlisted native extension origins, stores large artifacts through bounded MCP resources, and persists redacted diagnostics. Maintainer-facing release identity and native messaging details are in the source repository under docs/release-native-messaging.md; that repo-only document is not included in the npm tarball.

Published Package Contents

The npm package is intentionally small. package.json publishes only:

  • dist/cli.js
  • dist/native-host-cli.js
  • dist/extension/**
  • README.md
  • package.json
  • credentials/.env.example
  • scripts/encrypt-credentials.mjs

Repo-only documentation under docs/, ARCHITECTURE.md, prd.json, progress.txt, tests, source files, fixtures, and local credential files are not included in the package tarball. Use the package README for installed-user setup, and the repository docs for maintainer release checks and architecture details.

Troubleshooting

bb status says the hub is not running:

bb serve

The extension popup stays disconnected:

  • Confirm bb serve is still running.
  • Confirm the extension was loaded from dist/extension, not the source extension directory.
  • Click Connect in the popup.
  • Reload the extension from chrome://extensions if the service worker stopped unexpectedly.
  • Use Chrome for Testing or Chromium for automated verification if branded Google Chrome does not load the unpacked extension through automation.

An enterprise page is stuck, dialog-blocked, or no longer responding:

bb status
bb dialogs <tabId>
bb fresh-tab about:blank

Port 3333 is already in use:

bb serve --takeover

Native host validation reports placeholder origins:

  • Reinstall with exact --dev-extension-id, --release-extension-id, or repeated --allowed-origin values.
  • Keep placeholders only for maintainer tests that are explicitly not production-finalized.

Native messaging fails and the extension falls back to WebSocket:

  • Run bb native-host diagnostics --browser chrome for the target browser.
  • Confirm the registered host path points to dist/native-host-cli.js.
  • Confirm the loaded extension ID exactly matches one registered allowed_origins entry.
  • Use bb diagnostics to compare native transport and WebSocket fallback state.

Some pages fail to interact:

  • Chrome-protected pages such as chrome:// pages cannot be automated like normal web pages.
  • Selectors must match elements in the page. Use browser_snapshot or browser_search_dom to inspect candidate selectors.
  • Chrome debugger warnings are expected for CDP-backed tools such as snapshots, evaluation, and network-idle waits.

Development Checks

npm run typecheck
npm test
npm run build
npm run build:extension
npm pack --dry-run
node dist/cli.js native-host validate --browser chrome --json
node dist/cli.js native-host run --allowed-origin chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/
powershell -ExecutionPolicy Bypass -File scripts/test-chromium-extension.ps1
cd extension && npx tsc --noEmit

Project Layout

tools/better-browser-mcp/
+-- src/
+-- extension/
+-- fixtures/
+-- scripts/
+-- credentials/
+-- docs/
+-- package.json
+-- tsconfig.json
+-- tsup.config.ts
+-- README.md

License

No open-source license has been declared for this package. The package metadata is UNLICENSED.