web-bridge
MCP server that lets AI editors execute JavaScript, read console output, and simulate clicks/input on static web pages. Supports multiple browser pages and can be deployed remotely via HTTP transport.
README
web-bridge-mcp — Let AI editors operate any static web page
web-bridge-mcp is an MCP Server (single Node process, dual interfaces) that lets AI editors execute JavaScript, read the console, and simulate clicks / typing on any static web page that includes client.js. Great for cross-browser, multi-tab local debugging — and it can also be deployed to a public server.
AI Editor ┌───────────────────┐ Browser page
┌──────────────┐ │ MCP Server │ ┌──────────────────┐
│ MCP Client │ │ (Node, 1 process) │ │ <script src= │
│ │ stdio or │ · Iface B: MCP │WebSocket │ :3210/client.js">│
│ AI stops │◄───────►│ (stdio / http) │◄────────►│ client.js │
│ here │ Streamable│ · Iface A: WS │ Iface A │ (eval execution/│
└──────────────┘ HTTP(remote)│ · HTTP /client.js│ │ console capture)│
└───────────────────┘ └──────────────────┘
The AI editor and the browser never talk directly: both connections terminate at the MCP Server (server.js, the relay). The AI manipulates pages indirectly through tool calls.
Quick Start (3 steps)
Whether local or on a public server, the flow is the same: ① start the relay server with node → ② configure its http/https URL in your editor → ③ embed the <script> tag into your static page.
Step 1: Start the MCP relay server
# npm package: web-bridge-mcp — runs directly via npx; if you cloned this repo, run npm install first
# Local use (listens on 127.0.0.1 only by default)
npx web-bridge-mcp --transport http # in this repo: npm run serve
# Deploy to a public server (token is mandatory on the public internet;
# keep it running with systemd / pm2)
npx web-bridge-mcp --transport http --host 0.0.0.0 --port 3210 --token <secret>
Once started there are three endpoints (localhost:3210 for example):
| Endpoint | URL | For |
|---|---|---|
| MCP endpoint | http://127.0.0.1:3210/mcp |
Paste into your editor (step 2) |
| Page script | http://127.0.0.1:3210/client.js |
Embed into your page (step 3) |
| Status page | http://127.0.0.1:3210/ |
Open in a browser to see connected pages |
CLI flags can also be set via environment variables PORT / HOST / TOKEN / TRANSPORT.
Step 2: Configure the relay server URL in your editor
ZCode / Claude Code (project .mcp.json, or claude mcp add), Cursor (.cursor/mcp.json):
{
"mcpServers": {
"web-bridge-mcp": {
"type": "http",
"url": "http://127.0.0.1:3210/mcp"
}
}
}
When deployed on a server (with token), replace the URL with the public one and add the auth header:
{
"mcpServers": {
"web-bridge-mcp": {
"type": "http",
"url": "https://your-domain.com/mcp",
"headers": { "Authorization": "Bearer <secret>" }
}
}
}
Note 1:
typerefers to the MCP transport protocol (http= Streamable HTTP transport;stdio= process pipe, see below) — it is independent of the URL's own protocol. So even if theurlishttps://,typestayshttp: https is simply http over TLS, and there is no separate"https"transport type.Note 2: Claude Desktop only supports the local stdio mode below.
Step 3: Embed the script into your static page
Any page, any port works (CORS is open):
<script src="http://127.0.0.1:3210/client.js"></script>
When deployed on a server, point the script at it (with token if enabled):
<script src="https://your-domain.com/client.js?token=<secret>"></script>
Verify
Tell the AI: "Use web-bridge-mcp's list_pages to see which pages are connected, then eval_js to click #btn and read the console". If it lists your page, all three steps are wired up.
Order doesn't matter: the page can be opened before the server — client.js auto-reconnects (1s→2s→5s→10s backoff) and attaches as soon as the server is up. Hub status page: http://127.0.0.1:3210/
Once embedded, the page shows a small draggable status bubble in the top-right corner (green = connected, yellow = connecting, red = disconnected), so you can always tell the page is bridged to the MCP server and whether the link is alive. Double-click the bubble to see exactly what the MCP server has done to the page — every operation with its natural-language note, success/failure and duration, grouped per page load.
Alternative: local stdio mode (the editor starts the server)
If you only use it locally and don't want to run step 1 manually, put the server path directly in your editor config: the editor spawns it as a child process, MCP runs over the process's stdin/stdout pipe (no URL needed), and the relay is up automatically; the process exits with the editor.
{
"mcpServers": {
"web-bridge-mcp": {
"command": "npx",
"args": ["-y", "web-bridge-mcp"],
"env": { "PORT": "3210" }
}
}
}
(Same format for Cursor / Claude Desktop; if you cloned this repo, use "command": "node", "args": ["/path/to/web-bridge-mcp/server.js"] — see the template mcp.json.)
How to choose:
| HTTP mode (3-step guide above) | stdio mode | |
|---|---|---|
| Who starts server.js | You, manually; can live on a server | The editor, on demand |
| Editor config | Just a URL | command + args |
| Best for | Server deployment, multi-device / shared, remote access | Local instant use; the only option for Claude Desktop |
In both modes the browser side is identical (the <script> from step 3).
Admin Console & Groups (multi-project / shared)
To serve multiple projects at once, or hand each person/editor its own isolated entry point, start in group mode:
npx web-bridge-mcp --transport http --port 3210 --admin <admin-password>
# In this repo: npm run serve:groups (password defaults to 123456; override with the ADMIN_PASSWORD env var)
Open http://127.0.0.1:3210/admin, log in with the admin password, and you can:
- Create groups: each group gets its own secret token plus two ready-to-copy snippets —
- The editor-side MCP JSON (URL points to the group's own
/g/<token>/mcp) - The page-side relay
<script>tag (the group's own/g/<token>/client.js)
- The editor-side MCP JSON (URL points to the group's own
- Human observation window (auto-refresh every 1.5s — the human and the AI see the same live view):
- Connected pages (title / pageId / URL / connected-at)
- Console stream of any page
- AI call log — every action the AI performed via MCP in this group's pages (tool, code, duration, result), so you can audit exactly what the AI did
Groups are fully isolated: an editor connected to group A cannot see or touch group B's pages. Group data persists in data/groups.json (customize with --data <path>; the file contains tokens — never commit it; data/ is already gitignored).
Security & limits: a group token equals full control of that group (arbitrary JS in its pages) — treat it like a password. Generate the admin password with
openssl rand -hex 32; always use TLS on the public internet; in group mode editors can only connect via HTTP URL (stdio unavailable).
MCP Tools
| Tool | Params | Description |
|---|---|---|
list_pages |
— | List connected pages (pageId, title, URL, connected-at) |
eval_js |
code, optional pageId / timeoutMs / note |
Execute arbitrary JS in the page and return the serialized result; await supported; last expression is returned automatically, or use return in a statement block; $ / $$ (querySelector / querySelectorAll) provided |
get_console |
optional pageId / limit |
Read the page's recent console output and uncaught errors |
click |
selector, optional pageId / note |
Find element by CSS selector and click() (scrolls into view first) |
type |
selector / text, optional pageId / note |
Focus, write text, dispatch input / change events (contenteditable compatible) |
get_text |
optional selector (default body), pageId / note |
Read element innerText |
pageId rule: it can be omitted when exactly one page is connected; with multiple pages and no pageId the tool returns an error plus the page list, and the AI retries with the right pageId.
note: a natural-language description of the operation, shown to the human on the page side (in the status-bubble operation log, above the executed code). The AI is instructed to always provide it; click/type/get_text fall back to a short label like click #btn when omitted.
Public Deployment Notes
-
HTTPS pages can only reach
https/wss(mixed-content restriction). Use nginx / caddy as a reverse proxy for TLS termination; client.js auto-detectsX-Forwarded-Proto/X-Forwarded-Hostand generates the correctwss://URL — no extra config needed. caddy example (auto certs):your-domain.com { reverse_proxy 127.0.0.1:3210 } -
With a token enabled,
/mcpaccepts three auth styles:Authorization: Bearer <secret>(recommended; put it in editor headers config),X-Web-Bridge-MCP-Token: <secret>, or the?token=query parameter. -
HTTP transport implements the official Streamable HTTP protocol (stateless): every request is handled independently while sharing the same hub, so multiple editors can connect at once.
-
For public deployments always: set
--token, use TLS, and only open the ports you need in the firewall.
Security Notes
- By default the server listens on
127.0.0.1only. Any web page open on this machine (including third-party sites you browse) can try to connect to the local port — in default no-token mode they could receive AI-sent code and forge results. - On untrusted networks, or when exposing to LAN devices (
--host 0.0.0.0), always enable--token: fetching client.js then requires?token=<secret>, and the first WebSocket packet is verified too.
WebSocket Message Protocol (internal reference)
WS messages between the browser and the MCP Server are JSON text frames; refer to this when working on lib/hub.mjs / client.js:
| Direction | Message | Fields | Notes |
|---|---|---|---|
| page → server | hello |
role:"page", pageId, url, title, ua, token? |
First packet after connect; disconnected if not received within 5s; duplicate pageId (duplicated tab) → new connection replaces the old |
| page → server | page-info |
url, title |
Sent on connect, DOMContentLoaded/load/popstate/hashchange, and every 5s as a poll fallback (SPAs) |
| page → server | console |
level, text, ts |
Wrapped console methods & uncaught errors, batched with 500ms throttle; server keeps a 500-entry ring buffer per page (kept after disconnect) |
| page → server | eval-result |
reqId, ok, value?, error?, durationMs |
Late responses (already timed out) are ignored |
| server → page | welcome |
pageId |
hello accepted |
| server → page | eval |
reqId, code, timeoutMs |
Code to execute |
| server → page | error |
error |
e.g. invalid token |
eval conventions (client.js): code is first wrapped as an expression async () => ( code ), falling back to a statement block (with return) on SyntaxError; $ / $$ are predefined; timeouts are enforced server-side (default 30s, max 120s); results are safely serialized as string previews (Error→stack, DOM→outerHTML excerpt, circular refs marked, depth ≤ 6, ≤ 50k chars).
Development
- Tests:
npm test(Node e2e: spawns the server + fake pages + tool calls over both stdio and HTTP transports);npm run test:browser(Playwright real-browser e2e: Chromium loads test/test-page.html, verifies all 6 tools over a real WebSocket; runnpx playwright install chromiumfirst). The real-browser flow can also be verified manually with the test page. - Dependencies:
ws(WebSocket),@modelcontextprotocol/sdk(MCP),zod(validation); dev dependency@playwright/test. Node ≥ 18.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。
mcp-server-qdrant
这个仓库展示了如何为向量搜索引擎 Qdrant 创建一个 MCP (Managed Control Plane) 服务器的示例。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。