mobile-bridge-mcp,实现AI远程操控手机上的web页面

Whistle 远程控制桥接 MCP Server

概述

通过 whistle 代理向手机页面注入 JS 脚本,让电脑端编码助手(Qoder IDE)能够远程操控手机上的 Web 页面。提供 take_snapshot、click、fill、scroll、screenshot 等工具,类似 chrome-devtools-mcp 操控桌面浏览器的体验。

项目路径/Users/alsc/Documents/mobile-bridge-mcp/


系统架构图

graph TB subgraph 电脑端 Q[编码助手 Qoder] MCP[MCP Server
stdio 模式] BS[Bridge Server
HTTP + WebSocket :9300] W[Whistle 代理
:8899] Q -->|调用 MCP 工具| MCP MCP -->|内嵌启动| BS end subgraph 手机端 BR[手机浏览器] JS[bridge-client.js
注入的脚本] DOM[页面 DOM] BR -->|执行| JS JS -->|操作| DOM end BR -->|WiFi 代理| W W -->|jsAppend 注入脚本| BR JS -->|HTTPS: 同源路径长轮询| W W -->|路径匹配 /__mb__/ 转发| BS JS -->|HTTP: WebSocket 直连| BS

核心设计:双传输模式

问题背景

手机通过 whistle 代理访问 HTTPS 页面时,存在以下限制:

  • Mixed Content :HTTPS 页面无法加载 http:// 资源或建立 ws:// 连接
  • 假域名不可达mobile-bridge.local 等假域名在 iOS 上会触发 mDNS 解析,绕过 HTTP 代理
  • WebSocket 不走代理:手机浏览器的 WebSocket 连接可能不经过 HTTP 代理

最终方案:同源路径 + Whistle 路径转发

bash 复制代码
HTTPS 页面请求流程:
手机脚本 → fetch('https://当前域名/__mb__/register') → whistle 代理拦截
→ 匹配路径规则 /__mb__/ → 转发到 127.0.0.1:9300 → bridge-server 处理

关键原理

  1. 脚本向页面自身域名 发起请求(location.origin + '/__mb__/...'),属于同源请求
  2. 同源 HTTPS 请求一定经过 whistle 代理(页面本身就是通过代理加载的)
  3. Whistle 已对该域名做 HTTPS 拦截(jsAppend 需要),所以能看到请求路径
  4. Whistle 按路径 /__mb__/ 匹配规则,将请求转发到本机 bridge-server(HTTP :9300)
  5. 不存在 Mixed Content 问题,不需要假域名,不需要 DNS 解析

交互时序图

sequenceDiagram participant U as 编码助手 Qoder participant M as MCP Server participant BS as Bridge Server :9300 participant W as Whistle 代理 participant B as 手机浏览器 participant JS as bridge-client.js Note over U,JS: === 初始化阶段 === U->>M: IDE 自动启动 MCP Server (stdio) M->>BS: 内嵌启动 Bridge Server (:9300) B->>W: 请求 HTTPS 页面 W->>W: jsAppend 注入 bridge-client.js W->>B: 返回页面 + 注入脚本 B->>JS: 执行注入脚本 Note over JS,BS: === HTTPS 长轮询注册 === JS->>W: POST https://当前域名/__mb__/register W->>BS: 路径匹配,转发到 127.0.0.1:9300 BS->>W: 200 {clientId: "abc123"} W->>JS: 注册成功 Note over JS,BS: === 长轮询循环 === JS->>W: GET https://当前域名/__mb__/poll?clientId=abc123 W->>BS: 转发 BS-->>BS: 挂起等待命令 (最长25秒) BS->>W: 204 No Content (无命令) W->>JS: 204 JS->>W: 再次 poll... Note over U,JS: === 操作阶段 (take_snapshot) === U->>M: 调用 take_snapshot M->>BS: sendCommand("snapshot") Note over BS: 下次 poll 到来时返回命令 BS->>W: 200 {msgId, action:"snapshot"} W->>JS: 收到命令 JS->>JS: 遍历 DOM,生成快照 JS->>W: POST /__mb__/response {msgId, data: 快照} W->>BS: 转发结果 BS->>M: 返回快照 M->>U: DOM 快照文本

MCP 工具一览

graph TB MCP[mobile-bridge MCP Server] MCP --> T1[list_clients
列出已连接的手机] MCP --> T2[take_snapshot
获取 DOM 文本快照] MCP --> T3[click
点击元素] MCP --> T4[fill
输入文本] MCP --> T5[scroll
滚动页面] MCP --> T6[evaluate_script
执行任意 JS] MCP --> T7[get_url
获取当前 URL] MCP --> T8[screenshot
截图 base64] MCP --> T9[get_text
获取元素文本]
工具名 参数 说明
list_clients 列出当前连接的手机设备
take_snapshot clientId? 获取页面 DOM 文本快照(含 uid 标记)
click uid, clientId? 点击指定元素(触发 touch + click)
fill uid, value, clientId? 向输入框填入文本并触发 input/change
scroll uid?, x?, y?, clientId? 滚动到元素或坐标
evaluate_script code, clientId? 执行任意 JS 代码并返回结果
get_url clientId? 获取当前页面 URL 和标题
screenshot uid?, clientId? 截图返回 base64(依赖 html2canvas)
get_text uid, clientId? 获取指定元素的文本内容

当只有一个客户端连接时,clientId 可省略。


项目结构

bash 复制代码
/Users/alsc/Documents/mobile-bridge-mcp/
  ├── src/
  │   ├── bridge-client.js     # 注入手机页面的客户端脚本
  │   ├── bridge-server.ts     # HTTP + WebSocket 桥接服务
  │   └── mcp-server.ts        # MCP Server 入口(内嵌 bridge-server)
  ├── package.json
  └── tsconfig.json

核心模块详解

1. bridge-client.js(手机端注入脚本)

通过 whistle jsAppend 注入到手机页面,核心逻辑:

传输层自动选择

  • HTTPS 页面 → 同源路径长轮询(fetch(location.origin + '/__mb__/...')
  • HTTP 页面 → WebSocket 直连(ws://电脑IP:9300

长轮询通信协议

  1. POST /__mb__/register --- 注册客户端,获取 clientId
  2. GET /__mb__/poll?clientId=xxx --- 长轮询拉取命令(25秒超时返回 204)
  3. POST /__mb__/response --- 回传命令执行结果

DOM 快照

  • 使用 data-mb-uid 属性动态标记可见且可交互的 DOM 元素
  • 输出格式模仿 chrome-devtools-mcp 的 snapshot,便于编码助手理解

诊断标记

  • 脚本执行后会修改页面标题为 [MB] 原标题
  • 注册成功后标题变为 [MB:clientId] 原标题

2. bridge-server.ts(电脑端桥接服务)

Node.js 服务,监听 9300 端口,同时处理:

  • HTTP 路由/register/poll/response/status/bridge-client.js
  • WebSocket:用于 HTTP 页面的直连模式
  • 路径兼容 :自动去掉 /__mb__ 前缀,兼容 whistle 转发的同源请求

核心特性

  • 支持多客户端(通过 clientId 区分)
  • 指令通过 msgId 匹配请求和响应
  • 命令超时机制(默认 15 秒)
  • 长轮询挂起等待(25 秒超时)
  • 轮询客户端 30 秒无活动自动清理

3. mcp-server.ts(MCP Server 入口)

基于 @modelcontextprotocol/sdk 实现 stdio 模式的 MCP Server:

  • 内嵌启动 BridgeServer(同进程)
  • 注册 9 个 MCP 工具
  • IDE 打开项目时自动启动

Whistle 配置

在 whistle Rules 中添加两条规则:

csharp 复制代码
# 1. 注入 bridge-client.js 到目标页面
*.ele.me jsAppend://{bridge-client.js}

# 2. 路径匹配: /__mb__/ 开头的请求转发到本机 bridge-server
/\/__mb__\// 127.0.0.1:9300

规则说明

  • 第 1 条:whistle 对 *.ele.me 的 HTTPS 响应做拦截,在 HTML 末尾追加 bridge-client.js 脚本内容
  • 第 2 条:正则匹配所有包含 /__mb__/ 路径的请求,转发到本机 9300 端口的 bridge-server

前提条件

  • whistle 已安装根证书到手机(HTTPS 拦截需要)
  • 手机 WiFi 代理指向电脑 whistle(默认 :8899)

whistle Values 配置

  • 在 Values 中创建 bridge-client.js,内容为 /Users/alsc/Documents/mobile-bridge-mcp/src/bridge-client.js 的完整代码

IDE 注册

.vscode/mcp.json

json 复制代码
{
  "servers": {
    "mobile-bridge": {
      "type": "stdio",
      "command": "npx",
      "args": ["tsx", "/Users/alsc/Documents/mobile-bridge-mcp/src/mcp-server.ts"]
    }
  }
}

IDE 打开项目时自动拉起 MCP 进程并监听 9300 端口。


踩坑记录

1. .local 域名不可用

iOS/macOS 上 .local TLD 触发 mDNS (Bonjour) 解析,绕过 HTTP 代理。mobile-bridge.local 的请求永远不会到达 whistle。

2. WebSocket 不走 HTTP 代理

手机浏览器建立 WebSocket 时可能不经过 HTTP CONNECT 代理,导致 wss://假域名 连接失败。

3. Mixed Content 限制

HTTPS 页面无法发起 http://ws:// 请求。所有通信必须是 HTTPS/WSS。

4. 最终解决方案

使用同源路径长轮询 :请求页面自身域名 + /__mb__/ 路径前缀,whistle 按路径匹配转发。彻底避免了 DNS、代理、Mixed Content 三大问题。

5. 端口冲突导致 MCP 启动失败

手动 kill 9300 端口进程后,需要 Reload IDE Window 让 IDE 重新拉起 MCP 进程。避免手动启动导致端口冲突。


验证通过的功能

  • get_url --- 获取页面标题和 URL
  • evaluate_script --- 远程执行 JS(修改页面标题)
  • 长轮询通信稳定(register 200、poll 204/200、response 200)
  • IDE 自动启动 MCP Server
  • HTTPS 页面(h5.ele.me)正常工作
相关推荐
舒一笑2 小时前
Windows 下执行 pnpm install 报 EBUSY: resource busy or locked,我最后用这一招解决了
前端·windows·程序员
龙月2 小时前
Gitlab迁移与升级技术方案
前端·后端
用户223586218202 小时前
核心三角-Command Agent Skill - claude_0x02
前端
竹林8183 小时前
在NFT项目中集成IPFS:从Pinata上传到前端展示的完整踩坑指南
前端·javascript
吴声子夜歌3 小时前
Vue3——渲染函数
前端·vue.js·vue·es6
Hello--_--World3 小时前
ES15:Object.groupBy() 和 Map.groupBy()、Promise.withResolvers() 相关知识点
开发语言·前端·javascript
Cache技术分享3 小时前
386. Java IO API - 监控目录变化
前端·后端
Hooray3 小时前
管理后台框架 AI 时代的版本答案,Fantastic-admin 6.0 它来了!
前端·前端框架·ai编程
2501_913680003 小时前
Vue3项目快速接入AI助手的终极方案 - 让你的应用智能升级
前端·vue.js·人工智能·ai·vue·开源软件