Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Browden

БесплатноНе проверен

Read-only browser MCP server — a local, configurable MCP shell around real Chrome for LLM agents.

GitHubEmbed

Описание

Read-only browser MCP server — a local, configurable MCP shell around real Chrome for LLM agents.

README

CI License: Apache 2.0 Python 3.11+ MCP server

A local, cross-platform, read-only (configurable) MCP shell around a real Chrome browser. It lets an LLM agent look at and navigate the web through your own browser. The agent can read pages, query the DOM, and take screenshots, but can never execute any write action. The MCP is configurable to allow button-clicks and text-fill, allowlisted per website and visible element.

Short demo video: https://youtu.be/q-W3Z9nlj58

Disclaimer

With great power comes great responsibility: Only point this MCP to sites whose Terms of Service permit automated access.

When to use browden

browden is deliberately narrow: safe, local, undetected, and read-only, with allowlisted writes. This allows your agent to run wild on your own logged-in Chrome. Use browden for your daily research needs + a few writes. Defer to richer automation tools when you need to drive the browser rather than read it.

Typical usecases:

  • Let an agent read and navigate your logged-in pages while it stays structurally unable to click "Buy", send mail, or delete anything.
  • You need a prompt-injection perimeter around the agent.

When NOT to use browden

If you need… Consider
Full write automation and agentic task loops browser-use/browser-use
A complete Playwright tool surface over MCP (click, type, fill, upload) microsoft/playwright-mcp
Cloud browsers at scale, or natural-language act/extract/observe browserbase/mcp-server-browserbase

Key Features

  • Read-only by default. The agent gets a small, audited surface: list/open/ close/select tabs, navigate, read the DOM, screenshot. All write actions must be allowlisted by each domain, each action and each element on which action is being taken.
  • A trusted-website perimeter for reads. Even reading untrusted websites exposes your agent to a myriad of prompt-injections. The MCP allowlists readable websites using Tranco top sites and an overridable list.
  • Safe on your logged-in Chrome. Because the agent can't take write actions on your browser, you can point browden at your primary Chrome profile and let it reuse your existing logins — the agent can read your logged-in pages but cannot click "Buy", change settings, send mail, or delete anything.
  • No data exfiltration, all local. It runs entirely on your machine and drives a Chrome on your machine. No cloud, no proxy — nothing about your browsing leaves the host.
  • One-click install for Linux/Mac (minimal for Windows). A single setup script installs a background service and prints the exact config block to paste into your agent.
  • Platform-agnostic. The same setup script and tool surface run on Linux, macOS, and Windows, each using the OS's native service manager (systemd / launchd / Task Scheduler).
  • Modest resource usage. The server process holds to ~150 MB — see perf_benchmark.

Quick start

git clone --branch stable https://github.com/nishantsny/browden.git
cd browden
python3 setup/onetime_setup.py

--branch stable installs the latest release — the stable channel only ever advances to tagged releases, never mid-flight main. To track development instead, clone without --branch stable (that follows main). To update later: git -C browden pull --ff-only.

The setup script will print a MCP config (sample below), paste that into your agent's MCP config (e.g. ~/.claude.json)

"mcpServers": {
  "browden": {
    "command": "/path/to/browden/.venv/bin/python",
    "args": ["-m", "browden.mcp.server", "--allowlist", "/home/you/.browden/allowlist.yaml"],
    "env": { "DISPLAY": ":0" }
  }
}

For quick test, restart your agent and ask it to open a tab and read a page.

If you prefer a persistent mcp process, use setup/onetime_setup.py --mode service, details in Installation reference.

Dependencies

Requires Python ≥ 3.11 and Google Chrome on the host. Setup is the same clone-and-run on every OS; the notes below only cover what differs per platform.

Linux

  • Run the setup command with python3.
  • Headed Chrome needs an X11 DISPLAY (the env block in the stdio config); on a machine with no display, set BROWDEN_HEADLESS=1.
  • --mode service installs a systemd user unit.

macOS

  • Run the setup command with python3.
  • Chrome is found at /Applications/Google Chrome.app (or ~/Applications); no DISPLAY is needed.
  • --mode service installs a launchd LaunchAgent.

Windows

  • Run in PowerShell (or Windows Terminal), and use py -3 instead of python3 — e.g. py -3 setup\onetime_setup.py. No administrator rights are needed.
  • Chrome is found under Program Files.
  • --mode service installs a Task Scheduler logon task.

Tools

browden exposes tools over a swappable WebNavigatorBackend (Selenium + Chrome by default). Each tool that acts on a specific tab takes the tab's id — the value returned by new_blank_tab / list_tabs. Pass it back verbatim; it is globally unique and routes itself to the right profile (multiple user profiles are supported).

Tool What it does
list_tabs List every open tab across all profiles
new_blank_tab Open a new tab (optionally in a chosen profile_dir)
select_tab Focus a tab by id
navigate Point a tab at a URL (gated by the read allowlist)
close_tab Close a tab by id
get_element_by_id document.getElementById, server-side
get_elements_by_class_name document.getElementsByClassName, server-side
query_selector document.querySelector, server-side
query_selector_all document.querySelectorAll, server-side (paginated)
screenshot PNG of the tab's current viewport
force_reload_tab Reload a tab and refresh its cached DOM
click A write action, off by default — click a control on a host listed in the click allowlist; each host declares a required label regex the control's visible text must fully match (.* to allow any). No host is listed out of the box.
insert_text A write action, off by default — type text into a single visible, non-readonly text field (<textarea>, a text <input>, or a contenteditable) on a host listed in the separate write-text allowlist section; the field's visible label (placeholder / aria-label / associated <label>) must fully match that host's required label regex.

Profiles

A profile is equivalent to Chrome's --user-data-dir: one browsing session with its own cookies, storage, and logins. The crucial rule:

One profile = one Chrome window at a time. A profile directory can be held by only a single Chrome process (it's guarded by Chrome's SingletonLock).

browden keeps one browser session per profile, launched lazily on first use. That has two consequences:

  • Different profiles run in parallel. Give a request its own profile_dir and it gets an independent Chrome process — so separate profiles can be driven concurrently.
  • Within one profile, the agent drives one tab at a time — but concurrent requests are safe. A single session has one focused window, so browden serializes every request to that profile behind a per-session lock: each one waits its turn, then re-selects its own tab before acting, so a burst of parallel calls to ten tabs returns ten correct answers instead of racing over the shared window. Concurrency here buys safety, not speed — the calls still run one after another. For genuine parallelism, use separate profiles. A request that waits more than 10s for its turn gives up and returns {"error": "browser session busy — ...", "id": ...}; it's safe to retry.

Which profile should I use?

  • Let the agent create one (or pass a fresh profile_dir) when you just want the agent to drive a browser. This is the normal, friction-free path. The profile is persisted, so login will persist across restarts.
  • Point it at your real Chrome profile to reuse your existing logins. Since that profile can only be open in one window, browden becomes that window: you can watch it, but you shouldn't also run your everyday Chrome on the same profile at the same time, and the window is there for the agent to drive — not for you to click around in. Note that the read perimeter is enforced on the whole window: a tab parked on a site outside the read allowlist is closed when the agent lists tabs (so it can neither read it nor learn it exists) — don't keep tabs you care about open in a browden-driven window.

Safety: the allowlist

Every URL is checked before Chrome is told to go there. Merely navigating to a hostile page is risky. Each page's content is fed to the LLM and can lead to prompt injection. The "allowlist" policy has three layers, evaluated in order (first match wins):

  1. denylist — host/path rules that are always refused, before anything else. Wins over the allowlist below, even when reads are disabled. Empty by default.

  2. read allowlist — a URL may be read/navigated only if it is allowed by:

    • website_overrides — explicit (host, path) regexes (host * = any host). An override for a host triumphs over Tranco: once a host is listed here, its rule alone decides — so you can allow a host Tranco doesn't rank, or path-scope (or effectively block) one Tranco would otherwise wave through (reddit.com: ["^/r/pics/"]). Setting "*": [".*"] re-opens the whole web.
    • Tranco top-sites — for any host without an override, a local, offline snapshot of the top ~1m most-visited domains (fetched next to your allowlist by setup, not committed). A listed domain covers its subdomains (google.commail.google.com) but not lookalikes (google.com.evil.co). The cutoff (top_n) is configurable, and the whole read allowlist can be switched off (read.enabled: false) for a trusted throwaway profile. Popularity is a proxy for established, never a guarantee of safe — reputable sites host untrusted content too, so this shrinks attack surface rather than removing it.
    • scheme — orthogonal to host: only https is accepted, so file:// / ftp:// / data: can never reach Chrome even on a permissive host rule. A non-https scheme is allowed only for a host you name explicitly in website_overrides — e.g. localhost: [".*"] re-enables http://localhost, and "": ["^/home/me/.*"] re-enables file:// under that path. A blanket "*": [".*"] opens the web for https but does not silently re-enable non-https everywhere.

    Navigation also re-checks the URL the browser lands on after any redirect, so an open redirect on an allowlisted site (or a server-side 302) can't silently park the tab off-allowlist — an off-list landing resets the tab to about:blank.

  3. write actionsclick and insert_text are both default-deny, each gated by its own allowlist section (click and write-text), so permitting typing never implies permitting clicks, or the reverse. Each action must be enabled per domain. On each domain, the allowlist mandates a label regex, which must match the control's user-visible text (for click) or the field's user-visible label (for insert_text). Any control can be explicitly enabled via label: '.*'. The denylist vetoes both too; page-injected agent-targeted decoys are always refused regardless of the label.

To refresh the Tranco snapshot, use python3 setup/fetch_tranco.py and restart the MCP server.

Protecting browden's own files

The policy only holds if the agent can't rewrite it. That means the allowlist, the Tranco/PSL snapshots next to it, and browden's source — an agent that edits any of them widens its own access. Setup prints both steps at the end:

  1. Deny agent edits to every browden file (~/.browden/** and the install tree) in your agent's settings — "ask" if you'd rather approve each edit, never auto-approve.

  2. Lock them down at the OS level. Permission rules only gate the agent's file tools; any shell it runs (Bash, python -c, sed -i) writes to them directly, and command rules are trivially rephrased around. Make the files root-owned and read-only instead — browden only ever reads them:

    sudo chown -R root:root ~/.browden
    sudo find ~/.browden -type d -exec chmod 755 {} +   # +x = traverse, keep it
    sudo find ~/.browden -type f -exec chmod 444 {} +   # data, never executable
    

    Afterwards, refreshing the snapshots takes sudo.

Sample allowlists

Technical design

The agent never touches Chrome directly. Every tool call crosses the same audited path: the MCP server validates and routes it, a per-profile session serializes it onto the backend, and only the backend speaks to Chrome (over the DevTools protocol on a private debugging port). The agent only ever sees the tools and their JSON results.

sequenceDiagram
    actor Agent as LLM agent
    participant MCP as MCP server (FastMCP)
    participant Store as SessionStore
    participant Session as Session (per profile)
    participant Backend as Chrome backend
    participant Chrome as Chrome (real profile)

    Agent->>MCP: navigate(url, id)  · via SSE/stdio
    MCP->>MCP: validate_url(url) against the allowlist
    MCP->>Store: route(id)
    Store-->>MCP: session_handle, tab_id
    MCP->>Session: session_handle.navigate(url, tab_id)
    Note over Session: one driver op at a time<br/>(off the event loop)
    Session->>Backend: drive.navigate(url, tab_id)
    Backend->>Chrome: DevTools/CDP on the debug port
    Chrome-->>Backend: rendered page
    Backend-->>Session: TabInfo / parsed DOM
    Session-->>MCP: JSON (with id)
    MCP-->>Agent: result

Key points of the flow:

  • The MCP process owns policy (URL allowlist, write-action gating) and the set of sessions. It resolves a request's profile_dir to a concrete path, builds a backend for it, and hands that to the session store.
  • The session store keeps one session per profile and routes each tab id (a <profile>-<handle> composite) back to the session that owns it.
  • The session is the async coordinator: it runs the synchronous, non-thread- safe Selenium backend off the event loop, one operation at a time, and manages the per-tab DOM cache and idle-tab cleanup. Cleanup is a background pass on a timer (infra.reap_interval_seconds, 2h by default) — never work done on a tool call — that closes tabs the agent hasn't touched in an hour and forgets tabs the human closed in the browser. A tab is therefore closed between 1h and 1h + one interval after its last use; only agent activity counts as use.
  • The backend is the only code that imports a browser library. It launches Chrome itself — a plain google-chrome --user-data-dir=… --remote-debugging- port=… subprocess — and attaches Selenium over the DevTools port. It deliberately avoids letting ChromeDriver spawn Chrome, because ChromeDriver injects automation switches (--enable-automation, AutomationControlled) that set navigator.webdriver = true and show the "controlled by automated software" banner. Launching Chrome ourselves keeps the window indistinguishable from an ordinary, human-run browser.

Installation reference

python3 setup/onetime_setup.py creates a venv at .venv and installs browden into it (via uv sync --frozen, pinned by the committed uv.lock; falls back to stdlib venv + pip when uv is absent), copies the sample allowlist to ~/.browden/allowlist.yaml (never overwriting an existing one), fetches the Tranco snapshot next to it, and prints the JSON block to add to your agent plus the hardening steps in Protecting browden's own files. It's idempotent, and defaults to stdio (shown in Quick start) — pass --mode service for the persistent SSE service below.

Useful flags: --mode (stdio default, or service), --config-dir, --venv and --python (use your own interpreter and skip venv creation), --display, and — for service mode — --port and --service-name (stand up a second instance without touching the first).

Background service over SSE

Runs browden as a background service via the host's native service manager — systemd (Linux), launchd (macOS), or Task Scheduler (Windows) — so the Chrome session stays warm across agent restarts, serving SSE on port 22001 pinned to the venv.

git clone --branch stable https://github.com/nishantsny/browden.git
cd browden
python3 setup/onetime_setup.py --mode service

The same command works on all three platforms — each writes its native service description:

OS Service manager What gets written
Linux systemd (user) ~/.config/systemd/user/<name>.service
macOS launchd ~/Library/LaunchAgents/<name>.plist
Windows Task Scheduler a logon-triggered task running a windowless launcher

(Chrome is located on PATH, then at the OS's canonical install location — macOS /Applications, Windows Program Files; point BROWDEN_CHROME_BINARY at it if it lives elsewhere.)

Then paste the printed block into your agent's MCP config:

"mcpServers": {
  "browden": {
    "type": "sse",
    "url": "http://127.0.0.1:22001/sse"
  }
}
Refreshing the allowlisted domains

The Tranco top-sites list the read allowlist uses is an offline snapshot fetched into your config dir next to allowlist.yaml (not committed), so it doesn't update on its own. Refresh it, then restart the service to load the new list:

python3 setup/fetch_tranco.py                    # re-download the top-1m snapshot
systemctl --user restart browden.service   # reload it into the running server

fetch_tranco.py writes the snapshot into your config dir (~/.browden by default) — the very file the server reads — so the restart is all it takes to load the new list. Pass --top-n N to keep a different number of domains, --config-dir if you installed elsewhere, and use your own --service-name in the restart if you installed under one. (If you instead did a non-editable install, point --out at that copy, or reinstall.) On the default stdio setup there's no service to restart — just restart your agent, which relaunches the server on its next call.

stdio config notes — allowlist fallback & DISPLAY

stdio is the default the Quick start sets up; the block it prints looks like:

"browden": {
  "command": "/path/to/browden/.venv/bin/python",
  "args": ["-m", "browden.mcp.server", "--allowlist", "/home/you/.browden/allowlist.yaml"],
  "env": { "DISPLAY": ":0" }
}

--allowlist is optional (it falls back to ~/.browden/allowlist.yaml then the repo sample). DISPLAY is only needed when launching headed Chrome from a non-graphical parent process. Restart the agent to register the server.

Development

Requirements: Python ≥ 3.11 and Google Chrome on the host. Runtime deps are mcp[cli], selenium (bundles Selenium Manager, so ChromeDriver auto- downloads), beautifulsoup4, and pyyaml.

uv sync --extra dev                     # locked install from uv.lock (or: python -m venv .venv && pip install -e ".[dev]")
uv run pytest test/unit/                # never launches a browser
uv run pytest test/e2e/                 # drives a real Chrome
BROWDEN_HEADLESS=1 uv run pytest test/e2e/   # on a machine with no display

Dependency versions are pinned in the committed uv.lock; uv sync installs exactly that set. After changing dependencies in pyproject.toml — or bumping version — run uv lock and commit the updated lockfile (CI installs with --locked and fails on drift).

The e2e suite renders inline data: pages in a throwaway profile (no network, no allowlisted host) and includes a harness that stands the real MCP server up on an ephemeral port. The same suites run on every push/PR via the e2e workflow.

Future work

  • Non-Chromium browsers — extend the WebNavigatorBackend interface beyond Selenium/Chrome (e.g. Firefox) so the same guarded tool surface drives other engines.
  • Expose debugging APIs — surface read-only console, network, and performance signals (browser logs, request/response metadata) so an agent can inspect a page, not just read its DOM.
  • Expose more write actions — grow the gated write surface beyond click and insert_text (e.g. select/checkbox, file upload), each held to the same allowlist-and-label policy.

All feedback is welcome — please open an issue.

Attribution

The read allowlist's default top-sites list is the Tranco ranking — fetched at setup time, not redistributed in this repo:

V. Le Pochat, T. Van Goethem, S. Tajalizadehkhoob, M. Korczyński, W. Joosen. Tranco: A Research-Oriented Top Sites Ranking Hardened Against Manipulation. NDSS 2019. https://tranco-list.eu

Tranco aggregates several upstream rankings whose licenses govern the resulting data — notably Majestic (CC BY 3.0) and Chrome UX Report (CC BY-SA 4.0), which require attribution, and Cloudflare Radar (CC BY-NC 4.0), which is non-commercial. Because the default combined list may include the NC/SA sources, if you intend commercial use, generate a list restricted to permissively-licensed sources at https://tranco-list.eu/configure and pin its permanent list ID via fetch_tranco.py --url <permalink> — which also makes your allowlist reproducible. This is attribution guidance, not legal advice.

License

Apache License 2.0 — a permissive license with an explicit patent grant. You can use, modify, and redistribute browden, including in proprietary products, provided you preserve the license and attribution notices. This governs browden's code; the Tranco data carries its own terms (see Attribution).

from github.com/nishantsny/browden

Установка Browden

У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.

▸ github.com/nishantsny/browden

FAQ

Browden MCP бесплатный?

Да, Browden MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для Browden?

Нет, Browden работает без API-ключей и переменных окружения.

Browden — hosted или self-hosted?

Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.

Как установить Browden в Claude Desktop, Claude Code или Cursor?

Открой Browden на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Compare Browden with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории browse