agent-browser
agent-browser
一个为 AI 代理加固的本地浏览器。零依赖。 通过 DevTools 协议驱动你已有的 Chrome,提取 token 高效的 Markdown,并且不容易被识别为自动化。
表面 | 安装 | 用途 |
MCP 服务器 |
| Claude Code、Cursor、Codex 以及任何 MCP 客户端 |
CLI |
| shell、脚本、CI |
库 |
| 你自己的 Node 代码 |
DSH / Cordis 插件 | composition row | DSH 环境中的原生工具 |
npx -y @truenix/agent-browser markdown https://news.ycombinator.com一切都在本地运行。无需账户、无需 API 密钥、无需远程服务、无配额。
为什么
向代理提供原始 HTML 会浪费其大部分上下文。在真实页面上测量:
而一个自称是自动化的浏览器会被阻止、降级或提供不同的内容——这会悄悄破坏代理得出的任何结论。
重站点基准测试(实时,curl 对比 markdown)
站点 | 原始 HTML | markdown |
| 节省 |
| 927 kB | 120 kB | 55 kB | 7.7× / 16.8× |
| 273 kB | 11 kB | 8.7 kB | 23.8× / 31.4× |
| 323 kB | 10 kB | 7.3 kB | 32.0× / 44.2× |
| 648 kB | 69 kB | 13 kB | 9.3× / 48.9× |
| 360 kB | 4.6 kB | 3.5 kB | 78.6× / 102.8× |
| 35 kB | 10 kB | 3.2 kB | 3.4× / 11.0× |
使用 npm run charts 重新生成这两个图表和此表的基础测量数据——它实时测量,然后使用 agent-browser 本身绘制 PNG,因此图像中的数字不会与代码产生的结果出现偏差。
在同一个页面上花费更少的 token
在维基百科文章中,URL 占输出的三分之一,在 github.com/trending 上占五分之四。当代理是在阅读而不是导航时,可以去掉它们:
agent-browser markdown https://github.com/trending --links text # 69 kB -> 13 kB
agent-browser markdown https://en.wikipedia.org/wiki/Rust --links relative模式 | 渲染 | 使用时机 |
|
| 代理接下来会导航 |
|
| 同站爬取;保留目标,丢弃来源 |
|
| 阅读、总结、问答 |

每次 YC 检查的 token 成本(使用 --links text 阅读 HN + YC 博客,51.1k → 1.5k tokens,+500 输出)— 2026 年 8 月列表定价:Anthropic Haiku 4.5 $1/$5, Sonnet 5 $3/$15, Opus 5 $5/$25 • OpenAI GPT-4o $2.50/$10, GPT-4o mini $0.15/$0.60
加载后渲染的页面
导航等待 load 事件,由 CDP 生命周期事件驱动,而不是轮询循环和固定延迟。在真实页面上,对于字节相同的输出,速度快 2–3 倍:
页面 |
| 旧的固定 250 毫秒稳定 |
| 7 ms | 258 ms |
| 115 ms | 340 ms |
| 160 ms | 365 ms |
| 218 ms | 466 ms |
一个客户端渲染的应用,其内容在加载之后到达,需要真实的信号,而不是更大的猜测——旧的 250 毫秒稳定时间也会错过该内容:
agent-browser markdown https://some-spa.example --wait idle模式 | 等待 | 使用时机 |
| DOM 已解析 | 你只需要 HTML 中自带的标记 |
| load 事件 | 几乎总是 |
| 网络安静下来 | 结果看起来像空壳 |
Related MCP server: Browser-MCP Navigator
任何其他框架
如果你的框架支持 JSON Schema,它就可以驱动 agent-browser,而无需此项目知道该框架的存在。agent-browser tools 以你需要的任何形式打印目录:
agent-browser tools # MCP: {name, description, inputSchema}
agent-browser tools --format openai # OpenAI: {type:"function", function:{...}}
agent-browser tools --format anthropic # Anthropic: {name, description, input_schema}然后每个工具都可以以任何一种方式运行,无论你的框架支持哪种:
import { findTool, TOOLS } from '@truenix/agent-browser/tools';
import { withBrowser } from '@truenix/agent-browser';
// in-process: the harness can hold a CDP session
const tool = findTool('browser_markdown');
const text = await withBrowser({}, async (session) => {
await session.navigate('https://example.com');
return tool.run(session, { url: 'https://example.com', links: 'text' });
});
// out-of-process: the harness can only run a command (sandboxes, shells)
tool.cli({ url: 'https://example.com', links: 'text' });
// => ['markdown', 'https://example.com', '--links', 'text']安装
作为 MCP 服务器
claude mcp add browser -- npx -y @truenix/agent-browser mcp{
"mcpServers": {
"browser": {
"command": "npx",
"args": ["-y", "@truenix/agent-browser", "mcp"]
}
}
}工具:browser_markdown、browser_text、browser_html、browser_links、browser_screenshot、browser_evaluate、browser_accessibility_tree、browser_pdf、browser_probe。
每个表面都从同一个共享目录中暴露相同的九个工具,因此 CLI、MCP 服务器和 DSH 插件永远不会出现偏差。
作为库
npm install @truenix/agent-browserimport { withBrowser } from '@truenix/agent-browser';
const md = await withBrowser({}, async (session) => {
await session.navigate('https://example.com');
return session.markdown();
});作为 DSH / Cordis 插件
一行命令(推荐——当你确实使用 DSH 时自动接线):
npx -y @truenix/agent-browser install # adds the bundle to ~/.dsh/profiles/web/package.json, then pnpm install (the mount ships in the package's own cordis.patch.yml layer)
# npx -y @truenix/agent-browser install --profile web --dry-run # preview
# npx -y @truenix/agent-browser uninstall # remove again重启 dsh——所有九个 browser_* 工具都会作为原生工具出现。普通的 npm install 不会运行任何处理程序;需要有意进行 install。
手动(如果你更愿意自己编辑组合):
npm i -g @truenix/agent-browser
# or inside the harness checkout: pnpm add @truenix/agent-browser需要 Node ≥ 18 和 Chrome/Chromium 安装。然后添加到主机组合中(工具注册表位于主机上,而不是每个代理):
# ~/.dsh/profiles/web/cordis.patch.yml — persists for every web session
- insert:
- id: agent-browser
name: '@truenix/agent-browser/cordis'
config:
timeoutMs: 180000 # per-tool call budget; default respects AGENT_BROWSER_BIN / ENDPOINT
# cli: 'npx -y @truenix/agent-browser' # override only if needed简短形式(当组合已经包装了 insert 时):
- '@truenix/agent-browser/cordis':
timeoutMs: 180000环境变量覆盖:AGENT_BROWSER_BIN(Chrome 二进制文件)、AGENT_BROWSER_ENDPOINT(通过 --endpoint 附加到长期运行的浏览器)或 config.cli。
CLI
agent-browser <command> [options]
markdown <url> Extract the whole page as Markdown (main content by default)
text <url> Visible text only
html <url> Full serialized DOM after JavaScript runs
links <url> Every anchor as JSON
screenshot <url> PNG/JPEG (-o file, --full)
pdf <url> PDF (-o file)
a11y <url> Filtered accessibility tree
eval <url> <expr> Evaluate JS, return only its value (cheapest)
probe Browser, GPU and capability report
mcp Run as an MCP server on stdio
tools Print tool schemas (--format mcp|openai|anthropic)选项:--headful、--no-stealth、--block-images、--gpu/--no-gpu、--width、--height、--viewport WxH、--main、--raw、--links、--max-rows、--limit、--wait、--settle、--full、--endpoint <ws>、--timeout、--json、-o。
markdown 用于阅读,eval 用于查找内容
代理使用此工具时最昂贵的错误是渲染整个页面来回答一个一行的问题。通过 eval 在三个重页面上提出三个有针对性的问题只需 235 字节,而 Markdown 则需要 77 kB:
agent-browser eval https://github.com/trending \
"JSON.stringify(Array.from(document.querySelectorAll('article h2 a')).slice(0,3).map(a=>a.innerText.trim()))"
# ["openai / codex","mattpocock / skills","affaan-m / ECC"] -> 58 bytes使用 innerText,而不是 textContent。textContent 返回原始源空白("openai /\n\n codex");innerText 给出实际渲染的内容("openai / codex")。如果代理弄错了,就要付出整个重试往返的代价,这比它节省的字节要昂贵得多。
当你确实需要阅读、总结或搜索页面时,使用 markdown。
每次调用都会启动自己的浏览器(约 1.4 秒)。对于连续多次查找,保持一个浏览器存活并将 --endpoint 指向它——同样的三个问题在三次冷启动中需要 5.9 秒,而针对一个热浏览器只需 3.2 秒。
--endpoint 附加到已经运行的浏览器而不是启动一个——这对于在多次调用中重用单个长期运行的浏览器很有用。
内存
Chrome 的底线是加载任何内容之前大约 420 MB PSS,跨 18 个进程,而这个底线是 Chrome 的,不是这个包的——标志调整只能移动约 5%,而进一步移动它的标志(--enable-low-end-device-mode)在 24 核旁边报告 navigator.deviceMemory: 2,这是一台不可能的机器,正是那种会导致浏览器被标记的不一致。因此,杠杆是更少的浏览器,而不是更小的浏览器。
MCP 服务器保持一个浏览器,并为每个工具调用提供其自己的隔离上下文。并发获取三个重页面:
峰值 PSS | 进程数 | 墙钟时间 | |
每次调用一个浏览器 | 1289 MB | 44 | 2114 ms |
一个浏览器,3 个上下文 | 654 MB | 20 | 2049 ms |
隔离不变——每次调用的浏览器上下文始终是提供隔离的。浏览器在空闲 30 秒后关闭(AGENT_BROWSER_IDLE_MS,0 表示立即关闭),因此长期运行的服务器不会在对话之间占用 420 MB。在它热的时候重复调用会完全跳过启动,运行速度大约快两倍。
权衡:任务共享一个进程树,因此浏览器级别的崩溃会摧毁所有正在进行的任务,而不是一个调用。死浏览器会在下次调用时被检测并重新启动。如果你需要每个任务的爆炸半径隔离,请使用 withBrowser,它仍然为每个调用提供自己的浏览器。
import { withPooledSession, shutdownPool } from '@truenix/agent-browser/pool';
await withPooledSession({}, async (session) => {
await session.navigate('https://example.com');
return session.markdown();
});
await shutdownPool(); // or let it idle out对于 CLI,每次调用都是自己的进程,因此重用意味着将 --endpoint 指向你自己保持存活的浏览器。
守护进程:每个 CLI 调用一个浏览器
每个 CLI 调用都是自己的进程,因此默认情况下每个调用都会冷启动自己的 Chrome——十个并发调用意味着十个浏览器。--daemon 改为共享一个常驻浏览器,每个调用仍然获得自己的隔离上下文:
agent-browser markdown https://example.com --daemon
agent-browser daemon --status
agent-browser daemon --stop墙钟时间 | 峰值 Chrome 进程数 | |
3 次顺序查找,无守护进程 | 5330 ms | — |
3 次顺序查找, | 3254 ms | — |
10 次并发调用,无守护进程 | 1443 ms | 140 |
10 次并发调用, | 1144 ms | 32 |
它是可选的(--daemon 或 AGENT_BROWSER_DAEMON=1),因为启动一个比你的命令存活时间更长的后台进程是一个值得要求的副作用。它按需启动,并在空闲五分钟后退出(AGENT_BROWSER_DAEMON_IDLE_MS)。设置环境变量也会将 MCP 服务器路由到它,当多个 MCP 客户端共享一台机器时,这值得做。
--headful、--width、--height、--block-images 和 --gpu 在浏览器启动时是固定的,因此共享浏览器无法满足它们。传递其中任何一个都会胜出:该调用会悄悄获得自己的私有浏览器,并在 stderr 上说明。
它围绕四种故障模式构建,每种都经过测试验证:
恰好一个守护进程。 十个同时的首次调用者都会生成一个;九个在绑定套接字的竞争中失败,并在启动任何内容之前退出。绑定套接字就是锁,因此没有会过期的锁文件。
打开的套接字是引用计数。 客户端在工作时保持其连接,因此被
SIGKILL杀死的客户端仍然会释放——内核会关闭套接字。一条“请释放”的消息会永远泄漏一个引用。空闲退出,因此它不会在对话之间占用约 420 MB。
被
SIGKILL的守护进程不会留下任何东西:其配置文件标记命名了它的 pid,因此下面的清理会在下次启动时回收浏览器。
自我清理
每次启动都会在其临时配置文件中写入所有者标记,并清理所有者已死亡的配置文件,杀死仍然附加到它们的浏览器。结合 SIGINT/SIGTERM/SIGHUP 处理程序,可以得到:
调用如何结束 | 孤立进程 | 下次启动后 |
正常 | 0 | 0 |
SIGTERM / SIGINT | 0 | 0 |
SIGKILL(不可捕获) | 1 个浏览器 | 0 |
所以一次硬性终止最多遗留一个浏览器,而不是每次被中断的调用都遗留一个。十个并发 CLI 调用,反复执行,不会留下任何东西。存活的浏览器永远不会被清理——其所属进程仍在运行,而无法归属的配置文件会一直保留,直到没有进程打开它且空闲 60 秒。
机器人检测
自己运行一下:npm run test:bot。最新结果:
检测器 | 结果 |
31 通过,0 失败 | |
6 绿色,0 红色, | |
|
普通的无头 Chrome 在 sannysoft 上有四行失败(HEADCHR_UA、CHR_MEMORY、WebGL SwiftShader、旧 UA),并被报告为机器人。
无法访问的检测器计为 SKIP,绝不视为通过,门禁要求三个可访问的通过——所以测试站点宕机的日子不会被误认为成功。
环境比补丁更重要
相同的代码,在两个地方测量:
此工作站 | GitHub Actions 运行器 | |
IP | 住宅 | 数据中心 |
GPU | 真实(NVIDIA) | 无 → SwiftShader |
| 31 通过,0 失败 | 30 通过,1 失败( |
| 6 绿色,0 红色 | 6 绿色,0 红色 |
|
|
|
所有 CDP 级别的信号在两种环境中都保持干净——这部分是代码,代码是正确的。改变判定结果的是环境:数据中心 ASN 加上软件渲染会触发弱信号综合判定,这不是任何程度的指纹修补能解决的。
这就是问题的真实面貌。加固浏览器消除了明显的破绽。在哪里运行决定了其余部分。
加固做了什么,以及为什么
每一项都来自检测器告诉我们错了:
没有
--enable-automation。 这个标志——Puppeteer 和 Playwright 会添加——正是设置navigator.webdriver = true的东西。原始 CDP 不会设置它,所以无需修补它保持false。没有
Runtime.enable。 它是最响亮的 CDP 破绽,为经典的 console/Error.stack检测器提供动力。Runtime.evaluate没有它也能正常工作。窗口和屏幕一起移动。 没有
--ozone-override-screen-size的--window-size会产生outerWidth > screen.width,这在物理上是不可能的——比普通无头模式更强的信号。没有默认的设备指标覆盖。 通常的 1280×720 是 Playwright 的默认视口,检测器按名称标记它。仅在需要时设置
--viewport。可用时使用真实 GPU,提供真正的
ANGLE (NVIDIA …)渲染器而不是 SwiftShader。UA 在启动时设置,而不仅仅通过 CDP。
Emulation.setUserAgentOverride无法到达 Web Workers,所以 worker 会继续报告无头 UA,而页面报告干净的 UA(hasInconsistentWorkerValues)。没有
acceptLanguage覆盖。 CDP 通过拆分该头来推导navigator.languages,所以"en-US,en;q=0.9"变成["en-US","en;q=0.9"]——一个不可能合法存在的 q 值,以及另一个页面/worker 不匹配。--lang能正确做到这一点。客户端提示源自二进制自身的版本,所以
Sec-CH-UA不可能与navigator.userAgent不一致。每个会话独立的浏览器上下文,关闭时销毁——每个任务干净的状态,无需第二个浏览器进程。
反复出现的教训:一致性胜过覆盖面。 其中有四项是部分伪造使检测更容易的情况,只有运行真实检测器才能发现。
为什么 WebGL 伪造默认关闭
spoofWebgl 存在且实现得很仔细——一个围绕原生 getParameter 的 Proxy,所以 Function.prototype.toString 仍然报告 [native code]。它默认关闭,因为测量表明它适得其反。 来自 test/webgl-spoof-experiment.mjs:
分支 | 声称的渲染器 | maxTexture | 扩展 | sannysoft | 判定 |
真实 GPU,无伪造 | NVIDIA | 32768 | 37 | 0 失败 |
|
SwiftShader,诚实 | SwiftShader | 8192 | 35 | 1 失败 |
|
SwiftShader + 伪造 | NVIDIA | 8192 | 35 | 0 失败 |
|
声称拥有你没有的硬件修复了一行表面问题,却导致综合检测器失败:注入的脚本无法到达 Web Workers,所以 worker 仍然报告 SwiftShader,而 MAX_TEXTURE_SIZE 保持在软件值,同时渲染器字符串声称是独立 GPU。
诚实的 SwiftShader 通过。令人信服的谎言却不行。给浏览器一个真实 GPU 吧——这是免费的。
这不做什么
指纹级检测是全部范围。它无法击败,也不试图击败:
TLS/JA3-JA4 和 HTTP/2 指纹识别——在任何 JavaScript 运行之前就已决定
IP 信誉——数据中心与住宅 ASN,通常是真正的障碍
行为分析——鼠标轨迹、时序、停留时间
商业挑战产品依赖这些,所以"通过门禁"意味着不会被轻易标记为自动化,绝不是无法检测。适用于你自己的网站、测试、无障碍工作和普通的代理浏览。
内存
一个 Chrome 栈大约消耗 450 MB。杠杆是架构,不是标志:运行一个浏览器和多个隔离上下文,而不是每个任务一个浏览器。启动一次浏览器,然后通过 --endpoint / AGENT_BROWSER_ENDPOINT 让每次调用都指向它。--block-images 对文本工作有帮助。
零依赖
dependencies 是空的,包括 WebSocket 传输。
Node 的全局 WebSocket(WHATWG)无法发送请求头,任何需要认证或代理的 CDP 端点都需要请求头,而 undici 无法单独导入。所以 src/ws.mjs 直接在 node:http(s) 之上实现 RFC 6455——握手、掩码、延续分片、64 位长度、ping/pong——这是 CDP 所需的全部。
Markdown 转换器使用显式栈遍历 DOM,无论嵌套多深,JS 调用深度都保持在 O(1),并对叶级内联节点使用原生 innerText。这使得它在深度嵌套的文档上递归安全,在大页面上也明显更快。
环境
| Chrome/Chromium 二进制文件的路径 |
| 附加到此 CDP 端点而不是启动 |
要求
Node ≥ 18 和 Chrome/Chromium 安装。无需构建步骤。
致谢
这个项目的加固几乎完全源自其他人发表的检测研究——参见 CREDITS.md。特别感谢 rebrowser-bot-detector、bot.sannysoft.com、deviceandbrowserinfo.com 和 Camoufox 展示了如何正确地做到这一点。
许可证
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Browser MCP for logged-in tasks. Uses your Chrome — credentials stay local. Zero-token replay.
Web scraping MCP server: any URL to Markdown or JSON, with JavaScript rendering and batch jobs.
MCP server for Firecrawl — web search, scraping, and biomedical/arXiv paper search.
Stealth web browser for agents: search, fetch, click, download and type in persistent MCP sessions.
Related MCP Servers
- AlicenseAqualityBmaintenanceMCP server for browser automation with anti-detection. Scout pages, find elements, interact with websites, and monitor network traffic from any AI client that supports the Model Context Protocol.211MIT
- FlicenseBqualityCmaintenanceUltra-fast browser automation server over Chrome DevTools Protocol (CDP), exposed as MCP, enabling AI agents to control a real Chrome browser with low latency and minimal token usage.212-
- AlicenseAqualityBmaintenanceA fault-tolerant, stealth-enabled Model Context Protocol (MCP) server for web searching and content fetching. Built for AI Agents (Cursor, Claude Code, OpenCode), it uses a stealth browser engine to fetch pages, dynamically handles SPAs/React, and converts bloat into token-optimized Markdown.213 npm3MIT
- AlicenseAqualityAmaintenanceMCP server that drives your real Chrome/Edge/Opera browser through a Chrome extension and DevTools Protocol, preserving logins and session state, and can also perform OS-level mouse and keyboard input behind approval.5134 PyPI2MIT