browser-for-ai
A CDP-native MCP server that lets AI agents drive a real Chrome browser with deep network/console inspection, robust page interaction, and the ability to reverse-engineer a site's API flow into runnable code with dependency chaining and replay verification.
README
browser-for-ai (bfa)
English · ภาษาไทย
A CDP-native MCP server that lets an AI agent (Claude Code and any other MCP client) drive a real Chrome at full depth — reading the network and console the way a human does with DevTools open, operating the page robustly, and reverse-engineering a site's API flow into runnable code.
Why bfa
The things a screenshot-only browser tool can't do:
- ⭐ Turn a real session into runnable code. Mark a flow, perform it in the
browser, and bfa synthesizes replay code (curl / TypeScript / Go / Python)
with cross-call dependencies chained automatically — an auth token from one
response becomes a variable the next request re-uses, not a baked-in literal.
flow_replaythen runs it for real to prove the reversal reproduces. - 🔬 See the whole network. Full request/response bodies (text and binary
base64), the complete on-the-wire headers (
Cookieand custom signing headers included, merged from CDP ExtraInfo), timing, redirect hops, and WebSocket frames — surfaced by the exact question you're asking:failures,pending(hangs),slow. - 🎮 Drive anything. Ref / CSS interaction and raw coordinate + touch
for
<canvas>/ WebGL surfaces with no DOM. Every action reports the network / console / URL delta it caused. - 🧪 Shape traffic. Block / mock / modify requests, and throttle to Slow 3G / offline / custom bandwidth with CPU slowdown.
- 🗂️ Real sessions. Many concurrent sessions, incognito, attach to your logged-in Chrome, save/restore cookies + storage, and complete cache clearing.
How it compares
| Capability | bfa | typical browser MCPs |
|---|---|---|
| Reverse a captured flow → runnable code, dependency-chained + replay-verified | ✅ | ✗ (at most Playwright-script codegen from UI actions) |
| Full response bodies (text + binary) & WebSocket frames, on by default | ✅ | mostly metadata only |
| Secret redaction in the emitted code | ✅ | ✗ |
| Coordinate + touch interaction for canvas / WebGL | ✅ | some (vision mode) |
| Attach to your logged-in Chrome | ✅ | ✅ (common) |
| Network / CPU throttling presets | ✅ | some |
| Cloud-scaled browsers · stealth · proxies · CAPTCHA | ✗ (local by design) | some cloud tools |
bfa is a local, developer-facing inspection & reverse-engineering tool, not a cloud scraping farm — that focus is why the first three rows are rare elsewhere.
Requirements
- Node.js ≥ 20
- Google Chrome installed (or set
BFA_CHROME_PATHto your Chrome binary)
Install & build
git clone https://github.com/icueth/browser-for-ai.git
cd browser-for-ai
npm install
npm run build # → dist/server.js
Register with Claude Code
claude mcp add browser-for-ai --scope user -- node /absolute/path/to/browser-for-ai/dist/server.js
If
nodecomes from a version manager (nvm, asdf, …), pass the absolute path to the node binary — the MCP server is spawned by a non-interactive shell that won't resolve aliases.
Verify with claude mcp get browser-for-ai (should say ✔ Connected). Tools
load into a new session, so start a fresh Claude Code session afterward.
Quick start
browser_launch { "mode": "fresh", "url": "https://example.com" } // real window
page_screenshot
net_list // recent requests
net_failures // anything that errored
net_pending // anything still hanging
page_snapshot // ref-annotated DOM
page_click { "selector": "#login" }
net_get { "url": "/api/login" } // one call in full: headers + bodies
browser_close { "all": true }
Sessions
browser_launch { mode, url?, port?, profile?, incognito?, headless?, viewport? }
fresh— launch our own Chrome (headful by default;headless: truefor none).attach— connect to a Chrome started with--remote-debugging-port(onlyportis used; default9222).incognito: true— isolated context, no prior state.- Profiles. No
profile→ ephemeral temp profile wiped on close. A named{ "profile": "work" }persists under~/.bfa/profiles/workso logins survive. Two concurrent sessions on the same named profile collide; unnamed ones are always safe. - Viewport at launch, or
page_set_viewporton a live session.
Manage with browser_sessions, browser_use { sessionId }, browser_tabs,
browser_close. Most tools accept an optional sessionId; without it they target
the active session.
Tool reference (44)
Sessions & lifecycle
| tool | purpose |
|---|---|
browser_launch |
launch fresh / attach a session |
browser_sessions |
list open sessions |
browser_use |
set the default session |
browser_tabs |
list a session's tabs/targets |
browser_close |
close one session, or all |
browser_clear_cache |
clear cache + cookies + storage |
browser_hard_reload |
bypass-cache reload |
Navigation, state & read
| tool | purpose |
|---|---|
page_goto |
navigate to a URL |
page_state |
url, title, readyState, viewport |
page_set_viewport |
resize a live session's viewport |
page_snapshot |
compact, ref-annotated DOM (source of element refs) |
page_observe |
delta since last observe — new console/network/URL/DOM |
page_screenshot |
PNG of viewport, full page, or one element |
page_eval |
evaluate JS in the page, return the value |
Interaction
| tool | purpose |
|---|---|
page_click |
click a ref / selector (reports the delta) |
page_type |
type into a field (clear:true to replace) |
page_fill |
fill several fields in one call |
page_select |
choose an <option> by value |
page_key |
press a key or combo (e.g. "Enter", "Control+A") |
page_hover |
hover an element |
page_scroll |
scroll the window, or an element into view |
page_upload |
attach file(s) to a file <input> |
page_click_at |
click at raw {x, y} (canvas/WebGL) |
page_tap_at |
touch-tap at {x, y} |
page_drag |
drag between two points/elements |
Network (deep read)
| tool | purpose |
|---|---|
net_list |
recent requests (filter by url/method/type/status) |
net_get |
one request in full: headers, request & response bodies |
net_failures |
4xx/5xx + transport failures with error detail |
net_pending |
requests still in flight (hang candidates) |
net_slow |
finished requests slower than a threshold |
net_ws |
WebSocket connections + recent frames |
net_wait |
wait until a matching request appears / settles |
Traffic shaping & emulation
| tool | purpose |
|---|---|
net_intercept_add |
block / mock / modify matching requests (CDP Fetch) |
net_intercept_list |
list active intercept rules |
net_intercept_clear |
remove intercept rules |
net_throttle |
emulate network (offline / 3G / 4G / custom) + CPU slowdown |
Console
| tool | purpose |
|---|---|
console_list |
console messages (filterable by regex) |
console_errors |
errors + uncaught exceptions with stacks |
API-flow extraction
| tool | purpose |
|---|---|
flow_mark |
mark the start of a flow in the recording |
flow_export |
export captured calls as JSON summary or HAR |
flow_synthesize |
generate replay code (curl/ts/go/python) with deps chained |
flow_replay |
execute the reversed flow for real (Node fetch) to verify |
Session persistence
| tool | purpose |
|---|---|
session_save |
save cookies + local/session storage to ~/.bfa/state |
session_restore |
re-apply a saved session (origin-scoped) |
Reverse-engineering an API flow → runnable code
The flagship workflow. A page logs in with POST /api/login (returns a token),
then calls GET /api/me with Authorization: Bearer <token>:
browser_launch { "mode": "fresh", "url": "https://app.example.com/login" }
flow_mark { "label": "login flow" }
page_fill { "fields": [
{ "selector": "#user", "value": "alice" },
{ "selector": "#pass", "value": "s3cret" }
]}
page_click { "selector": "#submit" }
flow_synthesize { "target": "curl" }
produces:
resp0=$(curl -s -X POST 'https://app.example.com/api/login' \
-H 'content-type: application/json' \
-d '{"user":"alice","pass":"s3cret"}')
token=$(echo "$resp0" | jq -r '.token') # ← lifted from the response
curl -s -X GET 'https://app.example.com/api/me' \
-H "authorization: Bearer $token" # ← re-used, not a literal
flow_synthesize also emits TypeScript / Go / Python, flow_replay runs the
sequence for real (deps resolved from each live response) and reports ✓ / ✗
per call, and { "redact": true } swaps secret-bearing header values and
whole-token bodies for env placeholders.
Dependency detection is heuristic (exact / url-encoded / base64 / JWT-claim / substring). Unmatched values stay literal for you to review; always read the generated code before shipping it.
Cookbook
A. Debug a slow or hung page
browser_launch { "mode": "fresh", "url": "https://myapp.com" }
net_pending // the request that never finishes → the hang
net_slow { "thresholdMs": 1000 } // finished-but-slow calls, slowest first
net_failures // 4xx/5xx + transport errors
console_errors // the thrown stack trace
net_get { "url": "/api/user" } // the failing call in full
B. Reverse-engineer an API into runnable code
browser_launch { "mode": "fresh", "url": "https://app.com/login" }
flow_mark { "label": "login+fetch" }
page_fill { "fields": [
{ "selector": "#user", "value": "me" },
{ "selector": "#pass", "value": "pw" }
]}
page_click { "selector": "#submit" }
flow_synthesize { "target": "python" } // code with the token chained in
flow_replay // ✓/✗ per call — verified
C. Stay logged in across runs
session_save { "name": "myapp" } // first run, after logging in
// later:
browser_launch { "mode": "fresh" }
session_restore { "name": "myapp" } // back in, no re-login
D. Drive a canvas / WebGL app
browser_launch { "mode": "fresh", "incognito": true, "url": "https://game.example",
"viewport": { "width": 390, "height": 844 } } // portrait
page_click_at { "x": 195, "y": 700 } // press a button drawn on the canvas
net_ws // read the app's WebSocket frames
net_pending // catch asset-load hangs
E. Test under a bad network / mocked endpoint
net_throttle { "preset": "slow-3g", "cpuRate": 4 } // degrade the connection + CPU
net_intercept_add { "urlIncludes": "/api/config", "action": "mock",
"status": 200, "body": "{\"feature_x\":true}" }
browser_hard_reload
net_slow // see what drags under 3G
net_throttle { "preset": "none" } // reset to full speed
F. Upload a file through a form
page_snapshot
page_upload { "selector": "input[type=file]", "files": ["/abs/path/resume.pdf"] }
page_click { "selector": "#submit" }
net_get { "url": "/upload" } // confirm the multipart request
Canvas / WebGL games
Puppeteer defaults to an 800×600 landscape viewport. A portrait game then renders letterboxed, and its full-screen input overlay can swallow coordinate clicks. Launch (or resize) with a portrait viewport so the canvas fills the screen:
browser_launch { "mode": "fresh", "incognito": true, "url": "…",
"viewport": { "width": 390, "height": 844 } }
page_set_viewport { "width": 390, "height": 844 } // on a live session
Keep hasTouch:false (default) so page_click_at (a real mouse click) drives
games listening for mouse input. For touch-only games, set the viewport
hasTouch:true and use page_tap_at { x, y }.
Which mode do I want?
fresh(default) — a throwaway Chrome, zero setup. Use for reverse-engineering a public flow or any site that does not need your existing login.attach— connect to a Chrome you started with a debug port. Use when you need real logins/cookies or a human-looking browser:navigator.webdriverisfalse, real profile & fingerprint, so it passes basic bot checks that a puppeteer-launched Chrome fails. Setup below.
Attach to a real, logged-in Chrome
A normally-opened Chrome has no debug port, and Chrome 136+ refuses one on the default profile (an anti-cookie-theft hardening) — so attach always uses a separate profile:
# dedicated profile (recommended) — a window opens; log in there once, it persists
./bin/bfa-chrome 9222
# …or reuse your existing logins via a COPY of your profile (a non-default dir)
cp -R "$HOME/Library/Application Support/Google/Chrome" "$HOME/.bfa/real-copy"
./bin/bfa-chrome 9222 "$HOME/.bfa/real-copy"
Then: browser_launch { "mode": "attach", "port": 9222 }. (If the port isn't up, the
tool's error tells you this exact recipe.)
⚠️ A copied real profile hands the agent every logged-in session it contains — email, cloud consoles, banking, source control. It can read those pages and act as you. Prefer the dedicated profile; use a real-profile copy only when you need those logins and accept that blast radius.
Do not point
bfa-chromeat your live default profile: on Chrome 136+ the debug port silently won't open, and it would also collide with your running Chrome (one process per profile dir).
Roadmap
Gaps we know about, in rough priority order:
- iframe-aware refs —
page_snapshot/ interaction currently resolve the top document only; cross-frame ref support is the next correctness item. - Device emulation presets — bundle UA + viewport + touch + geolocation + permission grants into one call.
- PDF export —
Page.printToPDFfor report/invoice-style pages. - Playwright/Puppeteer test emission — a new
flow_synthesizetarget that outputs a runnable test script, not just replay code. - Natural-language element targeting — an optional LLM-assisted layer over the existing deterministic ref model.
- Performance tracing — a thin
Tracing.start/stopwrapper.
Out of scope by design: cloud-scaled browsers, stealth/anti-bot, and residential proxies — bfa stays a local inspection tool.
Notes & limitations
- The agent sees whatever the attached/launched browser sees. Treat an attached real-profile Chrome as full access to your logged-in accounts.
- Native dialogs (
alert/confirm/beforeunload) are auto-dismissed so the session never hangs on one. flow_replayonly replayshttp/https, times out per request, is capped overall (60 s / 200 steps), and never touches the live browser session.- Headers are captured from the actual wire (CDP ExtraInfo), so
Cookieand network-added headers are recorded — not just whatrequestWillBeSentfirst saw — andnet_getshows every one, including custom signing headers (x-api-key,x-signature,agent, …), not just a well-known subset. - Dependency detection and secret redaction are best-effort heuristics — review
generated code and exported HAR before sharing or running against production.
A computed value bfa can't reverse (e.g. a signature like
MD5(secret + timestamp)) stays a literal; a failingflow_replayusually means exactly such a header still needs to be reproduced in your own code.
Development
npm run typecheck
npm test # unit + real-Chrome integration + in-memory MCP e2e
npm run build
License
推荐服务器
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 模型以安全和受控的方式获取实时的网络信息。