Give your AI agent eyes.
~9 MB binary. HTTP API + MCP server. 10x cheaper than screenshots.
Whole navigations in one call, steered by a tiny decision model.
πͺΌ See the web through your agent's eyes
Browser automation tools are designed for humans, not agents. Playwright, Puppeteer and Selenium return massive DOM dumps or screenshots that burn through tokens. Most agent-specific solutions are locked into their own ecosystems.
Seebro takes a different approach: compact text and element refs over HTTP and MCP. One small Go binary controls Chrome (or the lightweight obscura engine). Any agent can drive it with plain HTTP calls, and Claude (or any other MCP client) can use it as a native toolset. For plain navigation the agent doesn't even have to steer step by step: /goal hands "get me to the pricing page" to a small decision model and returns when it's there.
# Interactive elements on screen β ~650 tokens for a Wikipedia article
curl "localhost:9867/snapshot?filter=interactive&format=compact&viewport=true"
# Act on them by ref
curl -X POST localhost:9867/action -d '{"kind":"click","ref":"e12"}'
# Or hand over a whole navigation: one call, several clicks
curl -X POST localhost:9867/goal -d '{"task":"Find the pricing page","startUrl":"https://vercel.com"}'
# Or let Claude drive it directly
claude mcp add seebro -- seebro mcp| Area | What you get |
|---|---|
| Reading | Page text as clean text or markdown (headings, lists, links, tables) with paging; interactive-element snapshots with stable refs, a viewport-only mode and diffs; full accessibility tree; element inspection (attributes, box, HTML, computed styles) |
| iframes | Snapshots, text and actions reach into iframes, including cross-origin ones (payment forms, embedded logins, editors) |
| Interaction | Click, double click, click at coordinates, type (fast insertText), fill (React/Vue-safe), whole forms in one call, keys and combos (Enter, Control+A), hover, select, scroll, drag & drop (HTML5 + mouse), file upload, human-like mouse and typing |
| Navigation | Load URLs, back / forward / reload, waitUntil load / network idle, wait for text / element / URL (reacts instantly to DOM changes), multiple tabs |
| Goal navigation | POST /goal / navigate_goal: a whole multi-step navigation in one call. The Jev decision model (TypeSafe, OpenRouter or Vercel AI Gateway) picks each click, search or dropdown choice; an offline matcher works without a key and takes over when a model call fails. Stop gates, no-effect recovery, bot-wall detection and a step trace with confidences and cost |
| Engines | Chrome (default) or obscura, a Rust headless browser using ~20x less memory; both together in one server, chosen per tab |
| DevTools | Network log with headers, POST data and response bodies, WebSocket and Server-Sent Events, console and uncaught exceptions, JS dialogs, HAR export, performance metrics and Web Vitals |
| Emulation | Mobile viewports and touch, dark mode, reduced motion, geolocation, network and CPU throttling, user agent, extra headers, timezone, locale |
| Output | Screenshots (JPEG/PNG/WebP; viewport, full page or element; auto-scaled for LLMs), screen recording to MP4 in real time with every click and input highlighted, PDF export, downloads (CSV/PDF exports land in a known folder) |
| Scripting | JavaScript in the page with awaited promises: fetch('/api/β¦') runs with the page's own login |
| Sessions | Persistent Chrome profile (logins survive restarts), cookies, isolated incognito-like contexts for parallel agents or second accounts |
| Multi-agent | One shared browser for all MCP clients, tab locks that are actually enforced, per-session identity |
| Robustness | Hung or crashed tabs are detected and can be reset; idle tabs are closed automatically; JS dialogs never block a call; Cloudflare challenge and block pages are recognised instead of clicked around on |
| Stealth | Passes common bot checks: no webdriver flag, no "HeadlessChrome" in user agent or client hints, real plugins, native-looking overrides, live self-check |
| Security | Loopback by default, web pages can't reach the API (CSRF/DNS-rebinding guard), token enforced beyond loopback |
| Agent integration | HTTP API, MCP server (stdio + Streamable HTTP, 46 tools), Claude skill |
| Seebro | Typical Browser Tools | |
|---|---|---|
| Token cost | ~650 (on-screen elements) / ~800 (text) | 10,000+ (full DOM/screenshot) |
| Interface | HTTP + MCP β works with any agent | Framework-specific |
| Output format | Compact refs, text, markdown | Raw HTML or pixels |
| Multi-step navigation | One /goal call, a small model decides each step (~$0.0002β0.0006 per step) |
One large-model round trip per click |
| Engines | Chrome or obscura (32 MB idle), per tab | One browser |
| Bot detection | Built-in, self-checking stealth | Manual configuration |
| Session handling | Persistent profile + isolated contexts | Usually ephemeral |
| Deployment | Single ~9 MB binary | Complex dependencies |
Measured on the same machine against the previous release (Chrome 155, headless):
| Operation | Before | Now |
|---|---|---|
| Interactive snapshot, Wikipedia article (27k a11y nodes) | 1,248 ms, 10 MB transferred from Chrome | 77 ms |
β¦same, only elements on screen (viewport=true) |
β (83 KB β 20k tokens) | 73 ms, 2.6 KB β 650 tokens |
| Interactive snapshot, GitHub repo page | 251 ms | 18 ms |
Navigate to a page without <title> |
2,394 ms | 108 ms |
| Type 300 characters | 495 ms | 7 ms |
| Full-page screenshot, Wikipedia (16,384 px) | timeout, tab hung for good | 673 ms |
| Text of Hacker News | lines glued together | correct line breaks |
Seebro can drive two browsers with the same API, MCP tools and refs:
| Chrome (default) | obscura | |
|---|---|---|
| What it is | Google Chrome / Chromium | Rust headless browser (V8, own DOM/layout), Apache-2.0 |
| Install | Chrome on the machine | scripts/install-obscura.sh (no Chrome needed) |
| Memory, idle | 654 MB | 32 MB |
| Memory, 8 parallel shop flows | 1,869 MB | 251 MB |
| Small pages, 4 workers | 136 ops/s | 163 ops/s |
| Shop click-through, 1 / 4 / 8 workers | 155 / 345 / 425 flows/min | 153 / 334 / 328 flows/min |
| Large page (25k elements), 4 workers | 38.5 ops/s | 9.9 ops/s |
| Mixed load, 8 workers | 92.8 ops/s | 33.8 ops/s |
| Real sites (navigate) | GitHub 2.4 s, Wikipedia 1.1 s | GitHub 14.8 s, Wikipedia 2.9 s |
| iframes, JS dialogs, scrolling, WebSocket capture, metrics, contexts | yes | no |
Measured with scripts/loadtest.py on a MacBook Pro (Intel Core i9-9980HK, 8 cores / 16 threads), local test site, 30β60 s per run, 0 errors in every run.
Rule of thumb: obscura for many parallel, simple-to-medium pages where memory matters (scraping, reading, standard forms); Chrome for large or script-heavy pages, iframes, dialogs and anything visual. All obscura pages share one JavaScript thread, so throughput stops growing beyond ~4 parallel tabs; BRIDGE_OBSCURA_WORKERS adds processes but helps only a little (8-worker mixed load: 33.8 β 41.8 ops/s).
BRIDGE_ENGINE=obscura seebro # obscura only, no Chrome
curl -X POST localhost:9867/tab -d '{"action":"new","url":"https://example.com","engine":"obscura"}' # hybridMCP: tab_new {url, engine: "obscura"}. health lists the available engines.
Normally an agent drives Seebro one call at a time: snapshot β think β click β wait β snapshot. Every hop costs a full round trip through a large model, and every intermediate page passes through its context. POST /goal (MCP: navigate_goal) hands the whole navigation to a small decision model instead and returns when the goal is reached.
Set up (once): give Seebro one Jev key. Without a key /goal still works with the offline matcher.
AI_GATEWAY_API_KEY=vck_β¦ seebro # Vercel AI Gateway (needs paid gateway credits)
# or TYPESAFE_API_KEY=β¦ / OPENROUTER_API_KEY=sk-or-β¦Use:
curl -X POST localhost:9867/goal -H "Authorization: Bearer $TOKEN" -d '{
"task": "Go to the source code repository of the Go programming language on GitHub",
"startUrl": "https://go.dev/"
}'{
"status": "done",
"decider": "auto(vercel:typesafe-ai/jev, fallback local)",
"final_url": "https://github.com/golang/go",
"elapsed_ms": 8042,
"steps": [
{"step": 1, "executed_action": "click_e128", "description": "link \"GitHub\" -> https://github.com/golang",
"top_probability": 0.98, "goal_done": 0.04, "stuck": 0.62, "decided_by": "vercel:typesafe-ai/jev",
"outcome": "navigated to https://github.com/golang"},
{"step": 2, "executed_action": "click_e160", "description": "link \"go\" -> /golang/go",
"top_probability": 0.98, "goal_done": 0.46, "outcome": "navigated to https://github.com/golang/go"},
{"step": 3, "proposed_action": "done", "top_probability": 0.73, "goal_done": 0.91, "outcome": "agent declared done"}
],
"usage": {"decider_calls": 3, "input_tokens": 23949, "output_tokens": 3565, "est_cost_usd": 0.001006}
}From Claude: navigate_goal {tabId, task, startUrl}, then get_text on the page it ends on.
Each step:
- Seebro's snapshot engine lists the interactive elements; they become named options (
click_e73,search_e4,type_e9/submit_e9,select_e12, plusscroll_down,scroll_up,back,done). Noise (edit links, citation markers,javascript:) and duplicate targets are dropped; when a page has more than 240 options, those that match the task survive the cap first. - One decider call answers three independent questions: which action? (a probability per option), is the goal reached?, are we stuck?
- Stop gates run before acting (goal > 0.85, stuck > 0.85 after step 2,
done). An action that had no visible effect and is proposed again is replaced by the next most probable one. - Seebro executes the action, waits for the navigation or page update, and records the outcome in the history the decider sees next. The page it observes afterwards is reused for the next step, so a step costs one snapshot.
Search boxes get the query (default: the task's keywords) as real key presses and Enter; a search field hidden in a collapsed header is filled and its form submitted instead. Dropdowns get a second question, "which option?". Ordinary text fields are only filled from inputs, never with a guess. Don't put passwords in inputs; do logins yourself (or once in headed mode, the profile keeps the session).
The run stops on Cloudflare challenge or block pages (status: "blocked" with evidence and guidance) instead of clicking around on them. Other end states: goal_achieved or done (arrived), stuck, max_steps, max_seconds, error.
| Field | Meaning |
|---|---|
task |
What to achieve (required) |
startUrl |
Navigate here first; otherwise start from the tab's current page |
decider |
auto (default), jev or local, see Deciders |
maxSteps, maxSeconds |
Budgets (default 24 steps, 180 s) |
query |
Text for search boxes (default: the task's keywords) |
inputs |
{"email": "a@b.c"}: text for fields whose label contains the key. Without an entry nothing is typed into ordinary fields. |
noTyping |
Only click, never type |
record, recordPath |
Record the run as an MP4 |
Everything runs inside the Seebro binary: its own Chrome, its own snapshot engine, and plain HTTPS calls to the model. No Playwright, Node or jev-browser is needed.
decider |
Behaviour |
|---|---|
auto (default) |
Jev when a key is configured, otherwise the offline matcher. If a Jev call fails after its retries (network, rate limit, credits used up, malformed answer), the matcher decides that step and the run continues. |
jev |
Jev only; a failed call ends the run with status: "error" and the provider's message |
local |
Offline keyword matcher: follows links named after the task's words in order and uses search boxes. Free and instant, fine for "go to X, then Y" and searches, no judgment |
Each step's decided_by and fallback_reason show who answered and why.
Jev is TypeSafe's decision model: it answers typed questions (pick one option, is this statement true?) with probabilities instead of generating text. Through the Vercel AI Gateway a step took 0.5β1 s and cost $0.0002β0.0006 (the billed cost is reported in usage). Set one key in Seebro's environment:
| Variable | |
|---|---|
TYPESAFE_API_KEY |
TypeSafe API (TYPESAFE_BASE_URL to override the endpoint) |
OPENROUTER_API_KEY |
OpenRouter decisions API (sk-or-β¦) |
AI_GATEWAY_API_KEY |
Vercel AI Gateway (vck_β¦, model typesafe-ai/jev; needs paid gateway credits, free tier is refused) |
JEV_PROVIDER, JEV_MODEL |
Force a provider (typesafe, openrouter, vercel); pick a model snapshot (default jev-latest) |
Measured (Wikipedia, same decisions for every contestant, median of 3, same machine and Chrome):
| Task | jev-browser (Playwright) | Seebro /goal |
Seebro, step by step |
|---|---|---|---|
| Coffee β Espresso | 2,436 ms | 2,155 ms | 2,288 ms |
| Search "Ristretto" | 1,939 ms | 1,640 ms | 1,610 ms |
| Coffee β Espresso β Ristretto | 4,215 ms | 3,184 ms | 2,652 ms |
With the real Jev model (Vercel AI Gateway) Seebro solved all of these open tasks without help, each in one call: Espresso β inventor's article (2.6 s, 2 steps), Hacker News "newest" (3.4 s), Vercel pricing page (3.0 s, $0.0004 billed), go.dev β github.com/golang/go (8.6 s, 3 steps), python.org β "Using Python on macOS" docs (9.0 s, 7 steps), Wikipedia search for Mount Everest (2 steps).
The browser side of /goal is 1.1β1.3Γ faster than jev-browser. The real gain is in the agent: a three-hop navigation is one tool call instead of about nine, and none of the intermediate pages pass through the large model's context.
- Good fit: getting to a page ("pricing", "newest submissions", "the docs page about X"), following a known path, searching a site, the same navigation across many sites (monitoring, scraping) where a large model per click would be slow and expensive.
- Not a fit: reading and judging content, filling forms with real data, logins, purchases or messages. Let the agent do those itself with the normal tools;
/goalis for getting there.
The loop design, the questions and the bot-wall detector are ported from jev-browser (MIT, Β© Joey Kudish).
Release builds (-ldflags "-s -w", as produced by goreleaser):
| Platform | Size |
|---|---|
| macOS arm64 | 9.3 MB |
| macOS amd64 | 9.8 MB |
| Linux arm64 | 9.0 MB |
| Linux amd64 | 9.6 MB |
| Windows amd64 | 9.9 MB |
At runtime the Seebro process uses about 15 MB of RAM; Chrome runs as separate processes. Network, console and WebSocket logs are fixed-size per-tab ring buffers (300 / 200 / 200 entries); response bodies stay in Chrome (capped at 5 MB per resource, 25 MB per tab) and are fetched only when asked for. Chrome itself starts with background services disabled (~9% less memory than a default Chrome) and idle tabs are closed after 30 minutes in headless mode. Goal navigation, the Jev client and the recorder add no dependencies; recording needs ffmpeg on the machine, obscura is a separate optional binary.
docker run -d -p 9867:9867 -e BRIDGE_TOKEN=your-secret --security-opt seccomp=unconfined seebro/seebro
curl -H "Authorization: Bearer your-secret" http://localhost:9867/healthgo build -ldflags "-s -w" -o seebro .
BRIDGE_HEADLESS=true ./seebroWith OpenClaw or Claude, ask your agent to set up Seebro. The skill file has everything it needs.
Seebro has a built-in Model Context Protocol server with 46 tools, in the same binary. No extra install, runtime or dependency.
Claude Code (stdio, recommended):
claude mcp add --scope user seebro -- /path/to/seebro mcpClaude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"seebro": { "command": "/path/to/seebro", "args": ["mcp"] }
}
}Any client over HTTP (Streamable HTTP, needs a running server):
claude mcp add --transport http seebro http://localhost:9867/mcp \
--header "Authorization: Bearer $BRIDGE_TOKEN"ββββββββββββββββ stdio ββββββββββββββββ HTTP POST /mcp ββββββββββββββββ CDP ββββββββββ
β Claude Code β ββββββββΊ β seebro mcp β ββββββββββββββββΊ β Seebro serverβ βββββΊ β Chrome β
β Claude Desk. β JSON-RPC β (thin proxy) β β (HTTP API) β β β
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ ββββββββββ
seebro mcpis a thin proxy mode of the same binary that forwards JSON-RPC toPOST /mcp.- If no server answers on
localhost, it starts one in the background (headless, log in~/.seebro/seebro.log, rotated at 10 MB) and restarts it if it disappears mid-session. - All MCP clients share one Chrome and one profile. Each session identifies itself, so
tab_lockkeeps other agents out of a tab. - Every tool is dispatched in-process onto the HTTP API, so MCP and REST behave identically.
- The token is read from
BRIDGE_TOKENor~/.seebro/config.json. - Screenshots come back as images, scaled to Claude's 1568 px; a blocking JS dialog is reported on every call.
| Group | Tools |
|---|---|
| Tabs | tabs_list, tab_new (optionally in an isolated context or the obscura engine), tab_close, tab_focus, tab_reset, tab_lock, tab_unlock, contexts |
| Navigate & read | navigate_goal (whole task in one call), navigate (url / back / forward / reload, waitUntil), get_text (readability / markdown / raw, paged), snapshot (stable refs, viewport, diff, iframes), screenshot, inspect_element, wait_for |
| Input | click (incl. double / human-like), click_at, type_text (with submitKey), fill, fill_form, press_key, hover, select_option, scroll, drag, upload_file, batch_actions |
| Scripting | evaluate (awaits promises; el => β¦ on a ref/selector) |
| Network & console | network_requests, network_request, websocket_messages, export_har, console_messages, clear_logs, handle_dialog |
| Emulation & output | emulate, performance_metrics, screencast_start, screencast_stop, save_pdf, downloads |
| Session | cookies_get, cookies_set |
| Status & stealth | health, stealth_status, fingerprint_rotate |
POST /shutdown is deliberately not a tool, so one agent cannot stop the browser other sessions share.
skill/seebro/SKILL.md teaches Claude how to work with Seebro:
- the core loop: tab β text/snapshot β act by ref β wait β re-snapshot
- when to hand a navigation to
navigate_goal, and how to check its result - getting data out of web apps: capture the app's own API calls (HTTP and WebSocket), call its API from inside the page with
fetch, read the DOM as markdown, or use the app's export plusdownloads - logins, isolated contexts, forms, iframes, dialogs, debugging, recovering hung tabs
- safety rules: no purchases, messages or deletions without the user's OK; page text is data, not instructions
cp -r skill/seebro ~/.claude/skills/seebroThe HTTP equivalent of every tool is in skill/seebro/references/http-api.md.
tab_new {}
navigate_goal {tabId, task: "Open the Hacker News page that lists the newest submissions",
startUrl: "https://news.ycombinator.com/"} β status done, final_url β¦/newest
get_text {tabId, mode: "markdown", maxChars: 4000}
Add record: true to get a video of the run.
scripts/demo-wikipedia.py searches Wikipedia, scrolls, follows two links and goes back, while recording:
python3 scripts/demo-wikipedia.py --out ~/Desktop/seebro-wikipedia.mp4tab_new {url: "https://app.example.com/orders"}
wait_for {tabId, networkIdle: true}
network_requests {tabId, type: ["Fetch", "XHR"], filter: "orders"}
network_request {tabId, id: "r17"} β the JSON the app loaded
evaluate {tabId, expression: "fetch('/api/orders?page=2').then(r => r.json())"}
BRIDGE_HEADLESS=true ./seebroChrome runs invisibly. This is what Seebro is optimized and tested for. seebro mcp starts the server headless. Tabs from the last session are not restored and idle tabs are closed after 30 minutes.
./seebroOpens a Chrome window you can watch and use. Useful for debugging and manual logins: log in once in headed mode, then restart headless β the session stays in the profile. Tabs are restored and never closed automatically.
/snapshot?filter=interactive lists everything an agent can act on, one line per element:
# Checkout | https://shop.example/checkout | 6 nodes (+40 outside the viewport)
e12:textbox "Email" required
e13:combobox "Country" val="Germany" opts=[Germany|Austria|Switzerland]
e14:checkbox "Save address" checked
e15:button "Continue"
# frame f1 https://js.payments.example/elements
f1e3:textbox "Card number"
f1e4:textbox "Expiry"
- The list is computed inside the page by a small engine running in an isolated JavaScript world. The page can't see it, and only matching elements leave the browser. That makes it ~15x faster than reading Chrome's accessibility tree on large pages.
- Refs are stable.
e12keeps pointing at the same element across snapshots for as long as it exists. A ref from a previous page fails with a clear "stale" error and never hits a different element. f1e3lives in iframef1. Cross-origin iframes are included.viewport=truekeeps only what's on screen;diff=truereturns only what changed;links=trueadds link URLs.filter=allreturns Chrome's full accessibility tree (refsa0,a1β¦, main frame only).
Measured on a Wikipedia article:
| Method | Tokens |
|---|---|
Full accessibility tree (format=compact) |
~100,000 |
Interactive elements (?filter=interactive&format=compact) |
~20,000 |
Interactive elements on screen (&viewport=true) |
~650 |
Text, first page (/text?maxChars=30000) |
~7,500 |
| Screenshot of the viewport (1366Γ681) | ~1,250 |
Log in once, stay logged in. Seebro maintains a Chrome profile at ~/.seebro/chrome-profile/ that keeps cookies and storage across restarts. Isolated contexts ("context": "name" when opening a tab) live only in memory.
Every tab records its HTTP requests, WebSocket frames, Server-Sent Events and console output from the moment Seebro attaches. Lists default to "since the last navigation" (all=true includes earlier entries still in the buffer).
All endpoints accept tabId. Without it, the last tab used is taken. GET endpoints take query parameters, POST endpoints a JSON body. Errors are {"error": "β¦"}.
| Endpoint | Purpose |
|---|---|
GET /health |
Connection status |
GET /tabs |
Open tabs with lock owner, open dialog, health and context |
GET /snapshot |
Elements with refs (see options below) |
GET /text |
Page text: mode=readability|markdown|raw, maxChars, offset (returns nextOffset) |
GET /screenshot |
Image: format=jpeg|png|webp, quality, fullPage, ref/selector, maxDim, maxHeight, raw=true, output=file&path= |
GET /element |
One element: attributes, text, value, box, visibility, html=true, styles=true|all |
GET /cookies |
Cookies of the current page (url, name) |
GET /stealth/status |
Live bot-detection self-check with score |
| Endpoint | Purpose |
|---|---|
POST /navigate |
url, or type: back|forward|reload; waitUntil: domcontentloaded|load|networkidle; newTab (+ context) |
POST /action |
Single action (kinds below) |
POST /actions |
Several actions (stopOnError) |
POST /goal |
Whole navigation in one call: task, startUrl, decider (auto/jev/local), maxSteps, maxSeconds, query, inputs, noTyping, record; returns status, final URL, step trace, usage (see Goal Navigation) |
POST /evaluate |
JavaScript; promises awaited; with ref/selector runs el => β¦ |
POST /wait |
text (one or many), selector (or gone), url, networkIdle, timeout; 408 on timeout |
POST /tab |
new (+ url, context, engine: chrome|obscura), close, focus, reset |
POST /tab/lock, /tab/unlock |
Reserve a tab; others get 423 until it is released or expires |
POST /cookies |
Set cookies |
POST /fingerprint/rotate |
New user agent, platform, screen, CPU cores |
POST /shutdown |
Graceful shutdown |
Action kinds for /action and /actions:
| Kind | Does |
|---|---|
click, dblclick |
Click / double-click by ref or selector |
clickAt |
Click at x/y (count for multi-click) |
type |
Focus and type text (fast insert, last key as a real key press) |
fill |
Set value of input, textarea, select, checkbox/radio or contenteditable with input/change events |
press |
Key names and combos: Enter, Tab, Escape, ArrowDown, Control+A, Meta+Shift+K |
focus, hover, select, scroll |
As named (scrollX/scrollY for page scroll) |
drag |
From ref/selector to toRef/toSelector or x/y |
upload |
Set files on an <input type=file> |
humanClick, humanType |
Human-like mouse path / typing rhythm |
| Endpoint | Purpose |
|---|---|
GET /network |
Requests since the last navigation; filter by type, filter (URL), method, status (2xx, 4xx, failed, 404); headers=true, limit, offset, all |
GET /network/request |
One request by id: headers, POST data, body (maxBytes, saveTo, base64) |
GET /network/websocket |
WebSocket frames and SSE events (filter, dir=sent|received|sse, limit) |
GET /network/har |
HAR 1.2 export (bodies=true, path=β¦har) |
POST /network/clear, POST /console/clear |
Reset a tab's logs |
GET /console |
Console messages, exceptions, browser log (level, limit, all) |
GET /dialog, POST /dialog |
Open JS dialog; accept / dismiss (promptText) |
POST /emulate |
viewport (390x844x3,mobile,touch), userAgent, colorScheme, reducedMotion, geolocation, network (Slow 3G β¦ Offline), cpuThrottling, extraHeaders, timezone, locale |
GET /performance |
TTFB, FCP, LCP, CLS, load, plus JS heap, DOM nodes, layout counts |
GET /pdf |
Print to PDF (path, landscape, printBackground) |
POST /screencast/start, POST /screencast/stop |
Record the tab as a real-time MP4 (needs ffmpeg); clicks and typing are highlighted while recording (path, maxWidth, maxHeight, quality) |
GET /downloads |
Downloads and files in the download folder |
GET /contexts, POST /context |
List isolated contexts; close one (and its tabs) |
POST /mcp |
MCP endpoint (Streamable HTTP, JSON-RPC) |
| Parameter | Effect |
|---|---|
filter=interactive |
Interactive elements from the in-page engine (recommended) |
viewport=true |
Only elements currently on screen |
links=true |
Add link URLs |
diff=true |
Only elements added, changed or removed since the last snapshot |
selector=CSS |
Only inside this element |
maxNodes=N |
Stop after N elements (default 5000) |
maxTokens=N |
Truncate to a token budget |
format=compact|text|yaml |
Output format (JSON by default) |
filter omitted / engine=ax |
Chrome's accessibility tree (depth=N to limit) |
output=file, path= |
Save to disk |
Environment variables (a config file at ~/.seebro/config.json is also supported, see seebro config init):
| Variable | Default | Purpose |
|---|---|---|
BRIDGE_PORT |
9867 |
Server port |
BRIDGE_BIND |
127.0.0.1 |
Listen address. Anything but loopback requires a token (generated if unset) |
BRIDGE_TOKEN |
β | Bearer token |
BRIDGE_ALLOWED_ORIGINS |
β | Web origins allowed to call the API from a browser (comma-separated) |
BRIDGE_ALLOWED_HOSTS |
β | Extra Host names accepted on loopback (reverse proxies) |
BRIDGE_HEADLESS |
false |
Run without a window (seebro mcp autostart: true) |
BRIDGE_PROFILE |
~/.seebro/chrome-profile |
Browser profile |
BRIDGE_STATE_DIR |
~/.seebro |
Logs, screenshots, PDFs, saved state |
BRIDGE_STEALTH |
light |
light or full (adds fingerprint noise) |
BRIDGE_SITE_ISOLATION |
false |
true keeps Chrome's site isolation (cross-origin iframes become unreachable) |
BRIDGE_LEAN |
true |
false keeps Chrome's background services |
BRIDGE_TAB_IDLE |
30m headless, off headed |
Close tabs unused for this long (0 = never) |
BRIDGE_RESTORE |
false headless, true headed |
Reopen the last session's tabs |
BRIDGE_DIALOG |
β | accept / dismiss answers JS dialogs automatically |
BRIDGE_DOWNLOAD_DIR |
~/.seebro/downloads |
Download folder |
BRIDGE_NETWORK_BUFFER |
300 |
Requests kept per tab |
BRIDGE_CONSOLE_BUFFER |
200 |
Console messages kept per tab |
BRIDGE_BLOCK_IMAGES / BRIDGE_BLOCK_MEDIA |
false |
Skip images / all media |
BRIDGE_NO_ANIMATIONS |
false |
Freeze CSS animations |
BRIDGE_TIMEZONE |
β | Timezone for all tabs |
BRIDGE_LOG_LEVEL |
info |
debug also logs every successful request |
BRIDGE_ENGINE |
chrome |
chrome, obscura (no Chrome) or auto (Chrome if installed) |
OBSCURA_BINARY |
β | Path to obscura (default: next to seebro, ~/.seebro/bin, PATH) |
BRIDGE_OBSCURA_WORKERS |
1 |
obscura worker processes |
BRIDGE_OBSCURA_STEALTH |
true |
obscura's own fingerprint + TLS impersonation |
BRIDGE_OBSCURA_PRIVATE_NETWORK |
true |
Let obscura open localhost/LAN URLs like Chrome does |
TYPESAFE_API_KEY / OPENROUTER_API_KEY / AI_GATEWAY_API_KEY |
β | Jev key for /goal (any one) |
JEV_PROVIDER, JEV_MODEL |
auto, jev-latest |
Force typesafe, openrouter or vercel; model snapshot |
TYPESAFE_BASE_URL, AI_GATEWAY_EVALUATION_URL, OPENROUTER_DECISIONS_URL |
provider defaults | Override a Jev endpoint (proxies, tests) |
FFMPEG_BINARY |
ffmpeg |
ffmpeg used to encode screen recordings |
CDP_URL |
β | Use an already running Chrome |
CHROME_BINARY, CHROME_FLAGS |
β | Chrome path, extra flags |
SEEBRO_URL |
http://127.0.0.1:$BRIDGE_PORT |
Server used by seebro mcp |
SEEBRO_NO_AUTOSTART |
β | true stops seebro mcp from launching a server |
SEEBRO_OWNER |
mcp-<pid> |
Identity of an MCP session for tab locks |
More in docs/ARCHITECTURE.md.
| Command | Purpose |
|---|---|
seebro |
Start the server (HTTP API + /mcp) |
seebro mcp |
MCP over stdio (proxies to the server, starts it if needed) |
seebro config init / seebro config show |
Create / show the config file |
seebro --version |
Print version |
Automation signals are removed at the browser level, where they leave no JavaScript traces:
navigator.webdriverisfalse, as in a normal Chrome (--disable-blink-features=AutomationControlled)- user agent and client hints say
Chrome, notHeadlessChrome - real plugins, mime types and
window.chrome(new headless mode) - the injected script lives in a closure (no globals), and every override keeps native descriptors and
toString()
light (default) changes nothing else. full adds seeded canvas, WebGL and font noise, WebRTC relay and hardware values that stay stable within a session. GET /stealth/status runs the same checks bot detectors use and scores them. On bot.sannysoft.com every check passes.
βββββββββββββββ HTTP / MCP ββββββββββββββββββββββββ CDP βββββββββββββββββββββ
β Agent β ββ JSON ββββΊ β Seebro β ββββββββΊ β Chrome β
β (any kind) β req/res β (Go) β ββββββββΊ β obscura (optional)β
βββββββββββββββ β snapshot engine β βββββββββββββββββββββ
β recorder, stealth β HTTPS βββββββββββββββββββββ
β /goal loop ββββββββββΌβββββββββΊ β Jev decision modelβ
ββββββββββββββββββββββββ β (optional) β
βββββββββββββββββββββ
Seebro is a translation layer. Agents speak HTTP or MCP, browsers speak CDP. Seebro adds an in-page snapshot engine, records network and console activity, handles stealth, sessions and tab health, compresses everything to save tokens, and can run whole navigations itself with a decision model. Details: docs/ARCHITECTURE.md.
Seebro gives agents real browser access with your real accounts. Anyone who can call it can act as you: read messages, make purchases, read cookies and auth headers from the network log.
What Seebro does for you:
- listens on 127.0.0.1 only, unless you set
BRIDGE_BIND; then a token is mandatory and generated if missing - rejects requests from web pages (any
Originor cross-siteSec-Fetch-Sitenot explicitly allowed) and foreignHostheaders, so a site you visit can't drive Seebro through your browser (CSRF, DNS rebinding) - answers CORS only for origins you allow β never
* - refuses to write output files into hidden files or folders (
~/.ssh, dotfiles) outside its state directory - enforces tab locks between agents
What you should do:
- set
BRIDGE_TOKENif other users or containers share the machine - treat
~/.seebro/as sensitive β it contains your browser profile - start with test accounts until you trust your agent
Think of it as handing someone an unlocked laptop. Powerful when intentional. Dangerous when careless.
| Package | Purpose | License |
|---|---|---|
| chromedp | CDP driver | MIT |
| cdproto | CDP types | MIT |
| gobwas/ws | WebSocket | MIT |
Everything else, including the MCP server and the Jev client, is Go standard library. The goal-navigation loop and bot-wall detector are ported from jev-browser (MIT); see THIRD_PARTY_LICENSES.md.
- Go 1.25+ (or download a release binary)
- Chrome or Chromium (or obscura alone:
scripts/install-obscura.sh) - Optional:
ffmpegfor screen recordings (not in the Docker image), a Jev key for model-steered/goal
git clone https://github.com/ChrisFeldmeier/seebro.git
cd seebro
go build -o seebro .
go test ./... # ~200 unit tests, no Chrome or API key needed
go test -tags integration ./... # with a real headless Chrome
# load test (see the script header for scenarios)
python3 scripts/loadtest.py --write-site /tmp/seebro-site
python3 -m http.server 8765 --bind 127.0.0.1 --directory /tmp/seebro-site &
python3 scripts/loadtest.py --engine obscura --scenario flow --workers 4 --duration 60
# recorded demo: Wikipedia search, scrolling, links, back
python3 scripts/demo-wikipedia.py --out /tmp/seebro-wikipedia.mp4| Script | Purpose |
|---|---|
scripts/loadtest.py |
Load test for both engines (small, large, click-through, mixed, real sites) |
scripts/demo-wikipedia.py |
Automated tour that produces a video |
scripts/install-obscura.sh |
Install the obscura engine into ~/.seebro/bin |
scripts/install-autostart.sh |
Run Seebro at login (launchd / systemd) |
scripts/check.sh |
The CI checks, locally |

Chris Feldmeier Β· Creator
Seebro works natively with OpenClaw, the open-source AI assistant. Use it as your agent's browser backend for efficient, low-cost web automation.
