Sandbox
@alejandroqh/browser39

Local browser MCP server for agent web access

browser39 gives agents a single local binary for web browsing, JavaScript execution, form filling, and session persistence. It exposes the browser through MCP, CLI commands, JSONL files, and a Rust library, so builders can plug it into different agents and tools.

86 stars11 forksRustUpdated 3mo ago
Who it's for

Builders who want their agent to fetch real web pages, keep login state, and return only the part they need.

What it delivers

You can give an agent reliable web access without opening a full browser stack or re-reading whole pages.

What it does

Token-optimized page fetches

Returns content sections first, then lets the agent re-fetch a targeted selector instead of sending the whole page.

JavaScript in a full DOM

Runs JavaScript with DOM traversal, events, storage, timers, and form interaction through V8.

Persistent sessions

Saves cookies, localStorage, and history to an encrypted session file so logins survive restarts.

MCP and CLI transports

Works through stdio MCP, HTTP MCP, CLI fetch/search commands, and JSONL watch or batch files.

Config and auth management

Lets agents manage search settings, cookies, storage, headers, and auth profiles while masking secrets in output.

Rust library and examples

Provides `BrowserService` and related types for embedding browser39 directly in Rust, plus Python, TypeScript, and Rust examples.

How to get it

  1. 1Run
    npm install @aquintanar/browser39
  2. 2Or via Cargo
    cargo install browser39
  3. 3Installs the binary and auto-configures it for every MCP client detected: Claude Code,…
    curl -fsSL https://raw.githubusercontent.com/alejandroqh/marketplace/main/h39.sh | bash
  4. 4Run
    browser39 fetch https://example.com
  5. 5Run
    # Example Domain
    This domain is for use in documentation examples without needing permission.
    
    [Learn more](https://iana.org/domains/example)
  6. 6Search the web using the configured search engine
    browser39 search "rust async traits"

README

browser39

browser39

A headless browser for AI agents that fetches modern web pages, runs JavaScript, manages sessions, and returns token-efficient Markdown.

  • Handles modern sites. Executes JavaScript, fills forms, queries the DOM, persists cookies and sessions across runs.
  • LLM-usable output. Compact Markdown with content preselection, so the agent reads the section it needs, not the whole page.
  • Local-only. No data sent to third-party services.
  • Single binary. No Chrome, no Puppeteer. ~52MB. macOS, Linux, Windows.

Comparison

browser39Playwright / PuppeteerRaw HTTP (requests, ureq)
External browserNone (single binary)Requires Chrome/ChromiumNone
Binary size~52MB~280MB with browserN/A (library)
PlatformsmacOS, Linux, WindowsmacOS, Linux, WindowsAny
JavaScriptYes (V8 via deno_core)Yes (full V8)No
HTML to MarkdownBuilt-in, token-optimizedNo (raw HTML or screenshots)DIY
Token preselectionContent sections, agent picks what to readNoNo
Cookies & sessionsAutomatic, persisted, encryptedManualManual
DOM queriesCSS selectors + full JS DOM APIFull DOM APINo
Formsfill + submitFull interactionManual POST
Auth & secretsProfiles, redaction, opaque handlesManualManual
TransportsMCP (stdio + HTTP), JSONL, CLILibrary APILibrary API

Token savings in practice

Real test: extracting the "Optical communications" section from Artemis II on Wikipedia (full page: ~14,600 tokens).

Raw HTTPWebFetch (Claude Code built-in)Mistral Web Searchbrowser39
How it worksFetch full page, truncate to ~1,000 tokensSend full page (~14,600 tokens) to intermediate model with extraction promptCloud API: search + page processing by Mistral modelFetch → content selectors list → targeted section fetch
Tokens consumed~1,000 (truncated)~14,600 (processed by intermediate model)Cloud processed, not disclosed196
Found the section?No. Section is at token ~6,320, truncated awayYes, but returns a lossy summaryDepends on search rankingYes. Exact original content
Content qualityNav menus, infobox, article introParaphrased, no links, no referencesSummary with citationsLossless markdown with links and citations
Session stateNoneNoneNoneCookies, history, follow-up queries free
Data processingLocalProcessed remotelyProcessed remotelyLocal
Cost per callFreeBundled$30 / 1,000 callsFree
Retries neededPagination to find itNone, but no control over outputMay not find specific sectionNone. Agent sees structure first

browser39 returns the exact section in 196 tokens at zero cost. The raw approach misses it entirely, WebFetch burns 75x more tokens through an intermediate model, and cloud tools like Mistral's charge $0.03 per call.

Install

npm install @aquintanar/browser39

Or via Cargo:

cargo install browser39

Install for any AI CLI / IDE

Installs the binary and auto-configures it for every MCP client detected: Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw.

curl -fsSL https://raw.githubusercontent.com/alejandroqh/marketplace/main/h39.sh | bash

Pre-built binaries available on the releases page.

Quick Start

MCP config

Add to your MCP client config:

{
  "mcpServers": {
    "browser39": {
      "command": "browser39",
      "args": ["mcp"]
    }
  }
}

29 tools available instantly: browser39_fetch, browser39_click, browser39_links, browser39_dom_query, browser39_fill, browser39_submit, browser39_search, cookies, storage, history, config management, and more.

CLI: one-shot fetch

browser39 fetch https://example.com
# Example Domain
This domain is for use in documentation examples without needing permission.

[Learn more](https://iana.org/domains/example)

CLI: one-shot search

Search the web using the configured search engine:

browser39 search "rust async traits"

Add --output json for structured results suitable for piping into other tools.

CLI: agent integration (watch mode)

Long-running subprocess that any language can talk to via JSONL files:

touch commands.jsonl
browser39 watch commands.jsonl --output results.jsonl
# From your agent (Python, Node, Rust, shell, anything):
echo '{"id":"1","action":"fetch","v":1,"seq":1,"url":"https://example.com"}' >> commands.jsonl

Drop-in web_search and visit_website tool examples: Python | TypeScript | Rust

See docs/install-cli.md for the full integration guide.

Rust library

Embed browser39 directly in a Rust application — no subprocess, no IPC.

[dependencies]
browser39 = "1.8"
tokio = { version = "1", features = ["full"] }
anyhow = "1"
use std::collections::HashMap;

use browser39::{BrowserService, Config, HttpMethod, InMemoryStore, SessionStore};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let store: Box<dyn SessionStore> = Box::new(InMemoryStore);
    let mut service = BrowserService::new(Config::default(), store).await?;

    let options = service.default_fetch_options();
    let page = service
        .fetch(
            "https://example.com",
            &HttpMethod::Get,
            &HashMap::new(),
            None,
            None,
            &options,
        )
        .await?;

    println!("{}", page.markdown);
    Ok(())
}

The crate root re-exports the common types: BrowserService, Config, PersistenceMode, FetchOptions, HttpMethod, SessionStore, InMemoryStore, create_session_store. Lower-level modules (browser39::core, browser39::cli, browser39::mcp) are also public if you need direct access to the HTML→Markdown engine, JSONL runner, or MCP server.

Features

Token optimization

browser39 minimizes token usage when feeding web content to LLMs:

  • Content preselection: on first fetch, returns available content sections with token estimates instead of dumping the full page. The agent picks the relevant section and re-fetches with a targeted selector.
  • Heading auto-expand: selector: "#Astronauts" returns the full section until the next same-level heading, not just the heading text.
  • HTML to Markdown: strips scripts, styles, and non-content elements.
  • Compact link references (JSON mode): [text][N] instead of inline URLs, with full URLs in the links array.
  • Same-origin URL shortening: links on the same domain show path-only.
  • Link deduplication: same-URL links (image + headline cards) emitted once.

JavaScript execution

V8 (via deno_core) runs JavaScript against a full DOM environment:

  • Traversal: parentElement, children, firstChild, lastChild, nextSibling, previousSibling, closest(), matches(), contains()
  • Lookup: getElementById, getElementsByClassName, getElementsByTagName, getElementsByName
  • Mutation: createElement, createTextNode, appendChild, removeChild, insertBefore, setAttribute, removeAttribute, textContent/innerHTML setters
  • Events: addEventListener, removeEventListener, dispatchEvent, new Event/CustomEvent/MouseEvent/KeyboardEvent/InputEvent
  • Web APIs: localStorage, document.cookie, console.log (captured), setTimeout, atob/btoa, getComputedStyle, MutationObserver
  • Forms: element.value get/set, element.click(), form.submit()
{"action": "dom_query", "script": "document.querySelectorAll('a').length"}
{"action": "dom_query", "script": "document.getElementById('content').closest('section').textContent"}
{"action": "dom_query", "script": "document.querySelector('h1').setAttribute('class', 'modified')"}

Session persistence

Cookies, localStorage, and browsing history are persisted to disk by default (~/.local/share/browser39/session.enc, AES-256-GCM encrypted). An agent can log in once and stay authenticated across restarts.

Disable with --no-persist or config:

[session]
persistence = "memory"

Forms

Fill fields by CSS selector and submit. browser39 handles enctype, builds the HTTP request, and returns the response page:

{"action": "fill", "fields": [{"selector": "#user", "value": "agent"}, {"selector": "#pass", "value": "secret", "sensitive": true}]}
{"action": "submit", "selector": "form#login"}

Security

Auth profiles keep credentials out of the LLM conversation. The agent references a profile name and never sees the token:

[auth.github]
header = "Authorization"
value_env = "GITHUB_TOKEN"
value_prefix = "Bearer "
domains = ["api.github.com"]
{"action": "fetch", "url": "https://api.github.com/repos", "auth_profile": "github"}

Config management via MCP

Agents can manage browser39's configuration directly through MCP tools: change the search engine, store credentials, manage auth profiles, cookies, storage, and headers. Sensitive values are stored securely on disk but never returned via MCP; config_show masks them with ••••••.

> browser39_config_set key="search.engine" value="https://www.google.com/search?q={}"
Set search.engine = https://www.google.com/search?q={}

> browser39_config_auth_set name="github" header="Authorization" value="Bearer ghp_..." domains=["api.github.com"]
Auth profile 'github' saved

> browser39_config_show section="auth"
{"auth": {"github": {"header": "Authorization", "value": "••••••", ...}}}

10 config tools: config_show, config_set, config_auth_set/delete, config_cookie_set/delete, config_storage_set/delete, config_header_set/delete.

All transports

TransportCommandUse case
MCP (stdio)browser39 mcpLocal MCP clients
MCP (HTTP)browser39 mcp --transport sse --port 8039Remote agents, cloud deployments
JSONL watchbrowser39 watch commands.jsonlAny language, long-running agent IPC
JSONL batchbrowser39 batch commands.jsonlOne-shot scripted operations
CLI fetchbrowser39 fetch <url>Quick page retrieval, shell scripts
CLI searchbrowser39 search <query>Quick web search from shell scripts
Rust librarycargo add browser39Embed in Rust apps, no subprocess

Configuration

browser39 --config path/to/config.toml fetch https://example.com

Precedence: --config flag > BROWSER39_CONFIG env > ~/.config/browser39/config.toml

See docs/config.md for the full reference.

Documentation

DocDescription
install-cli.mdCLI integration guide with Rust, Python, TypeScript examples
jsonl-protocol.mdFull JSONL protocol specification
config.mdConfiguration reference

Development

cargo build              # Build
cargo run                # Run
cargo test               # Run all tests
cargo clippy             # Lint
cargo fmt                # Format

Contributors

@nathan-widjaja

@janpauldahlke

License

Apache-2.0

Files in the repo

Repository payload12 top-level entries
  • .claude-plugin
  • docs
  • examples
  • npm
  • openclaw-plugin
  • src
  • .gitignore
  • .mcp.json
  • Cargo.toml
  • CHANGELOG.md
  • LICENSE
  • README.md

Discussion (0)

Ask about usage, or say what you built with it

Sign in to join the discussion.

No comments yet. Be the first to say what this is good for.

More connectors

Real-time global intelligence dashboard. AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface

86k

High-performance code intelligence MCP server. Indexes codebases into a persistent knowledge graph — average repo in milliseconds. 158 languages, sub-ms queries, 99% fewer tokens. Single static binary, zero dependencies.

43k

Universal provider proxy for OpenAI Codex & Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code

14k
okf-memory/
okf-agent-memory

Git-native persistent memory for AI coding agents. Implements Google OKF v0.2 with sub-300µs in-memory BM25 search, embedded MCP server, and progressive disclosure. Slashes token bloat by 80% with zero external databases or dependencies. Built in pure Go.

547
tirth8205/
code-review-graph

Local-first code intelligence graph for MCP and CLI. Builds a persistent map of your codebase so AI coding tools read only what matters, with benchmarked context reductions on reviews and large-repo workflows.

31k
2akouwu/
reverify

Stop your AI from making things up — it proposes, deterministic tools decide, every claim checked against ground truth with evidence. Grounded facts and context survive resets. Reverse engineering is the proving ground. MCP server + CLI.

1.1k