Skip to main content
Glama
MobileAiUse

phone-agent-mcp

by MobileAiUse
README.md
# phone-agent-mcp

> Local MCP Server — Exposes the AI Agent capabilities from the WebADB browser page as MCP Tools, allowing external AI clients such as OpenClaw / Qoder to invoke them via the standard MCP protocol.

## Architecture Overview

```
External AI Client (Qoder / OpenClaw / Browser-side MCP Client)
        │
        │  Streamable HTTP (SSE stream, preferred) or stdio (JSON-RPC 2.0)
        ▼
┌───────────────────┐      WebSocket       ┌──────────────────────────────┐
│  phone-agent-mcp  │ ◄──────────────────► │  Mobile AI Agent Browser Page│
│  (Node.js Process)│    ws://localhost    │  (https://mobile-ai-use.xyz) │
│                   │      :7788           │                              │
│  ┌──────────────┐ │                      │  ┌─────────────────┐         │
│  │ McpServer    │ │  callBrowser()       │  │ MCP Relay       │         │
│  │ (HTTP+stdio) │ │ ──── tool/args ────► │  │ (WS Client)     │         │
│  │              │ │ ◄─── result ──────── │  │                 │         │
│  │ 9 Tools      │ │                      │  │ Agent Scheduler │         │
│  │              │ │  progress ◄───────── │  │ Tool Executor   │         │
│  └──────────────┘ │                      │  └─────────────────┘         │
└───────────────────┘                      └──────────────────────────────┘
```

- **MCP Layer**: Built on `@modelcontextprotocol/sdk`'s `McpServer`, providing two transport channels:
  - **Streamable HTTP** (default port `7789`, `/mcp` endpoint): SSE streaming output for results and intermediate progress, supports multiple sessions — the preferred channel
  - **stdio**: Traditional transport compatible with local CLI clients such as Qoder / OpenClaw
- **Relay Layer**: Embedded `ws` WebSocket Server (default port `7788`), forwarding tool call requests to the MCP Relay in the browser page
- **Progress Reporting**: Supports the MCP `notifications/progress` protocol, pushing Agent thinking/execution progress to clients in real time
- **Tasks Extension**: Supports the MCP Tasks extension (`io.modelcontextprotocol/tasks`), making long-running tasks asynchronous — clients can poll status, receive progress, and recover after disconnection

## Prerequisites

- **Node.js** ≥ 18
- **pnpm** (recommended, the project's unified package manager)
- **Mobile AI Use browser page** open and running (`https://mobile-ai-use.xyz`), with the MCP Relay status indicator in the top bar showing green (connected)

## Quick Start

```bash
# 1. Enter the mcp-server directory
cd mcp-server

# 2. Install dependencies
pnpm install

# 3. Build (generates dist/server.js)
pnpm run build

# 4. Start the server
pnpm start
```

Development mode (runs TypeScript directly without building):

```bash
pnpm run dev
```

After successful startup, the console outputs:

```
[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
```

## Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `PHONE_AGENT_WS_PORT` | `7788` | WebSocket relay port |
| `PHONE_AGENT_HTTP_PORT` | `7789` | Streamable HTTP port (also serves the `/adb/*` control plane) |
| `PHONE_AGENT_ADB_WS_PORT` | `7790` | ADB over WebSocket binary bridge port |
| `PHONE_AGENT_ADB_SERVER_HOST` | `127.0.0.1` | Local adb server host (control plane `/adb/*` only) |
| `PHONE_AGENT_ADB_SERVER_PORT` | `5037` | Local adb server port (control plane `/adb/*` only) |
| `PHONE_AGENT_BIND_HOST` | `127.0.0.1` | Bridge/control-plane bind address; a token is required when non-loopback |
| `PHONE_AGENT_ADB_TOKEN` | (empty) | Shared token enforced on non-loopback binds (used by both bridge and control plane) |

## ADB over WebSocket Bridge

Besides direct WebUSB, the web page can also treat a **remote/networked phone** as a first-class device: the host running `phone-agent-mcp` connects directly to the phone's adbd (`ip:port`, i.e. `adb tcpip` mode) and bridges the raw ADB stream to the browser over a WebSocket. The browser reuses `AdbDaemonWebSocketDevice` to produce a standard `Adb` instance, so screen mirroring / AI Agent / workflows all work with zero downstream changes.

```
Remote phone adbd (adb tcpip 5555)
        ▲  direct TCP to <ip:port>, raw ADB bytes relayed both ways
        │
phone-agent-mcp  ──  binary bridge WS (:7790)  ──►  Browser AdbDaemonWebSocketDevice
```

### Control-plane endpoints (reuse the `7789` httpServer)

| Endpoint | Method | Body | Success response | Description |
|----------|--------|------|------------------|-------------|
| `/adb/devices` | GET | — | `{ "devices": [ { serial, state, model?, device?, product? } ] }` | List devices currently known to the local adb server |
| `/adb/connect` | POST | `{ "host": "192.168.1.5", "port": 5555 }` | `{ "ok": true, "message": "..." }` | Make the host `adb connect` to the remote phone; failure returns `502` + `{ ok:false, message }` |
| `/adb/disconnect` | POST | `{ "serial": "192.168.1.5:5555" }` | `{ "ok": true, "message": "..." }` | `adb disconnect` the given device |

Error conventions: unauthorized `401`; missing param / malformed JSON `400`; wrong method `405` (with `Allow` header); unknown path `404`; no local adb (not on PATH) `503`; any other failure `500`.

### Bridge endpoint (`7790`)

The browser connects via `ws://<host>:7790/?serial=<serial>[&token=<token>]`; the bridge parses `serial` as `ip:port`, opens a direct TCP connection to the phone's adbd and relays bytes bidirectionally (the browser performs the CNXN/AUTH handshake itself). `serial` must be `ip:port`; USB serials are not supported (use WebUSB instead). Only one active bridge per `serial` is allowed; duplicate connections are rejected. Close codes: `4401` invalid token / `4400` missing serial or serial is not `ip:port` / `4409` device busy / `4502` failed to connect to adbd (refused / timeout / unreachable) / `4503` adbd connection errored or closed.

### Prerequisites

- adb is on the host `PATH` (`adb version` works).
- The remote phone has been switched with `adb tcpip 5555` and is reachable at its `ip:port`. The random TLS port of Android 11+ "Wireless debugging" is not supported by the bridge yet.
- The browser uses its own ADB key, so the phone shows an "Allow USB debugging" prompt on the first bridge; confirm it on the phone.

### Security

- **localhost needs no token**: when `PHONE_AGENT_BIND_HOST` is loopback (default), the control plane only accepts requests whose peer is a loopback address, and the bridge needs no token.
- **Non-loopback enforces a token**: binding to a non-loopback address requires `PHONE_AGENT_ADB_TOKEN`; otherwise the startup guard `assertBridgeAuth` throws and aborts startup. Afterwards the control plane requires `Authorization: Bearer <token>` or `?token=<token>`, and the bridge requires `?token=<token>`.
- **Mixed content**: if the page is served over `https`, the browser blocks `ws://`; configure `wss://` for the bridge (TLS terminated by a reverse proxy).

## Registered Tools

| Tool | Permission | Timeout | Description |
|------|------------|---------|-------------|
| `run_agent_task` | Write | Configurable | Run an AI Agent task on the connected phone with a natural language goal (supports async execution via the Tasks extension) |
| `abort_task` | Write | 10s | Abort the currently running Agent task |
| `get_task_result` | Read | 10s | Get detailed results (including steps) of a completed task by task ID |
| `get_latest_task` | Read | 10s | Get the result of the most recent Agent task in the current conversation |
| `take_screenshot` | Read | 30s | Take a screenshot of the connected phone screen, returns a PNG image |
| `get_device_status` | Read | 15s | Get device information (model, brand, OS version, etc.) |
| `search_memory` | Read | 10s | Search the Agent's persistent memory store by keyword |
| `save_memory` | Write | 10s | Save a semantic memory entry to the memory store |
| `delete_memory` | Write | 10s | Delete a memory entry by ID |

### `run_agent_task` in Detail

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `goal` | `string` | Yes | Natural language task goal, e.g. "Open WeChat and send 'Hello' to Zhang San" |
| `timeoutMs` | `number` | No | Maximum execution time in milliseconds, default 1,200,000 (20 minutes) |

The Agent will automatically plan execution steps and achieve the goal through screen visual recognition and touch control. During execution, thinking/tool calls/step results are pushed in real time via MCP `notifications/progress`.

## MCP Tasks Extension (Async Long-Running Tasks)

`run_agent_task` is registered as a **task-based tool** (`taskSupport: 'optional'`), supporting the MCP Tasks extension (`io.modelcontextprotocol/tasks`) to address blocking and connection interruption issues with long-running operations.

### Workflow

```
Client                                          Server
  │  tools/call run_agent_task                    │
  │  params: { arguments, task: { ttl } }         │
  ├──────────────────────────────────────────────►│  createTask → returns immediately
  │  CreateTaskResult { task: { taskId,           │  continues executing in background
  │    status: 'working', pollInterval } }        │
  │◄──────────────────────────────────────────────┤
  │  tasks/get { taskId }   ◄── poll ───►         │  notifications/tasks/status proactive push
  │  … status: working (statusMessage=progress) … │
  │  tasks/result { taskId }                      │  after task completes
  │◄────────────── CallToolResult ────────────────┤
```

### Task States

| State | Meaning |
|-------|---------|
| `working` | Task is executing; `statusMessage` carries the latest progress (Agent thinking/steps) |
| `completed` | Task completed successfully; final result available via `tasks/result` |
| `failed` | Task failed (including browser not connected, timeout, etc.); result contains error details |
| `cancelled` | Client cancelled via `tasks/cancel` |

### Server Capability Declaration

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

Supported endpoints: `tasks/get` (check status), `tasks/result` (get result), `tasks/list` (list tasks), `tasks/cancel` (cancel).

### Key Parameters

| Parameter | Value | Description |
|-----------|-------|-------------|
| TTL | `timeoutMs + 10min` | How long results are retained after task completion; auto-cleaned after expiry |
| pollInterval | `2000ms` | Recommended client polling interval |

### Backward Compatibility

- **New clients** (declare tasks capability): Call with `task` parameter → immediately receive `taskId`, poll `tasks/get` / fetch result via `tasks/result`
- **Legacy clients** (no `task` parameter): Server automatically falls back to internal polling, synchronously waits and returns `CallToolResult` directly — behavior identical to the previous version

> Note: `tasks/cancel` only marks the task as cancelled and stops result writing; to actually interrupt execution on the phone, call the `abort_task` tool separately.

## Configuration for AI Clients

### Streamable HTTP (Recommended)

Clients supporting remote/HTTP connections (including browser-side MCP clients) can point directly to the URL:

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

Verify connectivity:

```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"}}}'
```

The `mcp-session-id` response header is the session ID, which must be included in subsequent requests; the response body is an SSE stream (`text/event-stream`).

### stdio (Qoder / Claude Desktop / OpenClaw Local Integration)

Add the following to the client's MCP configuration file:

```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"
```

After configuration, all 9 phone Agent tools listed above will be available in the AI client — invoke them directly to control the phone.

## Streamable HTTP Transport Details

Streamable HTTP is the standard MCP transport protocol. The server outputs **intermediate progress notifications** and **final results** sequentially within the same HTTP response via an **SSE stream**, eliminating the need for additional client polling:

| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/mcp` | POST | Client message entry point (initialize / tools/call / tasks/* etc.), response is an SSE stream |
| `/mcp` | GET | Opens a dedicated SSE stream to receive server-initiated pushes (e.g. `notifications/tasks/status`) |
| `/mcp` | DELETE | Close the session |

### Streaming Output Content

During execution of long-running tools (e.g. `run_agent_task`), the response stream pushes in real time:

- `notifications/progress`: Agent thinking/execution progress (when client declares `progressToken`)
- `notifications/message`: Fallback channel for progress log downgrades
- `notifications/tasks/status`: Task state changes (working/completed/failed)

The end of the stream contains the final `CallToolResult` or `CreateTaskResult` (task mode).

### Session Management

- **Stateful mode**: Server generates a sessionId, delivered via the `mcp-session-id` response header with `initialize`; subsequent requests must include it
- **One McpServer instance per session**; tool handlers share a global browser relay and task store (`taskStore` is cross-session — tasks can continue to be polled after disconnect/reconnect)
- **CORS is open**: Browser-side MCP clients can access directly

## Connection Flow

1. **Start mcp-server**: `pnpm start` — launches stdio / Streamable HTTP dual channels and the WS relay
2. **Open the browser page**: Navigate to the WebADB page, ensuring the device is connected
3. **Confirm Relay connection**: The Relay status indicator in the top bar turns green; console outputs `Browser connected`
4. **Connect MCP client**:
   - HTTP clients point to `http://localhost:7789/mcp` (include `mcp-session-id` after initialize)
   - Local CLI clients launch via stdio: `node dist/server.js`
5. **Invoke tools**: Requests are forwarded via WebSocket to the browser for execution; results and intermediate progress are streamed back via SSE / stdio

## Project Structure

```
mcp-server/
├── src/
│   └── server.ts          # Main server: McpServer + WS + 9 tool registrations
├── dist/
│   └── server.js          # Build output
├── package.json
├── tsconfig.json           # TypeScript config (ES2022 / NodeNext)
└── pnpm-lock.yaml
```

## Tech Stack

| Component | Version | Purpose |
|-----------|---------|---------|
| `@modelcontextprotocol/sdk` | ^1.12.0 | MCP server framework (McpServer + StdioServerTransport) |
| `ws` | ^8.18.0 | WebSocket server, relays browser connection |
| `zod` | ^4.4.3 | Tool parameter validation and schema declarations |
| `typescript` | ~5.8.3 | Type safety |
| `tsx` | ^4.19.0 | Run TS directly in development mode |

## Design Highlights

- **Dual transport channels**: Streamable HTTP (SSE streaming for results and intermediate progress, multi-session) and stdio (local CLI compatible) coexist; tool registration logic is unified by the `createPhoneAgentServer()` factory function
- **Cross-session task store sharing**: `InMemoryTaskStore` is a global singleton — clients can continue polling non-expired tasks via `tasks/get` / `tasks/result` after reconnecting (new session)
- **Single browser connection**: Only one browser WS connection is accepted at a time; new connections replace old ones
- **Request-response matching**: WS responses are routed to the corresponding pending Promise via the `id` field
- **Timeout fallback**: Each tool call has an independent timeout; returns an error message on timeout instead of hanging
- **Dual-channel progress**: Prefers `notifications/progress` (requires client to declare `progressToken`), falls back to `notifications/message` (logging); in task mode, also writes `task.statusMessage` and pushes `notifications/tasks/status` simultaneously
- **Silent fault tolerance**: Progress push failures do not interrupt task execution; background writes are safely skipped after task cancellation/TTL cleanup
- **Browser offline hint**: Returns a friendly error "Browser not connected" when the WS is 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

ActivityMaintained
ResponsivenessNo issues