Content
# Browser MCP Server
[](https://opensource.org/licenses/MIT)
Operate a real browser's MCP server directly through a Chrome/Edge extension. A lightweight alternative to headless solutions like Playwright, it directly operates on a real browser and naturally supports logged-in pages.
Suitable for any client that supports the MCP protocol (Model Context Protocol), such as various AI programming assistants.
## Architecture
```
MCP Client (any AI programming tool that supports MCP)
│ stdio JSON-RPC
▼
mcp-server.ts
│ WebSocket ws://127.0.0.1:18765
▼
Chrome Extension (service worker)
│ chrome.debugger / tabs / cookies API
▼
Real Browser Page
```
Non-headless, no WebDriver, no Selenium dependency. The page is your daily used browser, with cookies and login state preserved.
## Prerequisites
- Node.js >= 18
- Chrome or Edge browser
## Installation
### 1. Load Chrome/Edge Extension
1. Open Chrome → `chrome://extensions/` (Edge use `edge://extensions/`)
2. Enable "Developer mode"
3. "Load unpacked" → Select `extensions/tmwd-cdp-bridge` directory
Confirm the extension icon appears in the toolbar.
### 2. Install Dependencies
```bash
cd /path/to/browser-mcp
npm install
```
### 3. Start MCP Server
```bash
npx tsx mcp-server.ts
```
See two lines of output to confirm success:
```
[ws] Listening on :18765
[ws] Extension connected
```
The extension's service worker may delay a few seconds to connect, wait for `Extension connected` to appear.
### 4. Configure MCP Client
Register this service in any AI programming tool that supports MCP, pointing `command` to `mcp-server.ts`:
```json
{
"mcpServers": {
"browser": {
"command": "npx",
"args": ["tsx", "/path/to/browser-mcp/mcp-server.ts"],
"enabled": true
}
}
}
```
> Replace `/path/to/browser-mcp` with your actual project path.
Restart the AI programming tool to call all browser tools in the conversation.
## All Tools
| Tool | Parameters | Description |
|------|------------|-------------|
| `connect` | None | Check if the extension is connected |
| `tabs` | None | List all open tabs (id, url, title) |
| `open_tab` | `url`, `active?` | Open URL in a new tab |
| `switch_tab` | `tabId` | Switch to the specified tab |
| `close_tab` | `tabId` | Close the specified tab |
| `navigate` | `url`, `tabId?` | Navigate to URL in the current/specified tab |
| `snapshot` | `tabId?` | Get page visible text content |
| `click` | `selector`, `tabId?` | Click the element that matches CSS |
| `screenshot` | `tabId?` | Take a screenshot, return base64 PNG |
| `evaluate` | `code`, `tabId?` | Execute JS in the page's MAIN world, return result |
| `type` | `text`, `tabId?` | Input text into the focused element (CDP Input.insertText) |
| `fill` | `selector`, `value`, `tabId?` | Fill form fields through CSS selector, trigger input/change events |
| `press_key` | `key`, `tabId?` | Send keyboard keystrokes (Enter / Escape / Tab / ArrowDown, etc.) |
| `wait_for` | `selector`, `timeout?`, `tabId?` | Poll and wait for CSS selector to appear (100ms interval, default 10s timeout) |
### Typical Workflow
```
1. connect → Confirm browser is online
2. tabs → See current tabs
3. navigate(url) → Open target page
4. snapshot → Read page content
5. click(selector) → Click link/button
6. snapshot → Read new content
7. evaluate(code) → Execute custom JS
```
## Best Practices
### 1. Wait for page load after navigation
`navigate` may return before the page finishes loading. Recommend giving AI sufficient context to judge in the tool call.
### 2. Pass tabId
All operations default to the current active tab. If multiple tabs are open, AI will automatically use `tabs` to view available tabs, then pass `tabId` for precise positioning.
### 3. Click selector
Prefer semantic CSS selectors:
```
good: a[href*="/article/"] #search-button
bad: body > div > p > a .c-OMlI
```
### 4. What evaluate is suitable for
- Extract structured data (JSON format tables, lists)
- Trigger page JS functions
- Read page internal state (`window.__INITIAL_STATE__`, etc.)
- Operations that cannot be completed with click (scrolling, hovering, etc.)
Example:
```
evaluate(code: "JSON.stringify([...document.querySelectorAll('a')].map(a => ({text:a.textContent, href:a.href})))")
```
## Notes
- **Extension service worker may sleep**: Chrome MV3's service worker sleeps after ~30s of inactivity. First call adds 1-2s wake-up delay. Server sends keepalive every ~24s to maintain connection
- **CDP免CSP**: `evaluate` uses CDP `Runtime.evaluate`, not restricted by page CSP, no need for `eval`/`Function`. 100-1000x faster than `scripting.executeScript`
- **URL redirection**: Some websites (Baidu, Weibo) have intermediate page jumps, navigate may not return the final URL
- **Single extension instance**: Currently only supports one browser instance connection
## Security Instructions
- WebSocket server only binds to `127.0.0.1:18765`, not exposed to the external network
- `evaluate` tool can execute arbitrary JavaScript in the page's MAIN world, with the same permissions as the page itself
- Any local process that can access the machine can connect to WebSocket and control the browser, ensure the running environment is trusted
## Performance Reference
| Operation | Typical Time Consumption |
|------|-------------------------|
| snapshot | 7-40 ms |
| click | 20-40 ms |
| evaluate | 5-10 ms |
| screenshot | 60-105 ms |
| navigate | 300-3600 ms (network overhead) |
| open_tab | 3-5 ms |
## Contribution
Welcome to submit Issues and Pull Requests. Ensure code style consistency and pass `npm run build` type checking.
## License
MIT — See [LICENSE](./LICENSE) file for details.