Metadata-Version: 2.5
Name: ai-browser-mcp
Version: 0.12.0
Summary: Local MCP server for reading and interacting with web pages through real Chrome.
Author: ywleeo
License: MIT
License-File: LICENSE
Requires-Python: >=3.12
Requires-Dist: httpx<1,>=0.28
Requires-Dist: mcp==2.0.0
Requires-Dist: platformdirs<5,>=4.4
Requires-Dist: pydantic<3,>=2.12
Requires-Dist: readability-lxml<0.9,>=0.8.4.1
Requires-Dist: websockets<16,>=15
Description-Content-Type: text/markdown

# Browser MCP

[![M8ven Verified](https://m8ven.ai/badge/mcp/ywleeo-browser-mcp-1fpz06)](https://m8ven.ai/mcp/ywleeo-browser-mcp-1fpz06)

[English](README.en.md) · [中文](README.md)

> **Let your AI really search the whole web.**
> Any site you can reach with a login — Xiaohongshu, Zhihu, X, Douyin, Bilibili, Reddit, or any
> logged-in site — its content can be captured, and it can be **automated** in the background: no
> headless browser, no reverse-engineering, no cookie theft.

Browser MCP is a local MCP server. It lets **any MCP-capable AI assistant**, inside the session state of
your **real Chrome**:

- **Search and capture the content of any site on the web** — public pages, JavaScript-rendered pages,
  and pages that are **only visible after login**;
- **Automate interactions in a background Chrome window** — click, scroll, type, press keys, select —
  **without switching you away from the page you're on**.

| Built-in search / scraper | **Browser MCP** |
| --- | --- |
| Only gets what a search engine has or what's publicly crawlable | **The whole web — if you can log in and reach it, it can be captured** |
| Can't reach logged-in content | **Uses your logged-in state and takes it directly** |
| Mostly read-only | **Can click, scroll, type, press keys, select** |
| Breaks because it depends on reverse-engineered APIs the moment the site changes | **Drives the real UI and reads the real rendered DOM — resilient to redesigns** |
| Easily blocked by anti-bot, leaks cookies | **No reverse-engineering, extension never requests `cookies`, downloads verified by SHA-256, side-effect actions need confirmation** |
| Only works with a couple of agents | **Standard MCP: Codex / Claude Desktop / Cursor / Claude Code… all work** |

**Fully compliant**: it doesn't reverse-engineer internal APIs, bypass CAPTCHAs, steal cookies, or
bulk-scrape — it behaves exactly like a normal logged-in user browsing. Your login state is only used
normally inside your own Chrome; it's never sent back or returned through MCP results.

## What you can do

In one line: **if you can reach it with a login, it can be captured.** Your AI can search, collect, and
operate on any site — Zhihu, Xiaohongshu, X, Douyin, Bilibili, Reddit and beyond — using your logged-in
state to get the platform's own data.

- **Search directly inside the real platforms**: Zhihu, Xiaohongshu, X, Douyin, Bilibili, Reddit — with your logged-in state, getting the platforms' own data, not your agent's built-in search.
- **Read any page**: article body, visible text, JS-rendered content, data returned by page requests, and content that's **only visible after login**.
- **Drive a page in the background**: get a screenshot plus numbered actionable elements in a background Chrome window, then keep clicking, scrolling, typing, pressing keys, and selecting options — without leaving the tab you're on.
- **Zhihu**: search, questions, answers, articles, answer invitations.
- **Xiaohongshu**: search, an account's posts, note details, **full comments (resumable)**, like/collect, image & video download.
- **Douyin**: search, video/image-post details, **full comments (resumable)**, like/collect, image & video download.
- **Bilibili**: video search, content metadata, multi-part info, video or audio-only download.
- **X (Twitter)**: post search, post details.
- **Reddit**: post search, post details, comments.
- **Search**: Google, Bing, Sogou.

Current version is `0.12.0`. See [CHANGELOG.md](CHANGELOG.md) for release notes.

Final actions that affect external state — like, collect, publish, send, buy, delete — should be
confirmed with the user before executing. The extension uses the current Chrome Profile's login
state but **never** returns or persists cookies over MCP.

---

## Quick start

### 1. Requirements

- Python 3.12+
- [uv](https://docs.astral.sh/uv/)
- Google Chrome

### 2. Install

```bash
git clone https://github.com/ywleeo/browser-mcp.git
cd browser-mcp
uv sync
```

(`/path/to/browser-mcp` below is the absolute path to this repo on your machine.)

### 3. Load the Chrome extension

1. Call `browser_status` and take the `extension_dir` from the result.
2. Open `chrome://extensions`.
3. Turn on **Developer mode**.
4. Click **Load unpacked**.
5. Pick the directory returned by `extension_dir`.
6. Call `browser_status` again.

A successful connection returns:

```json
{
  "state": "connected",
  "connected": true,
  "bridge_port": 17880,
  "server_version": "0.12.0",
  "install_mode": "source",
  "project_root": "/path/to/browser-mcp",
  "source_commit": "<git-commit>",
  "upgrade_check_command": "uv --directory /path/to/browser-mcp run browser-mcp upgrade --check --json",
  "upgrade_apply_command": "uv --directory /path/to/browser-mcp run browser-mcp upgrade --apply --json"
}
```

`bridge_port` may also fall in `17880..17889`. The extension normally only needs to be loaded once
and reconnects automatically when the MCP server starts. After updating the extension, click
**Reload** in `chrome://extensions` if it doesn't pick up automatically.

### 4. Connect your AI client

**Codex**

```bash
codex mcp add browser_mcp -- \
  uv --directory /path/to/browser-mcp run browser-mcp
```

Or write the Codex MCP config directly:

```toml
[mcp_servers.browser_mcp]
command = "uv"
args = [
  "--directory",
  "/path/to/browser-mcp",
  "run",
  "browser-mcp",
]
```

Restart Codex after adding or editing the config. Once connected, use the [MCP tools](#mcp-tools).
To remove it: `codex mcp remove browser_mcp`.

**Claude Desktop**

Add this to your Claude Desktop config file:

```json
{
  "mcpServers": {
    "browser-mcp": {
      "command": "uv",
      "args": [
        "--directory",
        "/path/to/browser-mcp",
        "run",
        "browser-mcp"
      ]
    }
  }
}
```

Save and restart Claude Desktop.

### 5. Start using it

Once connected, just describe what you want in plain language:

> “Search Zhihu for answers about MCP.”
> “Download all images from this Xiaohongshu note to `/abs/path`.”
> “Read this Douyin video's content and comments.”
> “Open this page, fill the search box from the screenshot, and click search.”

More copy-paste examples are in [Examples](#examples).

---

## Reference

### Upgrading

Source installs provide a safe upgrade command an agent can run directly. First check the version
and repo state:

```bash
uv --directory /path/to/browser-mcp run browser-mcp upgrade --check --json
```

Then apply if it's safe:

```bash
uv --directory /path/to/browser-mcp run browser-mcp upgrade --apply --json
```

The updater only accepts Git branches that have an `upstream`, and follows these safety rules:

- Refuses to upgrade if the worktree has untracked or uncommitted files.
- Refuses to upgrade if local and remote have diverged; no implicit merge commit is created.
- Only updates source with `git pull --ff-only`.
- Syncs locked dependencies with `uv sync --frozen`, never touching `uv.lock`.

After `--apply` succeeds and returns `restart_required: true`, have the client reconnect the MCP
server — start a new task or reconnect it in Codex; only restart the client if it can't reconnect on
its own. A fresh MCP server refreshes the extension bundle, and the already-loaded Chrome extension
auto-reloads by build ID.

Agents don't need to guess the project path: call `browser_status` and use the returned
`upgrade_check_command` / `upgrade_apply_command`. If installed from a wheel or another package
manager, `install_mode` is `"package"`, so upgrade with your original installer instead of mutating
an arbitrary Git repo.

### Extension permissions

- `<all_urls>`: to open public HTTP(S) pages explicitly requested by the caller, and to support the
  per-site adapters. It never crawls browsing history on its own.
- `debugger`: to capture page request responses and to send trusted browser input events in comment
  streams and visual interaction.
- `tabs`, `scripting`: to manage isolated background tabs and run the bundled, fixed extraction scripts.
- `storage`, `alarms`: for local pairing config and MV3 service-worker keep-alive.

The extension does **not** request the `cookies` permission. Login state is only used normally by
the target page inside the current Chrome Profile; cookies are never returned through MCP tool
results.

Zhihu, Xiaohongshu, Douyin, X and Reddit tools check the platform login state of the current Chrome
Profile before doing anything:

- Logged in: proceed.
- Not logged in: stop and return the platform's login URL, and the client prompts the user to log in.
- Can't tell: stop, to avoid touching protected content when the login state is ambiguous.

Login state isn't cached. Once you finish logging in in Chrome, just retry the original request.

### Media download

`xhs_download` and `douyin_download` accept these common parameters:

- `media`: choose `images`, `video`, or `all`.
- `output_dir`: optional absolute dir; defaults to `downloads` under the Browser MCP data dir.
- `overwrite`: default `false`; a same-named file gets a new name unless explicitly set.
- `max_file_mb`: per-file size cap, default 1024 MiB.

Before downloading, the tool reads the post detail through the current Chrome login state, then
validates the page-derived media URL against platform CDN allowlists, public addresses, hop-by-hop
redirects, and the response media type. Files stream to a `.part` temp file and atomically land on
disk; the result includes the final path, byte count, Content-Type, and SHA-256.

`bilibili_download_video` and `bilibili_download_audio` share the same absolute-dir, overwrite, and
size rules. Bilibili usually returns separate DASH video/audio tracks: the video tool losslessly
muxes them into MP4 via `ffmpeg` when available, and returns the two tracks as separate files when
it isn't, rather than pretending to have a complete video. The audio-only tool keeps the most
compatible track. For multipart videos, pass `?p=N` in the URL.

### Comment completeness & resumable collection

`xhs_comments` and `douyin_comments` scroll the comment stream, expand replies, and watch the page's
own signed pagination requests. In the result, `complete` means every discovered comment stream
reached its terminal page; `limit_reached` means `max_comments` truncated the collection;
`pages_fetched` and `scrolls` help diagnose the run.

A popular post's comment stream can take minutes to scroll — longer than any MCP client is willing
to wait in one call — so a single call doesn't try to finish: when `time_budget_seconds` (default
40s) elapses the collection **suspends instead of failing**, returning the newly gathered comments and
a `session_id`. Call again with the same `url` plus that `session_id` to resume from where it
stopped, without re-gathering already-collected comments:

- `budget_exhausted` means the run wrapped up at the budget; the data is complete and usable, just not
  fully collected yet.
- A non-empty `session_id` means you can resume; an empty one means it's done (finished, hit the cap,
  or the stream bottomed out).
- `collected_total` is the session's cumulative count; compare with `total` to gauge progress.
- Each call's `items` holds only the **newly** collected comments; merge them yourself.

A suspended session keeps a background collection window that closes after 5 minutes idle, after
which an old `session_id` is invalid and you must start over. Raise `time_budget_seconds` to resume
fewer times, but make sure the MCP client's single-call timeout (commonly 60s) leaves room.

### Like & collect

`xhs_like`, `xhs_collect`, `douyin_like`, and `douyin_collect` take a post `url` and the desired
`enabled` state (default `true`). The tool reads the current state, clicks once only if it's out of
sync, then polls to verify; passing the same state again doesn't toggle it back. Pass `enabled=false`
to unlike or uncollect.

These four tools modify external account state for the current Chrome Profile. The MCP client must
get the user's explicit confirmation immediately before each call; the tool won't auto-retry a click
that didn't verify.

### Extension troubleshooting

- `state: disconnected`: confirm the extension is enabled, then click **Reload** on its detail page.
- Port in use: the service auto-tries `17880..17889`; trust the `bridge_port` in the status result.
  It monitors the real MCP host behind `uv` and frees the listening port automatically when the host
  exits, so the agent doesn't have to guess at and kill other processes.
- Changed extension dir: rely on the latest `extension_dir` from `browser_status`.
- Never share `pairing.json` or `pairing-token`; they contain local connection credentials.

A special process supervisor can take the host PID via `BROWSER_MCP_OWNER_PID`; set it to `0` only to
disable host-liveness monitoring. Normal Codex, Claude, or CLI configs don't need this variable.

### MCP tools

These tools are called automatically by any MCP-capable client. In daily use just describe your goal;
you don't need to fill in parameters by hand.

| Tool | Scope | What it does |
| --- | --- | --- |
| `browser_status` | connection | Checks whether the MCP server and Chrome extension are connected, and returns server version, install mode, source commit, and runnable upgrade commands. |
| `browser_read` | general | Opens a page in real Chrome and reads article body, visible text, JS-rendered content, and data returned by page requests; can use the current Chrome login state. |
| `browser_read_page` | general | Continues reading paginated content while keeping the same page snapshot as the first read. |
| `browser_snapshot` | interact | Opens a page in a background Chrome window that shares the current login state, without leaving the user's page; returns a viewport screenshot, visible text, and numbered actionable elements (buttons, links, inputs). With no URL, observes the current page. |
| `browser_click` | interact | Sends one trusted Chrome click by the element's number in the current screenshot first; falls back to click by pixel coordinate when no element is identifiable. Screenshot coords are mapped to the CSS viewport; pass `coordinate_space=viewport` explicitly if needed. Returns a new screenshot and element numbers. |
| `browser_scroll` | interact | Scrolls the page up/down/left/right, or brings a given element into view. |
| `browser_type` | interact | Fills, appends, or replaces text in an input or editable area and returns the resulting state; passwords never appear in the element info. |
| `browser_press` | interact | Sends common keyboard actions: Enter, Escape, Tab, arrow keys, PageUp/Down, Home, End. |
| `browser_select` | interact | Picks an option in a native dropdown and returns the resulting state. |
| `site_login_status` | login | Checks whether the current Chrome Profile is logged into Zhihu, Xiaohongshu, Douyin, X, or Reddit; only checks session state, runs no platform task, and returns no cookies. |
| `zhihu_search` | Zhihu | Searches Zhihu's combined content, answers, articles, or questions; gets titles, authors, summaries, engagement data, and original links. |
| `zhihu_content` | Zhihu | Reads the body of a Zhihu question, answer, or article — good for summarizing, extracting opinions, or deeper analysis. |
| `zhihu_invitations` | Zhihu | Lists answer invitations for the logged-in account, including inviter, related question, time, and source. |
| `xhs_search` | Xiaohongshu | Searches Xiaohongshu notes, sorted by general / newest / hottest; returns titles, authors, covers, and engagement info. |
| `xhs_note` | Xiaohongshu | Reads a single image/video note's title, body, author, publish time, engagement data, and image/video URLs. |
| `xhs_like` | Xiaohongshu | Sets a note to the desired liked/unliked state; confirm before calling; re-sending the same state doesn't toggle it back. |
| `xhs_collect` | Xiaohongshu | Sets a note to the desired collected/uncollected state; confirm before calling; re-sending the same state doesn't toggle it back. |
| `xhs_download` | Xiaohongshu | Streams a note's images, video, or all media to a local dir; defaults to `downloads` under the Browser MCP data dir, or takes an absolute path. |
| `xhs_comments` | Xiaohongshu | Scrolls the note's own comment stream and expands replies, deduping by comment ID; returns incremental comments within a time budget, with a resumable `session_id` if incomplete. |
| `xhs_user_notes` | Xiaohongshu | Lists notes published by the logged-in account or a given account; can page through and dedupe, showing title, publish time, cover, likes, pinned status, and note link. |
| `douyin_search` | Douyin | Searches Douyin video/image posts; gets ID, description, author, publish time, cover, and engagement counts. |
| `douyin_video` | Douyin | Reads a single Douyin video/image post's author, body, publish time, engagement data, media URLs, and music. |
| `douyin_like` | Douyin | Sets a post to the desired liked/unliked state; confirm before calling; re-sending the same state doesn't toggle it back. |
| `douyin_collect` | Douyin | Sets a post to the desired collected/uncollected state; confirm before calling; re-sending the same state doesn't toggle it back. |
| `douyin_download` | Douyin | Streams a video/image post's media to a local dir; supports images only, video only, or all. |
| `douyin_comments` | Douyin | Scrolls the post's actual comment stream and expands replies, deduping by comment ID; returns incremental comments within a time budget, with a resumable `session_id` if incomplete. |
| `bilibili_search` | Bilibili | Searches videos sorted by general, plays, newest, danmaku, or favorites; returns title, author, duration, tags, stats, and canonical BV links. |
| `bilibili_video` | Bilibili | Reads a BV/AV video's title, intro, author, publish time, engagement stats, tags, and all multipart info; supports `?p=N`. |
| `bilibili_download_video` | Bilibili | Downloads the best compatible video+audio for a video or part; muxes losslessly to MP4 with FFmpeg, otherwise returns two tracks explicitly. |
| `bilibili_download_audio` | Bilibili | Downloads only the best compatible audio track of a video or part, saved as a directly playable audio file. |
| `x_search` | X | Searches X posts (top or latest) and gets author, body, publish time, engagement, media, and links; uses the current Chrome X login state. |
| `x_post` | X | Reads a single X post's body, author, publish time, replies, retweets, likes, views, media, and external links. |
| `reddit_search` | Reddit | Searches Reddit posts by relevance, hot, top, new, or comments; gets subreddit, author, score, comment count, and post link. |
| `reddit_post` | Reddit | Reads a Reddit post's body/media and the comments already loaded on the page, with author, time, score, and depth. |
| `google_search` | Google | Searches the web with Google; gets titles, URLs, sites, and snippets. |
| `bing_search` | Bing | Searches the web with Bing; gets titles, target URLs, sites, and snippets. |
| `sogou_search` | Sogou | Searches the web with Sogou; returns original site links, titles, sites, and snippets; excludes Sogou's own site nav and clearly-advertised results. |
| `site_read_page` | Zhihu etc. | Continues reading paginated content for long answers/articles without revisiting the page. |

### Examples

Just ask an MCP-capable client in plain language:

- “Read this page and summarize the key points.”
- “Open this page, fill the search box from the screenshot, and click search.”
- “Scroll down and click the contact-us button.”
- “Download all images from this Xiaohongshu note to `/abs/path`.”
- “Download this Douyin video and return the path and SHA-256.”
- “Check whether I'm logged into Xiaohongshu.”
- “Search Zhihu for answers about MCP.”
- “Read today's Zhihu answer invitations.”
- “Search Xiaohongshu for recent camping notes.”
- “Read this Xiaohongshu note's body and images.”
- “Get all comments and replies on this Xiaohongshu note.”
- “Like and collect this Xiaohongshu note.” (the client confirms before calling)
- “List every post my Xiaohongshu account has published.”
- “Search Douyin for posts about '牵手 APP'.”
- “Read this Douyin video's content and comments.”
- “Unlike this Douyin post.” (the client confirms before calling)
- “Search Bilibili for videos about OpenAI, and read the first video's meta.”
- “Download this Bilibili video, and also extract an audio-only track.”
- “Search X for the latest posts about OpenAI.”
- “Read this X post's body and engagement data.”
- “Search Reddit for high-scoring posts about MCP.”
- “Read this Reddit post and the first 20 comments.”
- “Search for Browser MCP on Google, Bing, and Sogou.”

Each page change produces a fresh set of element numbers; the agent should use the numbers from the
latest screenshot.

### Running directly

To start the stdio server manually:

```bash
uv run browser-mcp
```

The process waits for MCP JSON-RPC on stdin; no output in the terminal is normal.

## License

[MIT](LICENSE)
