browser-relay-mcp

balakumardev/localvily
0 starsCommunity

Install to Claude Code

This server doesn't publish a one-line install command. Follow the setup in the source repository.

Summary

Unlimited, session-authenticated web search and fetch for AI tools using your own browser. Supports authenticated/paywalled pages without API keys.

README.md

browser-relay-mcp

Unlimited, session-authenticated web search + fetch for your AI tools — driven through your own browser, behind a local MCP server.

No API keys, no per-query billing, no rate-limit walls. Searches and page fetches run in a real browser that is already logged into the sites you use, so paywalled / authenticated pages come back as clean readable text just like you'd see them.

Two interchangeable drivers sit behind one MCP surface:

  • relay (default) — drives your logged-in Chrome via a loaded extension. The extension polls the local relay at http://localhost:15552, runs the search/fetch in a tab, and posts the result back. Your sessions, your cookies.
  • cloak — an embedded patchright stealth browser, launched in-process by the relay. For bot-protected or unattended pages where you don't want to (or can't) use your attended Chrome. Requires patchright install chromium once.

Every MCP tool takes a driver parameter, so a caller can pick per-request.

Architecture

                          ┌─────────────────────────────────────────┐
  MCP client              │  browser-relay-mcp                        │
  (Claude, STORM, …)      │                                           │
        │                 │   MCP server  (stdio / FastMCP)           │
        │  search/fetch/  │       │                                   │
        │  search_and_    │       │  HTTP                             │
        │  fetch/resume/  │       ▼                                   │
        └───────────────► │   relay  (FastAPI @ localhost:15552)      │
           tool call      │       │                                   │
                          │       ├── driver=relay ─► job queue ──────┼──┐
                          │       │                                   │  │ poll /pending
                          │       │                                   │  │ post /result
                          │       └── driver=cloak ─► patchright ─────┼─┐│
                          └─────────────────────────────────────────┘ ││
                                                                       ││
                  ┌────────────────────────────────────────────────┐  ││
                  │  embedded stealth Chromium (in-process) ◄────────┼──┘│
                  └────────────────────────────────────────────────┘   │
                  ┌────────────────────────────────────────────────┐   │
                  │  YOUR logged-in Chrome  +  Browser Relay ext ◄───┼───┘
                  │  (polls localhost:15552, runs job in a tab)      │
                  └────────────────────────────────────────────────┘

The MCP server auto-starts the relay backend on first use (a local FastAPI process on 127.0.0.1:15552). The relay driver needs the Chrome extension loaded and pointed at that URL; the cloak driver needs patchright install chromium.

Install

1. The MCP server

Run it with uv — no checkout required:

uvx browser-relay-mcp

Add it to your MCP client config:

{ "mcpServers": { "browser-relay": { "command": "uvx", "args": ["browser-relay-mcp"] } } }

2. The Chrome extension (for the relay driver)

  1. Open chrome://extensions, enable Developer mode.
  2. Load unpacked → select the extension/ directory (or unzip a packaged build — see Packaging).
  3. Open the extension's options and set the Relay server URL to http://localhost:15552.
  4. The popup status should read Relay OK / connected.

The extension only acts on pages the relay explicitly tells it to drive — see docs/privacy-policy.md.

3. The cloak browser (for the cloak driver)

One-time, to download the stealth Chromium build patchright uses:

patchright install chromium

If you only ever use driver: "relay", this step is optional. When it's missing, /health reports the cloak driver as unavailable instead of failing your relay calls.

Tools

All tools return a JSON string. The driver parameter selects which backend runs the request.

| Tool | Signature | Returns | |------|-----------|---------| | search | search(query, k=10, engine="bing", driver="relay") | {status, query, engine, driver, count, results:[{title, url, snippet}]} | | fetch | fetch(url, include_html=False, driver="relay") | {status, url, driver, title, text, excerpt, length[, html]} | | search_and_fetch | search_and_fetch(query, k=5, engine="bing", driver="relay") | {status, query, engine, driver, count, results:[{title, url, snippet, text, length, fetch_error}]} | | resume | resume(resume_token) | the completed result, action_required again if still blocked, or error if the token expired | | health | health() | relay + extension + cloak connectivity and queue depth (see below) |

search_and_fetch runs the search, then fetches the top-k results in parallel. A page that fails to fetch records its own fetch_error and text=""; the batch still returns. Per-result escalation inside the batch is non-interactive — a blocked result is reported as fetch_error: "action_required: <action>", and you re-drive it with an individual fetch(url).

health shape

{
  "status": "ok",
  "extension_connected": true,
  "extension_status": "connected",
  "last_poll_age_seconds": 2.1,
  "search_queued": 0,
  "fetch_queued": 0,
  "in_flight": 0,
  "max_fetch_tabs": 5,
  "engine": "bing",
  "pending_actions": [],
  "version": "0.1.0",
  "drivers": {
    "relay": { "extension_connected": true, "extension_status": "connected", "last_poll_age_seconds": 2.1 },
    "cloak": { "available": true, "profile_path": "…/browser-relay/cloak-profile", "pages_open": 0 }
  }
}

extension_status is one of connected, stale (extension was seen but hasn't polled within ~75s), or never_seen. The cloak block reports available: false with an error when patchright's browser isn't installed.

Escalation flow (CAPTCHA / login)

When a search or fetch hits a CAPTCHA or a login wall, the call doesn't fail — it pauses and surfaces the blocked browser to you:

search/fetch  ──►  status: "action_required"
                   { driver, action: "solve_captcha" | "login",
                     message, resume_token, query|url }
                          │
        you solve the CAPTCHA / sign in
        in the surfaced browser window
                          │
                          ▼
        resume(resume_token)  ──►  status: "ok" (completed result)
                                   status: "action_required" (still blocked — solve again, same token)
                                   status: "error" (token expired, default TTL 300s)
  • relay: the extension holds the tab open; you solve it in your Chrome.
  • cloak: the embedded browser window is non-headless on purpose so you can solve the challenge there.

resume reuses the held tab/page rather than starting over, and the same resume_token stays valid across repeated action_required rounds until it succeeds or the TTL (BROWSER_RELAY_ACTION_TTL, default 300s) expires.

STORM adapter

adapters/storm.py maps a search_and_fetch result into the STORM / dspy retriever shape, so browser-relay can back STORM's knowledge-curation retrieval:

from adapters.storm import to_storm

# `result` is the parsed dict from search_and_fetch(...)
sources = to_storm(result)
# -> [{ "url", "title", "description", "snippets": [chunks of the page text] }, ...]

Only results that actually came back with text are included; page text is chunked (1000 chars/chunk) into snippets.

Run the acceptance checks

The PRD acceptance criteria are split between a live human-attended script and unit tests.

Live (C1–C4) — requires Chrome with the extension loaded and signed in:

# 1. start the relay
cd server && uv run browser-relay-mcp --backend --port 15552

# 2. in Chrome: load unpacked extension/, set server URL to http://localhost:15552 (status: connected)

# 3. run the checks
uv run --with httpx python tests/acceptance/run_acceptance.py

Expected output ends with ALL ACCEPTANCE CRITERIA PASSED. See tests/acceptance/README.md for what each criterion verifies (C3 is the headline test: 50 sequential searches with 0 errors and 0 silent-empty result sets).

STORM adapter (C5) — unit test:

cd adapters && python -m pytest tests/test_storm.py

Server + cloak suites:

cd server && uv run pytest                      # full server suite
BROWSER_RELAY_RUN_CLOAK_TESTS=1 uv run pytest   # include live cloak tests

Extension unit tests:

npm test

Packaging the extension

bash scripts/package-extension.sh
# wrote dist/browser-relay-extension.zip

This regenerates the injected bundles (npm run build:inject) and zips extension/ (tests excluded) for the Chrome Web Store or manual install. dist/ is gitignored.

Configuration

| Env var | Default | Purpose | |---------|---------|---------| | BROWSER_RELAY_URL | http://127.0.0.1:15552 | Relay base URL the MCP server talks to | | BROWSER_RELAY_FETCH_CAP | 5 | Max parallel fetch tabs | | BROWSER_RELAY_SEARCH_CONCURRENCY | 1 | Concurrent searches (near-serial by design) | | BROWSER_RELAY_SEARCH_MIN_SPACING_MS | 500 | Minimum spacing between search dispatches | | BROWSER_RELAY_ACTION_TTL | 300 | Seconds a paused (action_required) request stays resumable | | BROWSER_RELAY_DEFAULT_ENGINE | bing | Default search engine | | BROWSER_RELAY_CLOAK_PROFILE_DIR | user cache dir | Persistent profile for the cloak browser |

Related MCP servers

Browse all →