Skip to main content
Glama
ton-to-ton

Real Browser MCP

by ton-to-ton
README.md
# Real Browser MCP

Browser automation over MCP using a real Chrome-family browser and your existing profile.

This project is based on the upstream browser-bridge architecture:

```
MCP Server <-> Local Broker (unix socket) <-> Native Host <-> Chrome Extension
```

It keeps the parts that made the upstream bridge reliable:

- Chrome extension for real tabs and real profiles
- Native Messaging instead of exposing a WebSocket port
- Local broker with per-session tab ownership
- CLI installer for extension + native host setup

The main entrypoint is a stdio MCP server, so Codex and other stdio-capable MCP clients can load the same browser tools directly.
The server stays stdio-only, but now also exposes MCP `resources/*` and `prompts/*` for discovery-oriented clients.

The product focus is browser RPA over your real Chrome profile:

- logged-in business sites
- real downloads, cookies, and storage state
- repeatable list/detail/download/extract workflows
- deterministic run records that can be resumed or inspected later

## Status

Implemented in this repo:

- Chrome extension
- Native host
- Local broker
- stdio MCP server
- CLI commands for install, status, tools, self-test, and MCP serving
- Extension-backed self-test coverage for screenshot, download, structured extract, task runner, and network mock flows

## Install

1. Install dependencies if you want to use agent mode later:

```bash
npm install
```

This only installs dependencies. The browser/native-host setup stays behind the explicit `npx real-browser-mcp install` step.

2. Install the extension and native host.

If you are running inside WSL and want to automate native Windows Chrome, use the Windows runtime explicitly:

```bash
npx real-browser-mcp install --runtime-target windows
```

This is the standard WSL path. The installer places the runtime under `C:\Users\<you>\.real-browser-mcp\` and auto-installs a portable Windows `node.exe` there if Windows Node is missing. See `WSL Quick Start` below for the full flow.

If you want a local runtime instead:

```bash
npx real-browser-mcp install
```

From WSL, `npx real-browser-mcp install --runtime-target local` keeps the runtime inside WSL/Linux instead of Windows.

3. Load the unpacked extension in Chrome:

- Open `chrome://extensions`
- Turn on `Developer mode`
- Click `Load unpacked`
- Select the extension directory printed by the installer

If you want native isolated contexts (`browser_context_*` on the native backend), open the extension details page and enable `Allow in Incognito`.

When the extension prompts for permissions, grant site access and the optional runtime permissions used by the current flow:

- `nativeMessaging`
- `downloads`
- `debugger`
- `cookies`
- `<all_urls>`

After `npx real-browser-mcp update`, reload the extension from `chrome://extensions` so Chrome picks up the refreshed files from the runtime path printed by the updater.

You can verify the native backend is connected with:

```bash
npx real-browser-mcp tool browser_status --backend native
```

From WSL, add `--runtime-target windows` if you want to probe the Windows runtime explicitly.

`hostConnected: true` means the extension reached the native host through the broker.

4. Register the MCP server with your MCP client.

For Codex:

```bash
codex mcp add real-browser-mcp -- ~/.real-browser-mcp/mcp-wrapper.sh
```

For OpenCode, add this to `~/.config/opencode/opencode.json` or `~/.config/opencode/opencode.jsonc`:

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "real-browser-mcp": {
      "type": "local",
      "command": ["/Users/<you>/.real-browser-mcp/mcp-wrapper.sh"],
      "enabled": true
    }
  }
}
```

If `opencode` is on your `PATH`, `npx real-browser-mcp install` and `npx real-browser-mcp update` will create or refresh that MCP entry automatically. If you use a custom OpenCode config file, set `OPENCODE_CONFIG` before running those commands. Existing `opencode.json` and `opencode.jsonc` files are both detected.

If you are developing inside this repo, the checked-in `opencode.json` already points OpenCode at the local `node bin/cli.js mcp` entrypoint, so launching OpenCode from the repo root picks it up automatically.

5. Restart your MCP client.

The installer copies the unpacked extension into the selected runtime directory, registers the Native Messaging host, refreshes the global OpenCode MCP entry when `opencode` is on your `PATH` (or an OpenCode config already exists), and can prompt you to add the Codex MCP entry automatically if `codex` is on your `PATH`.
It also installs a stable MCP wrapper under `~/.real-browser-mcp/`, so MCP clients do not depend on a transient `npx` cache path.
If you already have a `~/.codex-browser/` install, `npx real-browser-mcp update` migrates or merges it into the selected runtime directory. After that migration, open `chrome://extensions` once and load the unpacked extension again from the path printed by the updater.

## WSL

### WSL Quick Start

For the default WSL-to-Windows Chrome setup, use the Windows runtime explicitly:

```bash
npx real-browser-mcp install --runtime-target windows
```

The standard WSL flow is now:

1. The installer sets up the real browser runtime under `C:\Users\<you>\.real-browser-mcp\`
2. If Windows does not already have `node.exe`, the installer downloads and installs a portable official Node.js build under `C:\Users\<you>\.real-browser-mcp\portable-node\`
3. Windows Chrome loads the unpacked extension from `C:\Users\<you>\.real-browser-mcp\extension`
4. WSL-side MCP clients keep using `~/.real-browser-mcp/mcp-wrapper.sh`

No manual `REAL_BROWSER_MCP_WINDOWS_NODE` setup is required for the default path.

After loading the extension in Windows Chrome, verify the bridge from WSL:

```bash
npx real-browser-mcp tool browser_status --backend native --runtime-target windows --json
```

Expect `hostConnected: true` and `nativeReady: true`.

When you run the CLI from WSL, the first `install` or `update` will ask which runtime target to use:

- `Windows native Chrome` (recommended, default)
- `Linux/WSL local runtime`

In Windows-target mode:

- the real browser runtime is installed under your Windows profile
- the installer prefers a Windows `node.exe` on `PATH`, but automatically bootstraps a portable Windows Node.js under `C:\Users\<you>\.real-browser-mcp\portable-node\` when Windows Node is missing
- Windows Chrome loads the unpacked extension from the Windows path printed by the installer
- the WSL-side `~/.real-browser-mcp/mcp-wrapper.sh` becomes a proxy that forwards OpenCode/Codex into the Windows runtime
- `npx real-browser-mcp tool ... --runtime-target windows` and `npx real-browser-mcp self-test --runtime-target windows` also use that same Windows runtime

That means a separate global Node.js install on Windows is optional for the WSL flow.

If you want to override the default Windows-side paths, these environment variables are available:

- `REAL_BROWSER_MCP_WINDOWS_NODE`: explicit Windows `node.exe` path
- `REAL_BROWSER_MCP_WINDOWS_BASE_DIR`: explicit Windows runtime base directory
- `REAL_BROWSER_MCP_WINDOWS_USERPROFILE`: explicit Windows profile directory used to derive the runtime base directory

The default WSL flow no longer requires setting `REAL_BROWSER_MCP_WINDOWS_NODE` just to get started.

You can switch explicitly at any time:

```bash
npx real-browser-mcp install --runtime-target windows
npx real-browser-mcp update --runtime-target local
```

### Agent Mode via TCP Gateway (WSL → Windows native browser)

`agent-browser` normally communicates over a Unix socket. When the MCP server
runs in WSL but the real browser (and `agent-browser` daemon) runs on Windows,
the `agent-gateway` bridges the two over TCP.

**Starting the gateway on Windows:**

```bash
# On the Windows side (PowerShell or cmd), with a shared secret:
set REAL_BROWSER_MCP_AGENT_GATEWAY_TOKEN=<your-secret>
node C:\Users\<you>\.real-browser-mcp\node_modules\.bin\real-browser-mcp agent-gateway
```

Or via the CLI (generates and persists a token automatically in
`~/.real-browser-mcp/config.json`):

```bash
npx real-browser-mcp agent-gateway
```

The gateway defaults to `127.0.0.1`. To bind to a non-loopback interface so
WSL can reach it, set the host explicitly **and** provide a token:

```bash
# On Windows — bind to all interfaces, require token auth
set REAL_BROWSER_MCP_AGENT_GATEWAY_HOST=0.0.0.0
set REAL_BROWSER_MCP_AGENT_GATEWAY_TOKEN=<your-secret>
node bin/agent-gateway.cjs
```

**Security invariant:** The gateway refuses to start if the resolved host is
non-loopback (`0.0.0.0`, an explicit IP, etc.) and no token is configured.
This prevents accidental unauthenticated LAN exposure.

**Connecting from WSL:**

```bash
# On the WSL side — point the agent backend at the Windows gateway
export REAL_BROWSER_MCP_AGENT_HOST=<windows-ip>   # e.g. 172.x.x.x
export REAL_BROWSER_MCP_AGENT_GATEWAY_TOKEN=<your-secret>
npx real-browser-mcp backend agent
npx real-browser-mcp tool browser_status
```

The token value set in `REAL_BROWSER_MCP_AGENT_GATEWAY_TOKEN` must be identical
on the Windows gateway side and the WSL client side. The MCP server sends an
auth handshake line before any protocol traffic so the gateway can verify the
client before proxying.

> **Fallback:** `OPENCODE_BROWSER_AGENT_GATEWAY_TOKEN` and
> `OPENCODE_BROWSER_AGENT_GATEWAY_HOST` are accepted as fallbacks for backward
> compatibility, but `REAL_BROWSER_MCP_*` names take precedence and are the
> canonical names to use.

## Development

For maintainer work in this repo, rebuild the runtime payload after changing files in `src/`:

```bash
npm run build
node build-cws-package.mjs
```

`src/*.ts` changes are not live until `dist/*.js` is rebuilt.

For sandboxed or test runs, you can redirect mutable runtime state without changing the installed broker/native-host path:

```bash
REAL_BROWSER_MCP_STATE_DIR=/tmp/real-browser-mcp-state node bin/cli.js tool browser_status --backend native
```

This moves contexts, tasks, RPA runs, logs, and artifacts under the override directory while still talking to the normal installed browser bridge.

## Usage

After registration, your MCP client gets these tools:

- `browser_status`
- `browser_get_tabs`
- `browser_open_tab`
- `browser_close_tab`
- `browser_navigate`
- `browser_query`
- `browser_click`
- `browser_hover`
- `browser_type`
- `browser_press_key`
- `browser_select`
- `browser_check`
- `browser_uncheck`
- `browser_focus`
- `browser_blur`
- `browser_drag`
- `browser_evaluate`
- `browser_handle_dialog`
- `browser_scroll`
- `browser_wait`
- `browser_screenshot`
- `browser_snapshot`
- `browser_download`
- `browser_list_downloads`
- `browser_set_file_input`
- `browser_highlight`
- `browser_console`
- `browser_errors`
- `browser_context_list`
- `browser_context_create`
- `browser_context_use`
- `browser_context_dispose`
- `browser_cookies_get`
- `browser_cookies_set`
- `browser_cookies_clear`
- `browser_storage_state_export`
- `browser_storage_state_import`
- `browser_emulate`
- `browser_network_list`
- `browser_route`
- `browser_extract`
- `browser_task`
- `rpa_templates_list`
- `rpa_runs_list`
- `rpa_run_get`
- `rpa_run_cancel`
- `rpa_run`
- `browser_trace_start`
- `browser_trace_stop`
- `browser_har_start`
- `browser_har_stop`
- `browser_video_start`
- `browser_video_stop`

`browser_status` now includes backend selection metadata for the active backend, including `backendPreference`, `backendSource`, `selectionReason`, `backendRuntime`, permission state, route/mock support, and whether isolated contexts and artifact capture are available.

For the native backend, use `nativeReady` / `basicNativeReady` plus `statusSummary` to tell whether normal profile-backed browser tools are usable. `foregroundNativeReady` and `rpaExecutionReady` are narrower and cover foreground-window-only flows such as `rpa_run`, although `rpa_run` will now try to bring a regular Chrome window to the foreground automatically before failing. `rpaReady` is still returned as a compatibility alias for `rpaExecutionReady`. `browser_status` also reports `runtimeReady`, `runtimeStatusError`, `selectionRefreshedAt`, `regularHostSelectionSource`, and `ffmpeg` so you can tell whether the status came from a fresh probe and why video support is or is not available.

The MCP surface also includes text-based resources and prompts:

- Resources:
  - `real-browser-mcp://server/info`
  - `real-browser-mcp://server/capabilities`
  - `real-browser-mcp://runtime/status`
  - `real-browser-mcp://runtime/contexts`
  - `real-browser-mcp://runtime/tabs`
  - `real-browser-mcp://runtime/tasks`
  - `real-browser-mcp://runtime/rpa-runs`
- Resource templates:
  - `real-browser-mcp://runtime/tasks/{taskId}`
  - `real-browser-mcp://runtime/rpa-runs/{runId}`
  - `real-browser-mcp://runtime/tabs/{tabId}/snapshot`
  - `real-browser-mcp://runtime/tabs/{tabId}/console`
  - `real-browser-mcp://runtime/tabs/{tabId}/errors`
  - `real-browser-mcp://runtime/tabs/{tabId}/network`
- Prompts:
  - `inspect_page`
  - `debug_page`
  - `download_from_page`
  - `capture_artifacts`
  - `run_browser_task`

The resource layer is read-only and text-based. Prompts are runbooks that tell an MCP agent which `browser_*` tools to call; they do not execute tools by themselves.

For local debugging without an MCP client session:

```bash
npx real-browser-mcp tools
npx real-browser-mcp tool browser_status
npx real-browser-mcp tool browser_status --json
npx real-browser-mcp self-test
```

For read-only dataset collection against a live tab, use the snapshot collector script:

```bash
npm run collect:snapshot -- --config examples/hyperliquid-snapshot-config.json
```

For repeated capture windows, use `count` + `intervalMs`:

```bash
npm run collect:snapshot -- --config examples/hyperliquid-sequence-config.json
```

The sequence example now expects a real strategy event channel. Emit events into the local bridge first:

```bash
node scripts/runtime-event-bridge.mjs emit \
  --channel hyperliquid-hype-usdc \
  --event-json '{"source":"strategy-runtime","type":"breakout_candidate","symbol":"HYPE/USDC","signalContext":"sequence-monitor","confidence":0.82}'
```

Then the collector can resolve the latest event for that channel on each capture.

You can override labels per capture session:

```bash
npm run collect:snapshot -- \
  --config examples/hyperliquid-snapshot-config.json \
  --symbol HYPE/USDC \
  --label fake-breakout \
  --signal-context liquidation-cascade \
  --event-json '{"eventId":"evt-123","source":"strategy-feed","type":"breakout_candidate"}'
```

To auto-attach runtime events on every capture, either point the collector at a JSON file or a command that prints JSON:

```bash
npm run collect:snapshot -- \
  --config examples/hyperliquid-sequence-config.json \
  --event-command 'node scripts/runtime-event-bridge.mjs latest --channel hyperliquid-hype-usdc'
```

For WSL-only or containerized capture without the native-extension or agent-browser backends, the repo also supports a dedicated Chromium CDP collector path. Start the long-lived browser once:

```bash
npm run capture:docker:up
curl http://127.0.0.1:9222/json/version
```

`capture:docker:up` waits until the container-internal CDP endpoint is ready and prints the debugger URL. It then makes a best-effort attempt to expose the same CDP endpoint at `127.0.0.1:$CDP_PORT` inside WSL and reports that result as `hostReachable`. If startup fails, it also prints `docker compose ps` and recent container logs.
When Docker Desktop does not expose the published port back into WSL, the helper starts a local `127.0.0.1:$CDP_PORT` relay. The raw Docker-published port is kept separate (`CDP_PUBLISH_PORT`, default `39222`) so the relay can own `127.0.0.1:$CDP_PORT` inside WSL without colliding with Docker's own port forward.
Standalone `npm run capture:docker:up` now fails if the host-facing `127.0.0.1:$CDP_PORT` endpoint is still unreachable after the relay attempt. The one-shot Docker capture commands still allow host-unreachable mode, including relay-startup failures, because collection runs inside the container.
The default startup path is idempotent and reuses the long-lived browser container. If you need to rebuild the Chromium container after changing launch parameters, run `npm run capture:docker:down` first or set `CDP_FORCE_RECREATE=1`.

Then capture against that CDP endpoint directly:

```bash
npm run collect:snapshot:cdp -- \
  --config examples/hyperliquid-symbols/hype-snapshot-config.json \
  --cdp-url http://127.0.0.1:9222
```

For a single end-to-end run that starts Docker, waits for CDP readiness, and saves a HYPE chart image, you can also run:

```bash
npm run capture:docker:hype
```

This one-shot path runs the collector inside the Docker container, so it does not depend on the host being able to `curl 127.0.0.1:9222`.
The default HYPE preset now expands the viewport, switches the TradingView chart to `1h`, hides matching announcement overlays when they are present, and crops the screenshot to the chart iframe instead of the full trading page.

To switch the preset interval explicitly, run one of:

```bash
npm run capture:docker:hype:5m
npm run capture:docker:hype:1h
npm run capture:docker:hype:1d
```

You can also override the timeframe on any config-driven run:

```bash
npm run capture:docker:hype -- --timeframe 5m
```

If you need a different relay/container port, set `CDP_PORT` for both startup and one-shot capture, for example `CDP_PORT=9333 npm run capture:docker:hype`. If you also want to change the raw Docker-published port, set `CDP_PUBLISH_PORT` as well.

For repeated HYPE/USDC capture with a preset `count` and `intervalMs`, use:

```bash
npm run capture:docker:hype:sequence
```

That preset uses [examples/hyperliquid-symbols/hype-sequence-config.json](/mnt/d/projects/real-browser-mcp/examples/hyperliquid-symbols/hype-sequence-config.json) with `count=60` and `intervalMs=1000`.

If you want the latest finalized HYPE chart artifact in a form that another agent can consume directly, run:

```bash
npm run capture:docker:hype:latest
```

That prints JSON with the latest `chart.png`, `chartAbsolutePath`, `metadata.json`, capture directory, timeframe, and image dimensions so a multimodal model can pick up the newest artifact without scanning the tree manually.
For the shortest agent-facing handoff, see [docs/hyperliquid-chart-analysis.md](/mnt/d/projects/real-browser-mcp/docs/hyperliquid-chart-analysis.md).

If multiple pages or chart tabs are open, the CDP collector reuses a uniquely matching page or opens the configured `openUrl` target when nothing matches. If multiple pages match the configured filters, it stops and asks you to pin the exact page with `--tab-id <targetId>` from `http://127.0.0.1:9222/json/list`.

You can still override label, signal context, output directory, and runtime event inputs (`--event-json`, `--event-file`, `--event-command`) the same way:

```bash
npm run collect:snapshot:cdp -- \
  --config examples/hyperliquid-symbols/btc-snapshot-config.json \
  --cdp-url http://127.0.0.1:9222 \
  --label setup-review \
  --signal-context breakout-monitor
```

The CDP collector keeps the same artifact directory layout as the standard collector (`chart.png`, `metadata.json`, `page-text.json`, `selectors.json`, optional `snapshot.json` and `runtime-event.json`). Its metadata also includes CDP-specific tab targeting details: `metadata.tab.targetId` is the real CDP target for `/json/list` and `--tab-id`, while `metadata.tab.id` / `runtimeEvent.captureContext.tabId` are stable synthetic numeric compatibility keys derived from that target and marked with `idKind` / `tabIdKind = "synthetic-cdp-hash"`. The CDP path is intentionally read-only and aimed at public/chart-state capture rather than real-profile account state.


If your strategy can stream ndjson, you can keep the bridge updated continuously:

```bash
python3 your_strategy.py | node scripts/runtime-event-bridge.mjs ingest-stdin --channel hyperliquid-hype-usdc
```

Each line should be one JSON object such as:

```json
{"source":"strategy-runtime","type":"liquidation_cascade","symbol":"HYPE/USDC","signalContext":"sequence-monitor","confidence":0.91,"emittedAt":"2026-03-18T05:20:00Z"}
```

The collector is intentionally read-only. It saves a screenshot, `browser_snapshot`, `page_text`, and selected DOM regions into a timestamped folder under `artifacts/`. This is useful for chart labeling, vision-model training data, and correlating runtime events with visible chart state.
Keep trading execution on structured exchange APIs or websocket feeds; treat browser UI captures as secondary context rather than the primary source of truth.

For symbol-specific capture folders and presets, see the example configs under `examples/hyperliquid-symbols/` such as `examples/hyperliquid-symbols/hype-snapshot-config.json` and `examples/hyperliquid-symbols/gold-snapshot-config.json`. These presets can carry symbol-specific notes and extra selectors for majors, higher-beta alts, or event-sensitive pairs. The verified chart-focused Docker presets currently cover `BTC`, `ETH`, `SOL`, `SUI`, `XRP`, `SPX`, `WTIOIL`, `S&P500`, `XYZ100`, `GOLD`, `GLD`, `SLV`, `SPY`, `QQQ`, `QQQM`, `AAPL`, `AMZN`, `META`, `MSFT`, `GOOGL`, `HOOD`, `CRCL`, `THBILL`, `TSLA`, `NVDA`, and `HYPE`.

Some requested symbols intentionally resolve to a different live market title:

- `GLD/USDC` -> `XAUT/USDC`
- `SLV/USDC` -> `SILVER (xyz)`
- `SPY/USDC` -> `USA500 (cash)`
- `QQQ/USDC` -> `USTECH100 (km)`
- `QQQM/USDC` -> `USTECH100 (km)`
- `S&P500/USDC` -> `S&P500 (xyz)`

If you override a preset with `--symbol`, pass the same `--symbol` again to `npm run capture:docker:latest` so the artifact lookup filters the same market. If you need the literal route instead of the alias, prefix the symbol with `raw:`, for example `--symbol raw:GLD/USDC`.

For non-trading work, start from one of the generic templates:

```bash
npm run collect:snapshot -- \
  --config examples/generic-site-snapshot-config.json \
  --url-includes your-site.example \
  --title-includes "Target Page" \
  --label site-review \
  --signal-context site-review

npm run collect:snapshot -- \
  --config examples/generic-procurement-snapshot-config.json \
  --url-includes procurement.example.gov \
  --title-includes "Tender" \
  --label tender-review \
  --signal-context deadline-check

npm run collect:snapshot -- \
  --config examples/generic-webapp-snapshot-config.json \
  --url-includes app.example.com \
  --title-includes "Dashboard" \
  --label ui-review \
  --signal-context regression-check

npm run collect:snapshot -- \
  --config examples/generic-social-snapshot-config.json \
  --url-includes social.example.com \
  --title-includes "Feed" \
  --label social-review \
  --signal-context content-review
```

These templates are meant for public procurement portals, internal web apps, and general website review tasks. Replace the target filters and selectors to match the page you care about.
The procurement template is now tuned for real tender-review work: it checks buyer/authority, procedure, reference, published/closing/deadline markers, lot/status cues, documents/attachments, clarification or Q&A sections, and generic listing tables so it works better on both notice detail pages and search result pages. It also includes common Japanese markers such as `締切`, `発注者`, `調達機関`, `公告`, `資料`, `添付`, `質問`, `質問回答`, and `落札`.
When procurement captures use selectors like `downloadLinks`, the dataset builder now emits explicit aggregate features such as `download_link_count_total` and `download_link_present_count`, which makes attachment-heavy pages easier to analyze downstream.
For social-media review work, you can use the generic social template or start directly from `examples/social/x-snapshot-config.json`, `examples/social/instagram-snapshot-config.json`, and `examples/social/threads-snapshot-config.json` for read-only feed/profile/post capture on X, Instagram, and Threads. The social presets now expose explicit `postLinks`/`statusLinks`/`threadLinks` and `mediaImages`/`mediaVideos` selectors, and the dataset builder turns those into aggregate features like `social_post_link_count_total` and `social_media_count_total`.

You can then build a labeled dataset manifest and train a lightweight local baseline classifier:

```bash
npm run build:vision-dataset -- \
  --input-dir artifacts/hyperliquid-snapshots \
  --output-dir artifacts/vision-datasets/hyperliquid-patterns \
  --label-key label

python3 scripts/vision-pattern-classifier.py train \
  --dataset artifacts/vision-datasets/hyperliquid-patterns/train.jsonl \
  --val-dataset artifacts/vision-datasets/hyperliquid-patterns/val.jsonl \
  --model-out artifacts/vision-datasets/hyperliquid-patterns/model.json

python3 scripts/vision-pattern-classifier.py predict \
  --model artifacts/vision-datasets/hyperliquid-patterns/model.json \
  --image artifacts/hyperliquid-snapshots/<session>/<capture>/chart.png
```

The dataset builder now extracts selector-derived features from each capture's `selectors.json`, plus context features from `metadata.json` and `runtime-event.json` such as notes, signal context, runtime event type, and sidecar metrics. The baseline classifier combines those features with the grayscale image vector. `predict` will automatically read sibling `selectors.json`, `metadata.json`, and `runtime-event.json` files next to the image when available.
The dataset builder also normalizes one-off capture labels into reusable canonical labels such as `breakout-long`, `breakout-candidate`, `liquidation-cascade`, `rejection-wick`, `risk-block`, and `setup-review`, while preserving the original label alongside the canonical one in the JSONL records. Train/validation splits are stratified by canonical label and keep at least one training sample per label when possible.
The current classifier is a dependency-free nearest-centroid grayscale-plus-selector baseline. It is intended as a local bootstrap for setup labels such as fake breakout, liquidation cascade, and rejection wick. The current default weights (`image=0.5`, `selector=0.2`, `context=1.25`) are tuned from a small repeated-label sweep on this repo's local dataset, so runtime/context sidecars currently carry more weight than raw pixels. You can later swap it for a stronger local vision stack while keeping the same capture folders and dataset manifest.

For low-level MCP smoke testing, the installed wrapper now responds to `resources/list`, `resources/read`, `prompts/list`, and `prompts/get` in addition to `initialize` and `tools/*`.

`browser_screenshot` also accepts `{"fullPage": true}` for full-page PNG capture, `{"path": "artifacts/page.png"}` to write the PNG directly to disk, and one-shot `width` / `height` / `deviceScaleFactor` overrides for higher-resolution capture.
`npx real-browser-mcp self-test` now verifies:

- hover, focus, key press, checkbox toggle, dialog handling, and drag/drop
- structured extraction for links, cards, list_items, detail_fields, and tables
- deterministic `browser_task` step execution and resume
- built-in `rpa_run` workflows for list/detail/download/extract/save_output
- cookie round-trip and storage state export/import
- debugger-backed network capture plus URL blocking and response mocking
- viewport and color-scheme emulation
- visible screenshot
- full-page screenshot
- direct URL download
- selector-triggered download
- slow download with `wait=true`
- concurrent download association

## Capability Notes

- The default backend preference is `native`: Real Browser MCP uses the Chrome extension backend unless you explicitly opt into `agent` or `auto`.
- Use `npx real-browser-mcp backend native` when you want your existing Chrome profile, signed-in sessions, and real downloads by default.
- Use `npx real-browser-mcp backend agent` when you want isolated `browser_context_*` sessions plus `browser_trace_*`, `browser_har_*`, and `browser_video_*`.
- Use `npx real-browser-mcp backend auto` only when you want the legacy agent-first auto-selection behavior.
- Use `--backend native|agent|auto` on `real-browser-mcp status|tools|tool|mcp` when you want a one-off session override without changing the persisted default.
- `browser_extract` and `browser_task` are backend-neutral layers built on top of the existing primitives, so they work with both the extension backend and the agent backend.
- `browser_extract` supports `text`, `links`, `headings`, `forms`, `tables`, `cards`, `list_items`, `detail_fields`/`pairs`, and `jsonld`. When you pass an explicit selector that matches nothing, it now returns an empty structured result with `selectorMatched: false` instead of silently scraping the whole page.
- `rpa_run` is native-first in v1, targets the real Chrome profile, automatically tries to foreground a regular Chrome window before enforcing the strict RPA binding, uses broker-owned tabs for that prep step, rolls back temporary prep tabs/claims when preflight fails, holds a single-profile lock while the run is active or paused, and its saved runs remain inspectable across later MCP sessions.
- `browser_status` now exposes `nativeReady`, `rpaExecutionReady`, `statusSummary`, `foregroundProfileLabel`, and `rpaLock` so an operator can distinguish general native readiness from stricter RPA readiness. `rpaReady` remains as a compatibility alias.
- `browser_route` now supports `action=block`, `action=mock`, and `action=clear`.
- `REAL_BROWSER_MCP_BACKEND=native|agent|auto` still overrides the persisted default for the current process.

## Commands

```bash
npx real-browser-mcp install
npx real-browser-mcp update
npx real-browser-mcp status
npx real-browser-mcp backend [native|agent|auto]
npx real-browser-mcp uninstall
npx real-browser-mcp mcp
npx real-browser-mcp tools
npx real-browser-mcp tool browser_status
npx real-browser-mcp tool browser_status --json
npx real-browser-mcp self-test
```

## Agent Mode (Opt-In)

The upstream agent-browser backend is still wired through the bundled tool layer, but it is only used when you explicitly select it.

```bash
npx real-browser-mcp agent-install
npx real-browser-mcp backend agent
```

Useful overrides:

- `REAL_BROWSER_MCP_BACKEND=native|agent|auto`
- `REAL_BROWSER_MCP_SESSION_ID`
- `REAL_BROWSER_MCP_HOST_ID`
- `REAL_BROWSER_MCP_AGENT_SESSION`
- `REAL_BROWSER_MCP_AGENT_SOCKET`
- `REAL_BROWSER_MCP_AGENT_AUTOSTART=0`
- `REAL_BROWSER_MCP_AGENT_DAEMON`
- `REAL_BROWSER_MCP_AGENT_HOST`
- `REAL_BROWSER_MCP_AGENT_PORT`

## Uninstall

```bash
npx real-browser-mcp uninstall
codex mcp remove real-browser-mcp
```

Then remove the unpacked extension from `chrome://extensions`.