Skip to main content
Glama
MobileAiUse

phone-agent-mcp

by MobileAiUse
README.md
# phone-agent-mcp

> 本地 MCP Server — 将 WebADB 浏览器中的 AI Agent 能力暴露为 MCP Tools,供 OpenClaw / Qoder 等外部 AI 客户端通过标准协议调用。

## 架构概览

```
外部 AI 客户端(Qoder / OpenClaw / 浏览器端 MCP 客户端)
        │
        │  Streamable HTTP(SSE 流,首选) 或 stdio(JSON-RPC 2.0)
        ▼
┌───────────────────┐      WebSocket       ┌──────────────────────────────┐
│  phone-agent-mcp  │ ◄──────────────────► │  Mobile AI Agent 浏览器页面    │
│  (Node.js 进程)    │    ws://localhost    │  (https://mobile-ai-use.com) │
│                   │      :7788           │                              │
│  ┌──────────────┐ │                      │  ┌─────────────────┐         │
│  │ McpServer    │ │  callBrowser()       │  │ MCP Relay       │         │
│  │ (HTTP+stdio) │ │ ──── tool/args ────► │  │ (WS Client)     │         │
│  │              │ │ ◄─── result ──────── │  │                 │         │
│  │ 9 个 Tools   │ │                      │  │ Agent 调度       │         │
│  │              │ │  progress ◄───────── │  │ 工具执行         │         │
│  └──────────────┘ │                      │  └─────────────────┘         │
└───────────────────┘                      └──────────────────────────────┘
```

- **MCP 层**:基于 `@modelcontextprotocol/sdk` 的 `McpServer`,提供两种传输通道:
  - **Streamable HTTP**(默认 `7789` 端口,`/mcp` 端点):SSE 流式输出结果与中间过程,支持多会话,首选通道
  - **stdio**:兼容 Qoder / OpenClaw 等本地 CLI 类客户端的传统接入方式
- **中继层**:内嵌 `ws` WebSocket Server(默认 `7788` 端口),将工具调用请求转发给浏览器页面中的 MCP Relay
- **进度上报**:支持 MCP `notifications/progress` 协议,Agent 思考/执行过程实时推送至客户端
- **Tasks 扩展**:支持 MCP Tasks 扩展(`io.modelcontextprotocol/tasks`),长耗时任务异步化,客户端可轮询状态、获取进度、断连后恢复

## 前置条件

- **Node.js** ≥ 18
- **pnpm**(推荐,项目统一包管理器)
- **Mobile AI Use 浏览器页面**已打开并运行(`https://mobile-ai-use.com`),页面顶栏 MCP Relay 状态指示器绿色亮起即表示已连接

## 快速开始

```bash
# 1. 进入 mcp-server 目录
cd mcp-server

# 2. 安装依赖
pnpm install

# 3. 构建(生成 dist/server.js)
pnpm run build

# 4. 启动服务
pnpm start
```

开发模式(无需构建,直接运行 TypeScript):

```bash
pnpm run dev
```

启动成功后,控制台输出:

```
[phone-agent-mcp] WebSocket relay on ws://localhost:7788
[phone-agent-mcp] MCP server ready (stdio transport)
[phone-agent-mcp] Streamable HTTP on http://localhost:7789/mcp
```

## 环境变量

| 变量名 | 默认值 | 说明 |
|--------|--------|------|
| `PHONE_AGENT_WS_PORT` | `7788` | WebSocket 中继端口号 |
| `PHONE_AGENT_HTTP_PORT` | `7789` | Streamable HTTP 端口号 |

## 注册的工具(Tools)

| 工具名 | 权限 | 超时 | 说明 |
|--------|------|------|------|
| `run_agent_task` | 写入 | 可配置 | 在已连接的手机上运行 AI Agent 任务,传入自然语言目标(支持 Tasks 扩展异步执行) |
| `abort_task` | 写入 | 10s | 中止当前正在运行的 Agent 任务 |
| `get_task_result` | 只读 | 10s | 按任务 ID 获取已完成任务的详细结果(含步骤) |
| `get_latest_task` | 只读 | 10s | 获取当前对话中最近一次 Agent 任务的结果 |
| `take_screenshot` | 只读 | 30s | 对已连接的手机屏幕截图,返回 PNG 图片 |
| `get_device_status` | 只读 | 15s | 获取设备信息(型号、品牌、系统版本等) |
| `search_memory` | 只读 | 10s | 按关键词搜索 Agent 的持久化记忆库 |
| `save_memory` | 写入 | 10s | 保存一条语义记忆到记忆库 |
| `delete_memory` | 写入 | 10s | 按 ID 删除一条记忆 |

### `run_agent_task` 详解

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `goal` | `string` | 是 | 自然语言任务目标,如 "打开微信,向张三发送你好" |
| `timeoutMs` | `number` | 否 | 最大执行时间(毫秒),默认 1,200,000(20 分钟) |

Agent 将自动规划执行步骤,通过屏幕视觉识别和触摸控制完成目标。执行期间的思考/工具调用/步骤结果会通过 MCP `notifications/progress` 实时推送。

## MCP Tasks 扩展(异步长耗时任务)

`run_agent_task` 注册为 **task-based 工具**(`taskSupport: 'optional'`),支持 MCP Tasks 扩展(`io.modelcontextprotocol/tasks`),解决长耗时操作的阻塞与连接中断问题。

### 工作流程

```
客户端                                     服务端
  │  tools/call run_agent_task              │
  │  params: { arguments, task: { ttl } }   │
  ├──────────────────────────────────────►│  createTask → 立即返回
  │  CreateTaskResult { task: { taskId,    │  后台继续执行
  │    status: 'working', pollInterval } } │
  │◄──────────────────────────────────────┤
  │  tasks/get { taskId }   ◄── 轮询 ───►  │  notifications/tasks/status 主动推送
  │  … 状态:working(statusMessage=进度)…  │
  │  tasks/result { taskId }                │  任务完成后
  │◄────────────── CallToolResult ─────────┤
```

### 任务状态

| 状态 | 含义 |
|------|------|
| `working` | 任务执行中,`statusMessage` 携带最新进度(Agent 思考/步骤) |
| `completed` | 任务成功完成,可通过 `tasks/result` 获取最终结果 |
| `failed` | 任务失败(含浏览器未连接、超时等),结果中包含错误信息 |
| `cancelled` | 客户端通过 `tasks/cancel` 取消 |

### 服务端能力声明

```json
"capabilities": {
  "tasks": {
    "list": {},
    "cancel": {},
    "requests": { "tools": { "call": {} } }
  }
}
```

支持的端点:`tasks/get`(查状态)、`tasks/result`(取结果)、`tasks/list`(列任务)、`tasks/cancel`(取消)。

### 关键参数

| 参数 | 取值 | 说明 |
|------|------|------|
| TTL | `timeoutMs + 10min` | 任务完成后结果保留时长,过期自动清理 |
| pollInterval | `2000ms` | 建议客户端轮询间隔 |

### 向后兼容

- **新客户端**(声明 tasks 能力):带 `task` 参数调用 → 立即获得 `taskId`,轮询 `tasks/get` / 取结果 `tasks/result`
- **旧客户端**(不带 `task` 参数):服务端自动降级为内部轮询,同步等待并直接返回 `CallToolResult`,行为与旧版一致

> 注:`tasks/cancel` 仅标记任务取消并停止结果写入;如需真正中断手机端执行,请另行调用 `abort_task` 工具。

## 配置到 AI 客户端

### Streamable HTTP(推荐)

支持远程/HTTP 接入的客户端(含浏览器端 MCP 客户端)直接指向 URL:

```json
{
  "mcpServers": {
    "phone-agent": {
      "url": "http://localhost:7789/mcp"
    }
  }
}
```

验证连通性:

```bash
curl -N -X POST http://localhost:7789/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
```

响应头 `mcp-session-id` 即会话 ID,后续请求需携带;响应体为 SSE 流(`text/event-stream`)。

### stdio(Qoder / Claude Desktop / OpenClaw 本地接入)

在客户端的 MCP 配置文件中添加:

```json
{
  "mcpServers": {
    "phone-agent": {
      "command": "node",
      "args": ["/path/to/mcp-server/dist/server.js"],
      "env": {
        "PHONE_AGENT_WS_PORT": "7788"
      }
    }
  }
}
```

### OpenClaw

```yaml
mcp_servers:
  phone-agent:
    command: node
    args:
      - /path/to/mcp-server/dist/server.js
    env:
      PHONE_AGENT_WS_PORT: "7788"
```

配置完成后,AI 客户端中即可看到上述 9 个手机 Agent 工具,直接调用即可操控手机。

## Streamable HTTP 传输详解

Streamable HTTP 是 MCP 标准传输协议,服务端通过 **SSE 流**在同一个 HTTP 响应内依次输出**中间过程通知**与**最终结果**,无需客户端额外轮询:

| 端点 | 方法 | 作用 |
|------|------|------|
| `/mcp` | POST | 客户端消息入口(initialize / tools/call / tasks/* 等),响应为 SSE 流 |
| `/mcp` | GET | 打开独立 SSE 流,接收服务端主动推送(如 `notifications/tasks/status`) |
| `/mcp` | DELETE | 关闭会话 |

### 流式输出内容

长耗时工具(如 `run_agent_task`)执行期间,响应流内会实时推送:

- `notifications/progress`:Agent 思考/执行进度(客户端声明 `progressToken` 时)
- `notifications/message`:进度日志降级通道
- `notifications/tasks/status`:任务状态变更(working/completed/failed)

流末尾为最终 `CallToolResult` 或 `CreateTaskResult`(task 模式)。

### 会话管理

- **有状态模式**:服务端生成 sessionId,随 `initialize` 响应头 `mcp-session-id` 下发,后续请求必须携带
- **每会话一个 McpServer 实例**;工具 handler 共享全局浏览器中继与任务存储(`taskStore` 跨会话,断连重连后可继续轮询任务)
- **CORS 已放开**:浏览器端 MCP 客户端可直接访问

## 连接流程

1. **启动 mcp-server**:`pnpm start`,启动 stdio / Streamable HTTP 双通道和 WS 中继
2. **打开浏览器页面**:访问 WebADB 页面,确保设备已连接
3. **确认 Relay 连接**:页面顶栏 Relay 状态指示器变绿,控制台输出 `Browser connected`
4. **接入 MCP 客户端**:
   - HTTP 客户端指向 `http://localhost:7789/mcp`(initialize 后携带 `mcp-session-id`)
   - 本地 CLI 客户端通过 stdio 启动 `node dist/server.js`
5. **调用工具**:请求经 WS 转发至浏览器执行,结果与中间过程经 SSE / stdio 流式返回

## 项目结构

```
mcp-server/
├── src/
│   └── server.ts          # 主服务:McpServer + WS + 8 个工具注册
├── dist/
│   └── server.js          # 编译产物
├── package.json
├── tsconfig.json           # TypeScript 配置(ES2022 / NodeNext)
└── pnpm-lock.yaml
```

## 技术栈

| 组件 | 版本 | 用途 |
|------|------|------|
| `@modelcontextprotocol/sdk` | ^1.12.0 | MCP 服务端框架(McpServer + StdioServerTransport) |
| `ws` | ^8.18.0 | WebSocket 服务端,中继浏览器连接 |
| `zod` | ^4.4.3 | 工具参数校验与 Schema 声明 |
| `typescript` | ~5.8.3 | 类型安全 |
| `tsx` | ^4.19.0 | 开发模式直接运行 TS |

## 设计要点

- **双传输通道**:Streamable HTTP(SSE 流式输出结果与中间过程,多会话)与 stdio(本地 CLI 兼容)并存,工具注册逻辑由 `createPhoneAgentServer()` 工厂函数统一提供
- **任务存储跨会话共享**:`InMemoryTaskStore` 全局单例,客户端断连重连(新会话)后仍可通过 `tasks/get` / `tasks/result` 继续轮询未过期任务
- **单浏览器连接**:同一时刻只接受一个浏览器 WS 连接,新连接替换旧连接
- **请求-响应匹配**:通过 `id` 字段将 WS 响应路由到对应的挂起 Promise
- **超时兜底**:每个工具调用有独立超时,超时返回错误信息而非挂死
- **进度双通道**:优先使用 `notifications/progress`(需客户端声明 `progressToken`),降级为 `notifications/message`(logging);task 模式下同步写入 `task.statusMessage` 并推送 `notifications/tasks/status`
- **静默容错**:进度推送失败不中断任务执行;任务被取消/TTL 清理后后台写入安全跳过
- **浏览器离线提示**:WS 未连接时返回友好错误 "Browser not connected"

TDQS

A3.7/5.0

Scored across 9 tools

Disambiguation4/5

Most tools are clearly distinct: run_agent_task, get_task_result, get_latest_task, take_screenshot, get_device_status, abort_task, and the three memory tools all have obvious purposes. get_task_result and get_latest_task could be mildly confused (both retrieve task results), and save_memory vs search_memory vs delete_memory are clear. No significant overlap issues.

Naming Consistency5/5

All tools follow a consistent verb_noun pattern throughout: delete_memory, run_agent_task, get_task_result, get_latest_task, take_screenshot, get_device_status, save_memory, search_memory, abort_task. Every name is snake_case with a clear action verb followed by a target noun, showing strong uniformity.

Tool Count5/5

Nine tools is well within the ideal 3-15 range for a phone agent MCP server. Each tool corresponds to a distinct capability: task management (run/get/latest/abort), memory management (save/search/delete), device inspection, and screen capture. None feels redundant or unnecessary.

Completeness3/5

The server covers core workflows: device inspection, task execution/retrieval/abort, screenshots, and memory operations. However, there are no explicit memory update operations (only save/delete/search), and no direct touch-input tool outside of the agent task abstraction, which may be fine given the agent-centric design but leaves some gaps for low-level control.

Maintenance

ActivitySlowing
ResponsivenessNo issues