worbrow
Free agent web-search MCP server driving local headless Chrome/Firefox via self-built CDP/Marionette — no API key, no playwright deps, data stays on-machine, stable JSON contract.
README
worbrow
Agent search CLI: drives local headless browsers (Chrome/Edge via hand-written CDP, Firefox via hand-written Marionette) to search general web search engines, emitting a stable JSON contract for AI agents to invoke as a subprocess.
Architecture & decisions: docs/design.md; feature roadmap: docs/roadmap.md. 简体中文版见 README.zh-CN.md.
Quick Start
Prerequisites: Chrome/Edge (>= 109) or Firefox (>= 55) installed on the system.
cargo run -- list # list available engines
cargo run -- doctor # environment self-check (browser binaries/engines/backend status)
cargo run -- "rust async runtime" --json # default engine bing→duckduckgo→baidu fallback, default timeout 60s
cargo run -- "rust" --engine duckduckgo --timeout 30 --max-results 5
cargo run -- "rust" --pages 2 --max-results 15 --lang zh-hans --region zh-CN # multi-page aggregation + language/region
cargo run -- "rust" --freshness week --safesearch strict # freshness filter + safe search
cargo run -- "rust" --site doc.rust-lang.org --filetype pdf # site/file-type filter
cargo run -- "rust" --engine bing,duckduckgo # engine fallback chain (auto-tries the next on captcha/low-quality/low-yield)
cargo run -- "rust" --retry 2 # backoff retry on transient network errors (exponential backoff capped at 8s)
# body fetch + structured extraction (ADR-009): pass an explicit URL, get cleaned text and optional fields
cargo run -- fetch https://example.com/rust --json
cargo run -- fetch https://example.com --extract price,rating --json # field extraction (allowlist)
cargo run -- fetch https://example.com --no-text --extract price # fields only, saves tokens
Backend status: firefox (Marionette, hand-written protocol) and chrome (CDP, hand-written protocol) are both implemented; fake is for tests/smoke. Protocol implementation: ADR-002.
Installation
Prebuilt binaries are attached to every GitHub Release (v* tags): worbrow-linux-x86_64.tar.gz, worbrow-linux-x86_64-musl.tar.gz (statically linked, runs on any Linux), worbrow-macos-arm64.tar.gz, worbrow-windows-x86_64.zip, each with a SHA256SUMS checksum. Debian/Ubuntu users can also install the release .deb (includes MCP support, worbrow mcp):
sudo apt install ./worbrow_*.deb
Or build from source (cargo install worbrow, or make deb for a local .deb). Runtime soft dependency on Firefox (Recommends: firefox | firefox-esr).
MCP (Model Context Protocol)
cargo build --release
Run worbrow mcp as an MCP stdio server, exposing tools to MCP clients:
web_search(query/engine/browser/max_results/timeout/lang/region/pages/freshness/safesearch/site/filetype/retry/no_cache/compact)fetch_page(url/browser/timeout/max_chars/extract/text/wait_selector/retry: fetches an explicitly passed URL, returns cleaned body text and optional structured fields)list_engines(list available engines),doctor(environment self-check: browser binaries/versions/engine registry)
Tool results reuse the output contract (schema v1). With compact=true, web_search results contain only rank/title/url (saves agent context tokens, meta complete).
Design: ADR-005 and ADR-009.
(If MCP is not needed: cargo build --no-default-features)
worbrow mcp --idle-timeout <secs>: exits automatically after this long without any request (prevents orphan processes after an agent crashes; 0 = disabled, the default).
Session pooling (MCP long-running): browser processes are reused inside the MCP process, removing the 2-5s spawn overhead per search.
--max-sessions <n> concurrency cap (default 1 = serial reuse, excess queued), --session-ttl <sec> idle-session reclamation threshold (default 60s); idle sessions past TTL are recycled, crashed sessions auto-rebuilt, transparent to agents (schema v1 unchanged). Design: ADR-007.
Network retry & result cache (ADR-008): the retry parameter (default 0) applies exponential backoff to transient network errors (capped at 8s, counted in the timeout budget); meta.retries records the actual retry count. Inside the long-running MCP process, repeated calls with identical parameters within a 60s TTL hit the cache directly (meta.cached=true, elapsed_ms=0); no_cache bypasses it. The CLI --retry <n> supports retry as well (no cache).
Body fetch & structured extraction (ADR-009)
fetch_page / worbrow fetch <url> fetches an explicitly passed URL and returns cleaned body text, optionally extracting structured fields via the extract allowlist (title/author/published_at/price/currency/rating/rating_max/reviews_count; extraction priority JSON-LD → meta → DOM; missing fields are omitted, never fabricated):
worbrow fetch https://example.com --json --extract price,rating
- Closing the loop: pass
results[i].urlfromweb_searchexplicitly tofetch_page— "search links → read content → compare fields" in one step; never auto-follows search results (fetches explicit URLs only) - Parameters:
--max-chars <n>(body truncation, default 20000, flagged bymeta.truncated),--no-text(extracted fields only, saves tokens),--extract a,b(allowlist, invalid values exit 2),--wait-selector <css>(SPA: wait for this selector before extracting text, best-effort) - Known behavior: navigation success always yields a success payload (body may be empty);
meta.http_statusreports the page HTTP status (best-effort viaPerformanceNavigationTiming.responseStatus;nullon Firefox < 105 / data: URLs), so 4xx/5xx/404 no longer go unnoticed;meta.final_urlrecords the redirect landing page; SPA/lazy-loaded content may be missing — pass--wait-selector <css>to wait for the content to render - Safety boundary:
http/httpsonly (missing scheme defaults tohttps://); navigates with a real browser, page JS runs inside the browser (equivalent to clicking the link yourself); can reach localhost/intranet (equivalent to your local browser — do not feed untrusted input if you want to avoid being induced to fetch intranet content); no bulk fetching; rate discipline still applies - Compliance: fetch is an explicit full-page fetch by the user, a separate path from search engines' snippet-only crawling policy
Agent Integration
worbrow offers two agent integration paths: MCP (recommended; long-running process + tool semantics) and CLI subprocess (zero-dependency, one-shot). Both share the same core and output contract (schema v1).
Claude Code
Register in claude_desktop_config.json (or .claude.json under mcpServers):
{
"mcpServers": {
"worbrow": {
"command": "worbrow",
"args": ["mcp", "--idle-timeout", "300"]
}
}
}
Tip:
--idle-timeout 300makes the long-running process exit 5 minutes after the agent session goes idle, avoiding orphans. Ensureworbrowis on PATH (automatically satisfied aftermake deb).
Cursor / generic MCP clients
Project-level .mcp.json (Cursor) or an equivalent global client config:
{
"mcpServers": {
"worbrow": {
"command": "worbrow",
"args": ["mcp", "--idle-timeout", "300"]
}
}
}
Exposed tools: web_search (parameters include engine/browser/max_results/timeout/lang/region/pages/freshness/safesearch/site/filetype).
CLI subprocess (without an MCP client)
worbrow "rust async" --engine bing --max-results 8 --timeout 60 --json
- Read stdout JSON (validate
schema_version), logs on stderr - On non-zero exit, stdout still carries the error JSON payload (code/message/detail)
- Each result carries
domain/https, no need to parse URLs yourself to judge the source
Output contract (agent side)
- stdout only emits JSON (
--json), all logs go to stderr - Semantic exit codes:
0success /2argument error /3environment error /4search failure /124timeout /1internal error - Versioned schema: top-level
schema_versionfield; fields only grow, never change - No interaction; hard timeout defaults to 60s
Example success payload:
{
"schema_version": 1,
"query": "rust",
"results": [{ "rank": 1, "title": "…", "url": "https://…", "snippet": "…",
"domain": "example.com", "https": true,
"published_at": "2025年5月25日", "is_ad": false,
"url_resolved": true, "result_kind": "web" }],
"meta": { "engine": "bing", "started_at": "…", "elapsed_ms": 1200,
"result_count": 3, "pages": 1, "low_yield": false,
"captcha": false, "engine_error": null,
"engine_tried": ["bing"], "cached": false, "retries": 0 }
}
Use as a library
worbrow's public library surface is a type-level top-level API (ADR-006): consumers assemble everything with one use worbrow::..., no need to know the internal module tree.
use worbrow::{BrowserKind, Config, search};
fn main() -> Result<(), worbrow::Error> {
let outcome = search(Config::new("rust async", "bing", BrowserKind::Firefox)
.with_max_results(5))?;
for r in &outcome.results {
println!("{} - {}", r.rank, r.title);
}
Ok(())
}
Body fetch (ADR-009) is a first-class library API too:
use worbrow::{BrowserKind, ExtractField, FetchConfig, fetch};
fn main() -> Result<(), worbrow::Error> {
let page = fetch(FetchConfig::new("https://example.com/rust", BrowserKind::Firefox)
.with_extract(vec![ExtractField::Title, ExtractField::PublishedAt]))?;
println!("{}: {} chars", page.url, page.chars);
println!("title: {:?}", page.extracted.get("title"));
Ok(())
}
Pick the entry point by whether you are already inside a tokio runtime, to avoid nested-runtime panics:
-
search(sync): use when there is no runtime context (main/CLI/scripts/spawn_blockingclosures); builds its own runtime internally, do not call in async contexts -
run(async): use when a runtime already exists (MCP handlers /#[tokio::main]/#[tokio::test]), awaiting reuses the external runtime; to block-synchronously wait inside async, usetokio::task::block_in_place(|| handle.block_on(run(cfg)))(needs a multi-thread runtime) -
Dependency surface: library consumers can use
default-features = falseto drop the MCP dependency (rmcp) — themcpfeature is enabled by default only to serve the CLI binary -
Extension: implement a custom engine via
SearchProviderand inject it withConfig::with_provider; implement a custom browser backend viaBrowserDriverand inject it withConfig::with_driver -
Contract serialization:
SuccessPayload/ErrorPayload(includingschema_version) can beserde_json::to_string'd directly; runnable examples live inexamples/(cargo run --example basic_search)
Quality commands
No just; the unified entry point is make:
make check # fmt + clippy(-D warnings, cognitive complexity <= 10) + test
make test # cargo test (mcp enabled by default, CI needs no browser)
make deny # cargo-deny license/vulnerability check
make machete # unused-dependency check
make doctor # run worbrow doctor
Search tips
- Avoid stacking nouns separated by spaces in Chinese queries: Bing may anchor
the whole query to the first strong entity (e.g. a fund-data query returns China
wiki/baike pages) — results are plentiful and well-formed but completely
off-topic. The relevance gate falls back to DuckDuckGo/Baidu automatically, but
it is more reliable to cut noisy terms; use double quotes for exact phrases
(e.g.
"天天基金网 净值查询") - Engine choice:
--engine duckduckgo(most reliable for Chinese queries in practice),--engine baidu(Chinese long-tail; reachable from CN networks with no CAPTCHA wall),--engine bing(English queries); the default chainbing,duckduckgo,baidutries the next engine when the quality gates fail - Query quality: avoid piling up high-noise words such as
best/learn(Bing tends to misclassify them as dictionary intent; see docs/roadmap-result-quality.md); rephrase with synonyms, quotes, orsite:constraints when results are poor
Layout
src/
main.rs # thin entry + CLI parsing (clap, bin-private)
lib.rs # public library surface: top-level re-exports (Config/BrowserKind/..., ADR-006)
app.rs domain.rs error.rs ports.rs output.rs extract.rs
drivers/ # resolve · jsonrpc(shared framework) · cdp · marionette · fake
engines/ # resolve/AVAILABLE · duckduckgo · bing · baidu
tests/ # integration tests + fixtures (offline HTML golden)
Dependency direction: cli → app → domain/ports ← adapters(drivers/engines), reverse is forbidden.
License
MIT OR Apache-2.0 (see LICENSE-MIT; Apache-2.0 text at https://www.apache.org/licenses/LICENSE-2.0).
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。