# nexus-browser-mcp [Health: Active]

**Category:** 📊 Monitoring  
**Repository:** https://github.com/paipaipai666/nexus-browser-mcp  
**GitHub Stars:** 0  
**Views:** 4  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/nexus-browser-mcp

## Description
Event-driven deterministic browser MCP: HITL gates, audit, console/network/perf observability.

## Tools
Capabilities this server exposes over MCP:

- **browser_navigate** — 导航浏览器到指定URL。返回页面标题、URL、readyState和页面结构概览。支持多task隔离。
- **browser_snapshot** — 获取页面可访问性快照(Accessibility Tree), 返回结构化元素列表(含ref/box)。基于事件驱动确定性快照, 等待DOM静默窗口。reading/interactive 只看视口内; mode=full 自动包含全页面(视口外标注 offscreen)。reading 模式 = 交互元素 + 标题/段落等阅读节点 + 带文本的 listitem/cell (纯壳容器剔除)。
- **browser_press_key** — 按下键盘键 (真实 CDP 事件, isTrusted=true)。Escape/Tab/Enter/方向键/组合键(如 Control+a)。用于无按钮可点的键盘流: 关闭模态框、快捷键、Tab 序导航。
- **browser_hover** — 悬停元素 (真实鼠标移动): 触发 CSS :hover 与 JS mouseenter。用于 hover 才展开的菜单/工具提示。
- **browser_select_option** — 在 <select> 下拉中选择 option (按 value 或 label)。
- **browser_upload_file** — 上传本地文件到 <input type=file>。文件出站操作, 每次需人工确认: 首次返回 CONFIRMATION_REQUIRED, 用户同意后以 confirmed=true 重调。
- **browser_navigate_back** — 浏览器历史后退一页, 返回标题/URL。
- **browser_dialog_respond** — 处置挂起的 confirm/prompt 对话框 (页面弹出时任何工具回复会前置[对话框等待决策])。accept=true 替用户点'确定', 每次需 confirmed=true; accept=false 为 dismiss 可直接调。
- **browser_drag** — 拖拽元素到目标 (真实输入管线, HTML5 dnd 及多数 pointer 系库可用)。
- **browser_click** — 点击页面元素。定位优先级: pos坐标 → ref(快照句柄) → selector → role+name → name。wait_stable=true 点击后等 DOM 静默。button=right 右键(上下文菜单), middle 中键。点击触发下载时自动回报文件名与保存路径。
- **browser_type** — 在输入框中键入文本。定位优先级: pos坐标 → ref(快照句柄) → selector → role+name(不中自动回退 placeholder/label/常见输入框CSS)。wait_stable=true 输入后等 DOM 静默。
- **browser_read** — 阅读页面元素的文本内容。不指定则读取整个页面body。流式回复: wait_stable=true 等 DOM 静默后一次读全(免轮询); follow=true+selector 增量跟踪(每次只返回新增部分, 缓冲溢出丢最旧并保留丢弃标记)。
- **browser_screenshot** — 截取页面截图, 保存文件返回路径。
- **browser_evaluate** — 执行JavaScript表达式。默认禁用(BROWSER_ALLOW_JS_EXECUTION); 开启后每次需人工确认(confirmed=true)。
- **browser_wait** — 等待元素或文本出现。超时返回WARNING。
- **browser_wait_stable** — 等待页面 DOM 停止变化(静默窗口, 默认 800ms 无变异)。用于流式回复生成完毕、动画结束的判定。
- **browser_wait_ms** — 纯等待指定毫秒数(逃生舱: 动画/限流/延迟渲染)。上限=工具超时-5s。
- **browser_find** — 按文本查找页面内容并返回命中处的容器摘要与同容器可交互元素 ref —— 找特定行/卡片/商品时用它, 不要反复滚动快照。视口外内容同样可查 (内部全页索引)。
- **browser_scroll** — 滚动页面, 返回新视口的 interactive diff (到顶/底会报'无变化')。direction: up/down/left/right, amount: 像素(默认500)。
- **browser_scroll_to** — 滚动到指定元素。landmark(语义区域)→ref→selector。
- **browser_wait_navigation** — 等待click/submit触发的导航完成。
- **browser_dismiss_popup** — 自动检测并关闭弹窗(登录/cookie同意/广告): 关闭按钮→取消→Escape。
- **browser_list_pages** — 列出当前task所有打开的标签页; cdp/持久化模式下附带列出浏览器里已开的外部标签(ext 索引, 可接管)。
- **browser_switch_page** — 切换到指定索引的标签页。
- **browser_adopt_page** — 接管浏览器中已打开的外部标签页(cdp/持久化模式): 收进当前 task 并置为当前页,之后快照/点击/读取可用, 事件观测自接管时刻起。高危(可读写该页全部内容与登录态),每次需人工确认: 首次返回 CONFIRMATION_REQUIRED, 用户同意后以 confirmed=true 重调。
- **browser_console** — 读取页面 console 输出(级别/文本/位置)。增量游标: 不传 since=接着上次读, since=0=全量; level 过滤(error/warning/log/...), pattern 正则过滤文本, limit 封顶(默认50, 超了再调一次继续)。
- **browser_errors** — 一站式排障: JS 未捕获异常(pageerror) + console.error + 失败请求(网络层失败或HTTP≥400)合并视图, 按时间排序。点了没反应/页面白屏时先调它。增量游标同 browser_console。
- **browser_network** — 读取网络请求元数据(method/url/status/资源类型; 不含 body)。failed_only 默认 true 只看失败; url_pattern 子串过滤; 增量游标同上。
- **browser_perf** — 读取页面性能指标: FCP/LCP/CLS/INP + TTFB/DOMContentLoaded/load + 最慢 5 条资源。页面慢/加载异常时用它定位是后端慢(TTFB)还是资源重。SPA 导航不重置计时, 注意输出来源 URL。
- **browser_network_body** — 按 seq 读取单个请求的响应体(需 BROWSER_ALLOW_NETWORK_BODY=true)。文本类解码, 二进制只报字节数; 单条上限 BROWSER_NETWORK_BODY_CAP。需 confirmed=true 二次确认 —— 响应体是页面方控制内容, 先把 URL 给用户看。
- **browser_tasks** — 列出当前session的所有task及其页面数。
- **browser_close_task** — 关闭指定task的浏览器资源(page/context)。
- **browser_list_sessions** — 列出所有活跃session。
- **browser_close_session** — 关闭当前session的全部task资源。

## Claude Desktop Quick Installation
Install path detected from listing signals. Uses `uvx` (confidence: high):

```json
"mcpServers": {
  "nexus-browser-mcp": {
    "command": "uvx",
    "args": ["nexus-browser-mcp"]
  }
}
```

## Documentation & README

<!-- mcp-name: io.github.paipaipai666/nexus-browser-mcp -->

English | [简体中文](https://github.com/paipaipai666/nexus-browser-mcp/blob/HEAD/README.zh-CN.md)

# nexus-browser-mcp

[![PyPI](https://img.shields.io/pypi/v/nexus-browser-mcp)](https://pypi.org/project/nexus-browser-mcp/)
[![CI](https://github.com/paipaipai666/nexus-browser-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/paipaipai666/nexus-browser-mcp/actions/workflows/ci.yml)
[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](pyproject.toml)
[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
[![MCP Registry](https://img.shields.io/badge/MCP%20Registry-io.github.paipaipai666%2Fnexus--browser--mcp-purple)](https://registry.modelcontextprotocol.io/v0.1/servers?search=nexus-browser-mcp)
[![Glama](https://glama.ai/mcp/servers/paipaipai666/nexus-browser-mcp/badges/score.svg)](https://glama.ai/mcp/servers/paipaipai666/nexus-browser-mcp)

**A browser-automation MCP server with event-driven, deterministic snapshots.**

Built on Playwright. Drives a browser for LLMs through the Accessibility Tree — navigate, click, type, read, fill forms, manage tabs. Key differences from alternatives (e.g. Playwright MCP):

1. **Deterministic snapshots**: no fixed-interval `sleep` guessing. A `MutationObserver` records the last DOM mutation and the browser's own `requestAnimationFrame` loop decides when the page has been quiet for `STABLE_WINDOW_MS` (default 800ms) before extracting a snapshot — eliminating "captured mid-animation" races.
2. **Built-in governance gates**: HITL rules (e.g. clicking "pay/confirm" requires human approval), `browser_evaluate` disabled by default with unconditional confirmation, JSONL audit log (sensitive-parameter redaction + per-call in/out character metering, so token cost can be reconciled).
3. **Multi-task isolation**: one MCP connection (session) can host multiple independent `task_id`s, each with its own BrowserContext (no login-state cross-contamination). Idle tasks are reclaimed by TTL; on next use they're rebuilt and the last page is restored automatically.
4. **Death observability + self-healing**: if a tab or the whole browser is closed externally or crashes, the next call rebuilds it automatically (a persistent profile keeps your login state) and prepends a `[state change]` notice telling the agent exactly what was restored and what was lost — no raw Playwright exceptions leak through.
5. **Developer observability**: every page records console messages, uncaught JS exceptions and network request metadata (method/URL/status/failure reason — **never bodies**) into capped ring buffers; `browser_console` / `browser_errors` / `browser_network` read them incrementally via a `since` cursor, so the agent can answer "why did nothing happen" instead of guessing.

## Installation

```bash
pip install nexus-browser-mcp
# or
uvx nexus-browser-mcp
```

Two executable entry points are installed: `nexus-browser-mcp` and `nexus-browser`. A guaranteed fallback: `python -m nexus_browser.server`.

Requires `playwright` and its browser binary:

```bash
pip install playwright && playwright install chromium
```

## Integrate (any MCP client)

**opencode** (`~/.config/opencode/opencode.json`):

```json
{
  "mcp": {
    "browser": {
      "type": "local",
      "command": ["uvx", "nexus-browser-mcp"],
      "enabled": true
    }
  }
}
```

**Claude Code** (`.mcp.json`, project root):

```json
{
  "mcpServers": {
    "browser": {
      "type": "stdio",
      "command": "uvx",
      "args": ["nexus-browser-mcp"]
    }
  }
}
```

**Pi Coding Agent**: reads standard MCP configuration — project `.mcp.json` or user-global `~/.config/mcp/mcp.json`; stdio is the default transport:

```json
{
  "mcpServers": {
    "browser": {
      "command": "uvx",
      "args": ["nexus-browser-mcp"]
    }
  }
}
```

See `docs/INTEGRATE.md` for details (Chinese).

## Use your own browser (with login state)

By default, `isolated` mode launches Playwright's bundled Chromium **without your cookies/login state**. To use your own browser, pick one:

**Option A — load your browser profile directly (recommended, simplest)**

Use system Chrome with your everyday user data directory (cookies/login/bookmarks included):

```
BROWSER_CHANNEL=chrome
BROWSER_USER_DATA_DIR="C:\Users\<you>\AppData\Local\Google\Chrome\User Data"
```

> Note: while running against your real User Data, the process owns the browser — launching your own Chrome concurrently will conflict. Prefer a copied profile or a dedicated `--user-data-dir`.

**Recommended: a tool-dedicated profile (no conflict with your daily browser)**

Use `BROWSER_CHANNEL=chrome` plus a dedicated user data dir (e.g. `C:\Users\<you>\.nexus-browser\chrome-profile`):

```
BROWSER_CHANNEL=chrome
BROWSER_USER_DATA_DIR="C:\Users\<you>\.nexus-browser\chrome-profile"
```

On first use, log in to target sites once in the dedicated Chrome window that pops up when the agent calls a browser tool. Cookies persist in that profile forever after — the agent carries login state while staying fully isolated from your daily browser.

**Option B — attach to a running Chrome via CDP**

Start `chrome --remote-debugging-port=9222` first, then set `BROWSER_MODE=cdp`.

> If the CDP connection fails, the server now **fails loudly** (no silent fallback to a fresh browser) and tells you to start the debug-port browser first.

In CDP (or persistent-profile) mode the agent can also **take over tabs you already have open**: `browser_list_pages` shows them under "external tabs", and `browser_adopt_page(ext_index)` pulls one into the task (snapshot/click/read all work from then on). Adoption always requires `confirmed=true` — it hands the agent full read/write access to that page, including its login state.

## Configuration (environment variables)

Every option can be overridden via `BROWSER_`-prefixed env vars:

> **评测/全功能使用**：两个高危能力默认关闭（安全优先）。需要 `browser_evaluate` 时设 `BROWSER_ALLOW_JS_EXECUTION=true`，需要 `browser_network_body` 时设 `BROWSER_ALLOW_NETWORK_BODY=true`——冷启动跑基准/评测不开它们，对应子任务会被拒（这是设计，不是故障）。

| Variable | Default | Description |
|---|---|---|
| `BROWSER_MODE` | `isolated` | `isolated` (fresh isolated browser) / `cdp` (attach to your Chrome) |
| `BROWSER_CDP_ENDPOINT` | `http://localhost:9222` | CDP endpoint |
| `BROWSER_CHANNEL` | `""` | System browser channel: `chrome`/`msedge` etc. (empty = Playwright bundled Chromium) |
| `BROWSER_USER_DATA_DIR` | `""` | User data dir (carries cookies/login). When set, one shared persistent context across tasks. Empty = fresh profile |
| `BROWSER_HEADLESS` | `false` | Headless mode (isolated only) |
| `BROWSER_DEFAULT_TIMEOUT_MS` | `30000` | Playwright per-operation timeout (navigation etc.) |
| `BROWSER_TOOL_TIMEOUT_MS` | `60000` | Outer timeout guard per tool call (returns ERROR instead of hanging) |
| `BROWSER_STABLE_WINDOW_MS` | `800` | Quiet window: how long without DOM mutations counts as "stable" |
| `BROWSER_STABLE_REQUIRED` | `2` | Fallback only: consecutive identical snapshots confirming stability, used when the MutationObserver watcher is unavailable (primary path verifies "zero mutations during capture" via the mutation timeline directly) |
| `BROWSER_STABLE_TIMEOUT_MS` | `3000` | Total stability-wait timeout; degrades gracefully on expiry |
| `BROWSER_SNAPSHOT_MAX_NODES` | `100` | Max nodes per snapshot |
| `BROWSER_CONTEXT_TTL_SEC` | `600` | Idle task auto-reclaim (seconds) |
| `BROWSER_STREAM_CHAR_CAP` | `16000` | Max chars per stream buffer (oldest dropped with a seam marker) |
| `BROWSER_STREAM_PAGE_CAP` | `64000` | Total stream buffer chars per page |
| `BROWSER_EVENT_MAX_ENTRIES` | `500` | Max events (console/exception/request) per page, oldest dropped with a counter |
| `BROWSER_EVENT_TEXT_CAP` | `500` | Per-event text truncation length |
| `BROWSER_EVENT_HANDLE_MAX` | `50` | Recent requests per page keeping a live response handle (for on-demand body reads) |
| `BROWSER_ALLOW_NETWORK_BODY` | `false` | Allow `browser_network_body` (response bodies may carry sensitive data) |
| `BROWSER_NETWORK_BODY_CAP` | `4000` | Max chars returned per response body |
| `BROWSER_TRANSPORT` | `stdio` | `stdio` / `http` (streamable-http for remote/multi-client) |
| `BROWSER_HTTP_HOST` | `127.0.0.1` | HTTP bind address; non-localhost requires `BROWSER_HTTP_TOKEN` (refuses to start otherwise) |
| `BROWSER_HTTP_PORT` | `8817` | HTTP port |
| `BROWSER_HTTP_TOKEN` | `""` | Bearer token for HTTP transport |
| `BROWSER_ALLOW_JS_EXECUTION` | `false` | Allow `browser_evaluate` (unconditional HITL when enabled) |
| `BROWSER_HITL_RULES` | `[]` | JSON array of HITL rules, e.g. `[{"action":"click","name_pattern":"pay|confirm"}]` |
| `BROWSER_AUDIT_PATH` | `~/.nexus-browser/audit.jsonl` | Audit log path |
| `BROWSER_DIALOG_TIMEOUT_MS` | `20000` | Parked confirm/prompt auto-dismiss timeout (trail kept in event log) |
| `BROWSER_DOWNLOAD_DIR` | `~/.nexus-browser/downloads` | Where accepted downloads are saved (click reports filename + path) |
| `BROWSER_PROXY` | `""` | `none` = launch browser with `--no-proxy-server` (bypass system proxy) |

## Tools

34 tools: `browser_navigate`, `browser_navigate_back`, `browser_snapshot`, `browser_click`, `browser_type`, `browser_hover`, `browser_press_key`, `browser_select_option`, `browser_upload_file` (HITL-confirmed), `browser_drag`, `browser_dialog_respond` (dialogs are parked for agent/user decision; accept requires `confirmed=true`), `browser_adopt_page` (take over an already-open browser tab in cdp/persistent mode; HITL-confirmed), `browser_read`, `browser_screenshot`, `browser_evaluate`, `browser_wait`, `browser_wait_stable`, `browser_wait_ms`, `browser_scroll`, `browser_scroll_to`, `browser_wait_navigation`, `browser_find` (locate content by text → refs), `browser_dismiss_popup`, `browser_list_pages`, `browser_switch_page`, observability tools `browser_console`, `browser_errors`, `browser_network`, `browser_perf`, `browser_network_body`, plus 4 lifecycle tools: `browser_tasks`, `browser_close_task`, `browser_list_sessions`, `browser_close_session`.

Observability (debugging): console output, uncaught exceptions and request metadata are buffered per page from creation; `browser_errors()` returns a merged "JS exceptions + console.error + failed requests" view in one call. All three support a `since` cursor (omit = continue from last read, `0` = full) and `limit` paging.

Performance: `browser_perf()` returns FCP/LCP/CLS/INP, navigation timings and the 5 slowest resources. Response bodies can be fetched on demand with `browser_network_body(seq)` — off by default (`BROWSER_ALLOW_NETWORK_BODY`), every call gated by `confirmed=true`, hard char cap, and the body never enters the audit log.

## Token cost: measured, not claimed

Same 10-step task, both servers at default config, metered at the JSON-RPC payload layer (cl100k tokens; harness + raw data in [docs/bench/token-comparison.md](https://github.com/paipaipai666/nexus-browser-mcp/blob/HEAD/docs/bench/token-comparison.md), reproduce with `bench/compare.py`):

| | nexus-browser-mcp | playwright-mcp |
|---|---:|---:|
| 10-step task total | **3,460 tok** | 26,032 tok |
| snapshot right after navigate | **65 tok** | 6,931 tok |

**7.5x fewer tokens overall; 100x on repeated snapshots** — the dominant cost in real agent loops (polling, multi-step forms, state confirmation).

Real-site benchmark (7 scenarios × 5-7 verifiable sub-tasks each: Baidu/Bing/DuckDuckGo search, Wikipedia reading, Hacker News, GitHub browsing — [docs/bench/realworld.md](https://github.com/paipaipai666/nexus-browser-mcp/blob/HEAD/docs/bench/realworld.md)): **sub-task completion 37/42 vs 31/42**, **18.2k vs 566.4k tokens (31x)**, wall-clock **107s vs 179s** — one Wikipedia article snapshot alone costs pw-mcp ~252k tokens where nexus caps + diffs. Enterprise task suite (filter/sort, dashboard reading, KB answers, multi-step ordering, price comparison — [docs/bench/enterprise-ops.md](https://github.com/paipaipai666/nexus-browser-mcp/blob/HEAD/docs/bench/enterprise-ops.md)): **21/21 on all three servers (vs playwright-mcp and chrome-devtools-mcp); tokens 3.6k vs 5.1k vs 10.9k**. At scale (**106 cases / 184 sub-tasks, three servers, seeded deterministic fixtures** — [docs/bench/scale-ops.md](https://github.com/paipaipai666/nexus-browser-mcp/blob/HEAD/docs/bench/scale-ops.md)): **completion 184/184 vs 180/184 vs 178/184; tokens 153.5k vs 180.5k vs 231.1k (1.00 : 1.18 : 1.51)** — the competitor gaps are stable zeros (richtext-iframe writes, download observability, right-click), not noise.

Element-identification accuracy on adversarially complex pages (duplicate-name button grids, shadow DOM, iframes, hidden/disabled traps, full re-render — [docs/bench/element-acc.md](https://github.com/paipaipai666/nexus-browser-mcp/blob/HEAD/docs/bench/element-acc.md)): **nexus 92% = pw 92% < chrome-devtools-mcp 100%, with cdt also cheapest** — its verbose per-node dump carries product texts that lean snapshots drop, which value-based targeting needs. We publish this as a known tradeoff of text clipping: total-token savings mean nothing unless per-point-of-accuracy cost holds, and this suite is the canary (fix direction benchmarked: attach sibling-text summaries only when same-name candidates conflict).

HITL confirmation closes a loop: any gated call returns `CONFIRMATION_REQUIRED` once; after the user approves in chat, the agent re-calls with `confirmed=true` (applies to HITL rules, `browser_evaluate`, `browser_network_body`).

## HTTP transport (remote / multi-client)

Default is stdio (single client). For remote or multi-client use, run a streamable-HTTP server:

```bash
BROWSER_TRANSPORT=http BROWSER_HTTP_PORT=8817 nexus-browser-mcp
```

Each MCP session gets an isolated `session_id` (isolated contexts per task, as usual). Safety rule: binding a non-localhost address without `BROWSER_HTTP_TOKEN` **refuses to start** — an unauthenticated browser-control port is a footgun; with a token set, requests must send `Authorization: Bearer <token>`.

Streaming content (AI replies etc.): `browser_read(wait_stable=true)` waits for DOM quiet and reads the full text in one call; `browser_read(selector=..., follow=true)` tracks incrementally and returns only new content per call (`full=true` returns the whole buffer). `browser_wait_stable` / `browser_wait_ms` provide event-driven and fixed-duration waiting primitives.

Snapshot diff: a repeated `browser_snapshot` whose tree is node-for-node identical to the last one (refs excluded — Playwright renumbers them per generation) returns a ~120-char `[no change]` notice instead of the full tree, and previously issued refs remain valid via generation chaining; `diff=false` forces a full snapshot. Any real change (content, box, attributes) yields the full snapshot — no partial merges, no stale views.

Most tools accept an optional `task_id` (defaults to a shared `default` task). See usage guides in `docs/` (Chinese).

## Development

```bash
uv venv
uv pip install -e ".[dev]"
python -m pytest tests -q
ruff check src tests
python -m smokes.test_e2e           # real-browser smoke
python -m smokes.test_e2e_interact  # forms + multi-task smoke
python -m smokes.test_e2e_observability  # console/exception/network observability smoke
```

## License

MIT

