~~~~~~~~~~~~~~~~~~~~
~~~ .---------. ~~~
~~~ | · clear | ~~~
~~~ | · focus | ~~~
~~~ '---------' ~~~
~~~~~~~~~~~~~~~~~~~~
barebrowse
Your agent browses like you do -- same browser, same logins, same cookies. Prunes pages down to what matters. 40-90% fewer tokens, zero wasted context.
barebrowse gives your AI agent a real browser. Navigate, read, interact, move on.
It uses the browser you already have -- your sessions, your cookies. Pages come back stripped to what matters -- 40-90% fewer tokens than raw output.
No Playwright. No bundled browser. No 200MB download. Two tiny dependencies (ws + Mozilla Readability).
npm install barebrowse
Requires Node.js >= 22 and any installed Chromium-based browser.
Ships with TypeScript types (generated from JSDoc) — autocomplete and type-checking work out of the box, no @types/barebrowse needed. The library is vanilla JS with no build step.
barebrowse open https://example.com # Start session + navigate
barebrowse snapshot # ARIA snapshot → .barebrowse/page-*.yml
barebrowse readable # Clean article text → .barebrowse/article-*.txt
barebrowse click 8 # Click element
barebrowse close # End sessionOutputs go to .barebrowse/ as files -- agents read them with their file tools, no token waste in tool responses. Output files older than 7 days are auto-removed at session start; saved state files (state-*.json) are kept.
Teach your agent the commands by installing the skill file (a markdown reference the agent reads as context). The CLI tool itself still needs npm install barebrowse -- the skill just teaches the agent how to use it.
Claude Code: Copy commands/barebrowse/SKILL.md to .claude/skills/barebrowse/SKILL.md (project) or run barebrowse install --skill (global).
Other agents: Copy commands/barebrowse.md to your agent's command/skill directory.
For writing your own skill files for other CLI tools: docs/product/skill-template.md.
Claude Code:
claude mcp add barebrowse -- npx barebrowse mcpClaude Desktop / Cursor:
npx barebrowse installOr manually add to your config (claude_desktop_config.json, .cursor/mcp.json):
{
"mcpServers": {
"barebrowse": {
"command": "npx",
"args": ["barebrowse", "mcp"]
}
}
}VS Code (.vscode/mcp.json):
{
"servers": {
"barebrowse": {
"command": "npx",
"args": ["barebrowse", "mcp"]
}
}
}MCP tools: browse, goto, snapshot, readable, click, type, press, scroll, hover, select, back, forward, reload, drag, upload, pdf, screenshot, wait_for, tabs, capabilities (reports the active engine — chromium or firefox — and what the session can do, plus a cookies field naming the cookies injected on the last goto — names only, never values; no side effects), switch_engine (swap chromium/firefox without restarting; page, tabs and refs reset, so call goto after). Plus assess (privacy scan) if wearehere is installed. Plus opt-in eval (BAREBROWSE_MCP_EVAL=1) — runs JS in the authenticated session, off by default because it can read cookies/localStorage. BAREBROWSE_TIER and BAREBROWSE_ALLOW restrict the tool set and the reachable hosts (see Site lock and MCP access tiers below). Session runs in hybrid mode with automatic cookie injection. Per-tool timeouts (goto/reload/wait_for 60s, back/forward 30s, interactive ops 15s, pdf/screenshot/upload 45s) with auto-retry on transient failures (idempotent only — mutating tools fail loudly to avoid double-submits).
Idle release. An MCP server can outlive its conversation, so it closes its browser after BAREBROWSE_IDLE_MINUTES (default 15, 0 = never, blank/invalid = default, fractional allowed, capped at ~24.8 days) with no tool call in flight. The next call opens a fresh browser (a new job, not a resume — a page left open, e.g. a solved captcha in the headed window, is gone). A transient browser error also closes the broken browser rather than orphaning it; an ordinary wait_for timeout does not reset the page.
Hard-kill safety (Linux). If the process that launched a browser is kill -9'd or crashes, the browser dies with it: Chromium via a --remote-debugging-pipe tether, Firefox via setpriv --pdeathsig (needs setpriv from util-linux; without it, or off Linux, a hard kill still orphans Firefox, as does a Snap Firefox). The dead launcher's leftover barebrowse-* temp dirs are cleaned at the next launch. Known limits: a reused pid keeps its dir, and dirs created under a different hostname are never removed.
browse and snapshot accept pruneMode: 'act'|'read'|'data'. act (default) keeps interactive elements — best for clicking/filling. read keeps paragraphs, headings, and long text — best for articles, docs, and content extraction. If act-mode collapses a content-heavy page near-totally, the snapshot includes a hint: … line suggesting pruneMode='read' so the agent doesn't bail to a separate HTTP fetch. data is one line per record — tables with headers, repeated items grouped; for data pages, not articles. Output starts with a one-line legend (# data mode: …): table rows read row "<label>": <column>: <value> · …, groups read <label>: a, b, c, and a title line followed by detail lines is one block, up to the next title line. It covers the main landmark only (nav, header and pagination outside <main> are dropped): use act for menus and page controls.
links: true (opt-in, snapshot({ links: true }), browse(url, { links: true }), or the connect({ links: true }) default; MCP/bareagent links param, CLI snapshot --links / browse <url> --links) adds each link's absolute URL right after its quoted name: - link "Fix login bug" <https://github.com/o/r/issues/42> [ref=12]. Works in every mode; a heading that wraps a link keeps the URL on its own line (heading "title" <url> [level=3] [ref=N]); data mode also puts it after link cells in table rows and each item of a collapsed group (Labels: bug <url> [ref=1], docs <url> [ref=2]) and its legend gains Links: "name" <url>.. javascript: and same-page #fragment links get no URL, every link line, row cell and group item carries its own URL (nothing is deduped across them, even when two links share a URL), and URLs never contain a raw space, < or >. Off by default: output is unchanged and costs nothing (about +23% chars on a GitHub issue list in data mode).
npx barebrowse doctor reports the runtime environment first — default engine/mode, installed Chromium/Firefox binaries, cookie sources, and any live session's engine + capabilities — then scans every known MCP config location and flags scope conflicts. npx barebrowse install --force overwrites an existing entry pointing at a different endpoint.
Import barebrowse in your agent code. One-shot reads, interactive sessions, full observe-think-act loops. Works with any LLM orchestration library. Ships with a ready-made adapter for bareagent (auto-snapshot after every action).
For code examples, API reference, and wiring instructions, see barebrowse.context.md -- the full integration guide.
| Mode | What happens | Best for |
|---|---|---|
| Headless (default) | Launches a fresh browser, no UI | Fast automation, scraping, reading pages |
| Headed | Auto-launches a visible browser window | Bot-detected sites, visual debugging, CAPTCHAs |
| Hybrid | Tries headless first, auto-launches headed if blocked | General-purpose agent browsing |
All three work on both engines (Chromium/CDP and Firefox/BiDi).
Start Chromium yourself with a debug port, then drive your real logged-in session:
chromium --remote-debugging-port=9222import { connect } from 'barebrowse';
const page = await connect({ port: 9222 });
await page.goto('https://your-logged-in-app.example.com');
const snap = await page.snapshot();
await page.close(); // closes only the tab barebrowse opened — your browser keeps runninggoto() resolves to { url, status } — the final URL after redirects and the
main document's HTTP status (0 when no HTTP response was observed, e.g. an aborted navigation).
page.lastNavigation returns the same object (null before any goto, and reset
to null by goBack/goForward/reload/switchTab — status is reported for
goto() only, so those never leave a stale warning). A
non-2xx page still loads, and its snapshot (and browse() output) carries a
[HTTP 404 — the page may be an error page] line. If the browser cannot
reach a document at all (DNS failure, connection refused, TLS error) goto()
and browse() reject with an Error whose code is 'NAV_FAILED' and reason
the browser's raw string (net::ERR_NAME_NOT_RESOLVED, NS_ERROR_UNKNOWN_HOST).
A download or a 204 is not a failure. Through MCP and the CLI, goto prints
ok for 2xx and ok (HTTP 404) otherwise.
const { url, status } = await page.goto('https://example.com/gone');CDP is deprecated in Firefox, so barebrowse drives it over the W3C WebDriver
BiDi protocol — a second transport over the same ws dependency, no extra
download. Same page.* API (the ARIA snapshot is reconstructed in-page since
BiDi has no getFullAXTree):
const page = await connect({ engine: 'firefox' });CLI: barebrowse open <url> --engine firefox. MCP: BAREBROWSE_ENGINE=firefox.
Firefox cookies (plaintext) reuse into the same engine.
Firefox is at practical parity with Chromium: stealth, consent auto-dismiss,
ad/tracker blocking, JS dialogs, console/network capture, hybrid fallback,
saveState, waitForNavigation, and download tracking all work the same way.
Chromium (CDP) stays the default; the only remaining gap is
reload({ignoreCache}) (upstream BiDi limitation). With no explicit engine,
connect() falls back to Firefox (warning once to stderr) if no Chromium
browser is installed, and throws naming both if neither is; an explicit
engine is always honored and errors on a missing binary instead of silently
switching.
Pass incognito: true for a clean, unauthenticated session — barebrowse
skips all auth injection (cookies + storage state), so the agent browses
logged out. It is not Chrome's --incognito flag: the session already runs in
a throwaway profile, so this gates the other auth source — your real browser
cookies. Works on both engines:
const page = await connect({ incognito: true });From the CLI: barebrowse open <url> --incognito. From MCP: set
BAREBROWSE_INCOGNITO=1.
Cookie consent walls (29 languages, with real mouse click fallback for stubborn CMPs), login walls (cookie extraction from your browsers), bot detection (challenge-phrase + node-count heuristic + stealth patches + automatic headed fallback — snapshot shows [BOT CHALLENGE DETECTED] warning when blocked), permission prompts, SPA navigation, JS dialogs, off-screen elements, pre-filled inputs, ARIA noise, and profile locking. The agent doesn't think about any of it.
barebrowse hands an autonomous — and therefore prompt-injectable — agent an authenticated browser, so the defaults are calibrated for that threat:
-
Local-resource schemes blocked.
file:,view-source:,chrome:, etc. are rejected by default (a confirmed local-file-read vector);http/https/datastay allowed. Override withallowLocalUrls: true. -
Cookie injection scoped to a precise RFC-6265 domain match — browsing one site can't pull look-alike or unrelated cookies into the session.
-
CLI daemon authenticated with a per-session token (loopback alone isn't an authorization boundary); snapshots and saved state are written owner-only (
0600). -
Opt-in hardening for stricter deployments:
blockPrivateNetwork(SSRF guard for loopback/RFC-1918/cloud-metadata) anduploadDir(confineupload()to one directory). Both available on the library, MCP, bareagent, and CLI (--block-private-network,--upload-dir). -
Site lock (
allowHosts): confine top-level navigation to a list of hosts. See below.
See barebrowse.context.md and the PRD's "Security Model & Safe Defaults" for the full rationale.
allowHosts: ['example.com', '*.docs.example.org'] on connect() and browse() (CLI --allow-hosts=a.com,*.b.org on open and browse; MCP BAREBROWSE_ALLOW=a.com,*.b.org) locks the top frame to those hosts, on both engines. An entry is host (exactly that host) or *.host (its subdomains, not host itself); a scheme, port or path in an entry is an error. A goto() to another host, or one that redirects there, rejects with err.code === 'NAV_BLOCKED' (err.host) and the page stays where it was. A clicked link, a JS jump or a window.open popup to another host never loads; the next snapshot() carries [blocked navigation to <host>]. New tabs are locked too. connect({ port, allowHosts }) (attach mode) throws: the lock is browser-wide and would reach your own tabs.
Not locked, by design: subresources (images, scripts, CSS, XHR/fetch), iframes and WebSocket. The lock stops an agent being steered to another site, not a page from loading its own assets. The MCP eval tool (tier rwx) can still fetch() anywhere.
MCP only: BAREBROWSE_TIER=r|rw|rwx (default rwx) hides tools above the tier. r reads (browse, goto, back, forward, reload, scroll, snapshot, readable, screenshot, pdf, wait_for, tabs, capabilities, assess); rw adds click, type, press, select, drag, hover, switch_engine; rwx adds upload and eval. A hidden tool answers Unknown tool. Every tool also carries MCP annotations (readOnlyHint, destructiveHint, openWorldHint, idempotentHint). An invalid BAREBROWSE_TIER or BAREBROWSE_ALLOW stops the server at start; it never falls back to rwx or to no lock. capabilities and the server instructions report the tier and the allowed hosts.
Raw ARIA output from a page is noisy -- decorative wrappers, hidden elements, structural junk. The pruning pipeline (ported from mcprune) strips it down to what matters.
| Page | Raw | Pruned | Reduction |
|---|---|---|---|
| example.com | 377 chars | 45 chars | 88% |
| Hacker News | 51,726 chars | 27,197 chars | 47% |
| Wikipedia (article) | 109,479 chars | 40,566 chars | 63% |
| DuckDuckGo | 42,254 chars | 5,407 chars | 87% |
Three snapshot modes: act (default) keeps interactive elements and visible labels -- for clicking, typing, navigating. read keeps all text content -- for reading articles and extracting information. data (opt-in) emits one line per record -- tables become row "Label": Header: value · ..., repeated items are grouped (Lines: Red, Green, Blue) -- for data pages (timetables, listings, results). [ref=N] stays on interactive nodes, so click/type work as in act. Every data snapshot starts with a one-line legend explaining the format. It is larger than act on prose pages; use read or readable() for articles.
Everything the agent can do through barebrowse:
| Action | What it does |
|---|---|
| Navigate | Load a URL, wait for page load, auto-dismiss consent |
| Back / Forward | Browser history navigation |
| Snapshot | Pruned ARIA tree with [ref=N] markers. Modes: act (buttons, links, inputs), read (full text), data (one line per record, for tables/listings). 40-90% token reduction. |
| Readable | Clean article text (title + body, chrome stripped — Reader-View engine). For reading article-like pages, not interacting. Advisory confidence; falls back to snapshot on non-articles. |
| Click | Scroll into view + mouse click at element center, JS fallback for hidden elements |
| Type | Focus + insert text, with option to clear existing content first |
| Press | Special keys: Enter, Tab, Escape, Backspace, Delete, arrows, Space |
| Scroll | Mouse wheel up or down (accepts direction or pixels) |
| Hover | Move mouse to element center (triggers tooltips, hover states) |
| Select | Set dropdown value (native select or custom dropdown) |
| Drag | Drag one element to another (Kanban boards, sliders) |
| Upload | Set files on a file input element |
| Screenshot | Page capture as base64 PNG/JPEG/WebP |
| Export page as PDF | |
| Assess | Privacy scan: score (0-100), risk level, 10-category breakdown. Tries headless first, falls back to headed if bot-blocked. Consent auto-dismissed before scan. Max 3 concurrent, 30s timeout, tabs cleaned up. Requires npm install wearehere. |
| Tabs | List open tabs, switch between them |
| Wait for content | Poll for text or CSS selector to appear on page |
| Wait for navigation | SPA-aware: works for full page loads and pushState |
| Wait for network idle | Resolve when no pending requests for 500ms |
| Dialog handling | Auto-dismiss JS alert/confirm/prompt dialogs |
| Save state | Export cookies + localStorage to JSON |
| Inject cookies | Extract from Firefox/Chromium and inject into the session (CDP or BiDi) |
| Raw CDP / BiDi | Escape hatch: page.cdp (Chromium) or page.bidi (Firefox) for any low-level command |
16+ sites across 8 countries, all consent dialogs dismissed, all interactions working:
Google, YouTube, BBC, Wikipedia, GitHub, DuckDuckGo, Hacker News, Amazon DE, The Guardian, Spiegel, Le Monde, El Pais, Corriere, NOS, Bild, Nu.nl, Booking, NYT, Stack Overflow, CNN, Reddit
URL -> find/launch browser (chromium.js)
-> WebSocket CDP connection (cdp.js)
-> stealth patches before page scripts (stealth.js, headless only)
-> suppress all permission prompts (Browser.setPermission)
-> extract + inject cookies from your browser (auth.js)
-> navigate to URL, wait for load
-> detect + dismiss cookie consent dialogs (consent.js)
-> get full ARIA accessibility tree (aria.js)
-> pruning pipeline from mcprune (prune.js)
-> dispatch real input events: click/type/scroll (interact.js)
-> agent-ready snapshot with [ref=N] markers
Firefox follows the same flow over WebDriver BiDi (bidi.js), with the AX tree
reconstructed in-page (ax-snapshot.js) and pruning/formatting shared verbatim.
A small set of focused modules, two small dependencies (ws, @mozilla/readability).
- Node.js >= 22 (built-in WebSocket, built-in SQLite)
- Any Chromium-based browser installed (Chrome, Chromium, Brave, Edge, Vivaldi) — or Firefox (>= 121) for the
engine: 'firefox'BiDi path - Linux tested (Fedora/KDE). macOS/Windows cookie paths exist but untested.
Local-first, composable agent infrastructure. Same API patterns throughout — mix and match, each module works standalone.
Core — the brain, the gate, the memory.
- bareagent — the think→act→observe loop. Goal in → coordinated actions out. Replaces LangChain, CrewAI, AutoGen.
- bareguard — the single gate every action passes through. Action in → allow / deny / ask-a-human out. Replaces hand-rolled allowlists and scattered policy code.
- litectx — tree-sitter code + memory graph with activation decay, plus lightweight context engineering (write · select · compress · isolate). Query in → ranked context out.
Optional reach — give the agent hands.
- barebrowse — a real browser for agents. URL in → pruned snapshot out. Replaces Playwright, Selenium, Puppeteer.
- baremobile — Android + iOS device control. Screen in → pruned snapshot out. Replaces Appium, Espresso, XCUITest.
- beeperbox — 50+ messaging networks via one MCP server (headless Beeper Desktop in Docker). Chat in → unified message stream out. Replaces Twilio, per-platform bot APIs.
What you can build:
- Headless automation — scrape sites, fill forms, extract data, monitor pages on a schedule
- QA & testing — automated test suites for web and Android apps without heavyweight frameworks
- Personal AI assistants — chatbots that browse the web or control your phone on your behalf
- Remote device control — manage Android devices over WiFi, including on-device via Termux
- Agentic workflows — multi-step tasks where an AI plans, browses, and acts across web and mobile
Why this exists: Most automation stacks ship 200MB of opinions before you write a line of code. These don't. Install, import, go.
Apache-2.0 — see LICENSE.