Skip to main content
Glama
README.md
# dsh-adb-phone

**English** | [简体中文](README.zh-CN.md)

An [MCP](https://modelcontextprotocol.io) server that lets a **DeepSeek Harness (dsh)** agent see and
control a USB-connected Android phone over `adb`.

dsh ships `@deepseek-ai/dsh-mcp-client`, which registers MCP tools as native tools named
`mcp__phone__tap` and friends.

## Why MCP instead of a native Cordis plugin

dsh is built on Cordis, so a native plugin is possible. But the logic here (`src/core.mjs`) is
host-agnostic — JSON Schema plus an `execute` function. Wrapping it as MCP makes it reusable by dsh,
Claude Code, Codex and any other MCP client, while a Cordis plugin would only work inside dsh.

The trade-off is two missing features: there is no `codemode`, and no OpenCode-style `phone`
permission action — approvals are handled by dsh's own policy.

## Requirements

- Android device with **USB debugging** enabled and the host authorised.
- Node.js ≥ 18.
- `adb` reachable through `ANDROID_HOME` / `ANDROID_SDK_ROOT` / `ADB_PATH`, or on `PATH`.

## Install

**From source**

```bash
git clone https://github.com/BLxieFox/dsh-adb-phone.git
cd dsh-adb-phone
npm install
```

There is no package-registry release; clone and run from the checkout.

## Register it in dsh

Edit `~/.dsh/profiles/<profile>/cordis.patch.yml` and append an insert entry:

```yaml
- insert:
    - id: mcp-phone
      name: '@deepseek-ai/dsh-mcp-client'
      config:
        serverName: phone
        transport: stdio
        command: node
        args: ['C:/Users/you/dsh-adb-phone/bin/mcp.js']
        env:
          ANDROID_HOME: 'E:/Android/Sdk'
        toolCallTimeoutMs: 120000
```

Save the file; `patchReload: live` reloads the profile. The tools then appear as `mcp__phone__*` —
try asking the agent to report the phone status.

## ⚠️ The child environment is scrubbed

MCP clients do **not** pass the full environment to a stdio server. `@modelcontextprotocol/sdk`
keeps only an allowlist (`PATH`, `APPDATA`, `LOCALAPPDATA`, `USERPROFILE`, `TEMP`, `SYSTEMROOT`, … on
Windows).

**`ANDROID_HOME` is not on that list.** Even if you export it in your shell, the MCP server will not
see it and `adb` fails with:

```
spawn adb.exe ENOENT
```

Two fixes, either works:

1. Pass `env.ANDROID_HOME` explicitly in the config (recommended, as above);
2. Make sure `adb` itself is on the `PATH` the child inherits.

## Tools

| Tool (inside dsh) | Description |
| --- | --- |
| `mcp__phone__status` | Devices, model, Android version, ABI, screen size/density, battery, foreground app |
| `mcp__phone__screenshot` | Screen capture, returned as an MCP image block |
| `mcp__phone__snapshot` | UI hierarchy as a labelled tree, optionally with a screenshot |
| `mcp__phone__tap` | Tap by `label` / `text` / coordinates; long press via `duration` |
| `mcp__phone__swipe` | Swipe by direction + distance, or explicit coordinates |
| `mcp__phone__type` | Type text; ASCII via `input text`, Unicode via clipboard; `clear`, `commit` |
| `mcp__phone__key` | Named keys or a raw keycode |
| `mcp__phone__open` | Launch an app, an activity, or a URL |
| `mcp__phone__apps` | List installed packages |
| `mcp__phone__wait` | Poll until text appears or a package becomes foreground |
| `mcp__phone__shell` | Raw `adb shell` |
| `mcp__phone__push` / `mcp__phone__pull` | File transfer |
| `mcp__phone__install` | Install an APK |

## Usage

```
mcp__phone__snapshot first, act on a [label] with mcp__phone__tap,
then snapshot again to verify.
```

With several devices attached, pass `serial` or set `ADB_SERIAL`.

## Differences from the OpenCode version

1. **Timeouts** — dsh allows 60 s per tool call by default; screenshots or app launches may need
   `toolCallTimeoutMs: 120000`.
2. **Images** — screenshots come back as MCP `image` blocks (base64). dsh renders them when the
   model accepts image input and attachments are enabled.
3. **Permissions** — no `phone` permission action; dsh's own approval policy applies.

## Relationship to opencode-adb-phone

`src/core.mjs` is shared with [opencode-adb-phone](https://github.com/BLxieFox/opencode-adb-phone)
(adb calls, uiautomator parsing, label addressing, text input). Keep the two in sync when changing
behaviour.

Only the entry point differs: an OpenCode plugin (`ctx.tool.transform`) there, an MCP server
(`tools/list` + `tools/call`) here.

## Development

```bash
npm test                    # real stdio handshake, asserts the 14 tools
ADB_PHONE_SMOKE=1 npm test  # also calls status once, needs a device attached
```

## Limitations

- `uiautomator dump` can fail on idle or animating screens; the server retries once.
- WebViews often expose no usable node text — fall back to a screenshot plus coordinates.
- CJK IMEs may leave text in the composing strip; use `commit: true`.
- Not a substitute for instrumentation tests on an app you own.

## License

MIT