# Phone-Eye ๐ฑ๐๏ธ
**English | [็ฎไฝไธญๆ](README.zh.md)**
**Your AI agent can finally *see* and *operate* a real Android phone.**
```
phone_look("what's on screen, where is the login button?")
โ "Login button at (540, 1830) โ a green 'Sign in' โฆ"
phone_tap(540, 1830)
phone_look("did the next page load?")
```
Works with any MCP client โ Claude Code, Codex, Cursor, dsh, and friends.
---
## What you need (plain words)
| You provide | One-time or every time? | How hard? |
|---|---|---|
| **An Android phone with USB debugging on** | **one-time, ~60 seconds** (tap "Build number" 7ร โ enable USB debugging) | easy, [step-by-step below](#1-enable-usb-debugging-once-per-phone) |
| **Plug the USB cable once** | **one-time** โ after that the tool switches the phone to Wi-Fi and you never need the cable again (unless the phone factory-resets) | trivial |
| **A computer with Python 3.10+ and git** (or just Docker โ see step 3) | โ | n/a |
| **A vision model** | one-time setup โ **bring any one of these**:
โข an OpenAI API key (or any OpenAI-compatible: GLM, DeepSeek, local llama.cpp/Ollamaโฆ)
โข an existing MCP vision server | one env var, most people already have a key |
That's everything. **No app to install on the phone. No root. No extra server.**
## What it can and can't do (honest table)
| โ
Stable | โ ๏ธ Works but with caveats | โ Not possible (any tool, not just us) |
|---|---|---|
| See the screen (screenshot + read it) | Locked screens can be *read* but not operated | Fully control a brand-new phone before you enable USB debugging yourself |
| Tap / swipe / type | Typing is ASCII (Chinese input needs a clipboard trick โ known adb limit) | The very first "allow USB debugging?" popup on a *new* computer key โ that one tap is yours |
| Survive Wi-Fi adb drops (auto-reconnect) | Some vendor ROMs restrict input on lock screens (e.g. MIUI) | iOS โ different universe |
| Run 24/7 unattended; unexpected popups get read & handled by your agent | Vision quality depends on the model you bring | |
| Multiple phones (one phone-eye process per phone, set `ANDROID_SERIAL` for each) | | |
**The 60-second rule:** every Android requires one human moment โ enable debugging + authorize once. After that, the phone belongs to your agent, even over Wi-Fi, even after reboots of the *computer*.
## Setup (3 steps)
### 1. Enable USB debugging (once per phone)
Settings โ About phone โ tap **Build number** 7ร (unlocks Developer options) โ Developer options โ **USB debugging ON**.
(Got stuck? Tell us your phone model in Discussions โ we'll walk you through it. MIUI/HyperOS may also ask you to sign into a Xiaomi account first.)
### 2. Install adb (if you don't have it), plug USB once, then go wireless
```bash
# macOS: brew install android-platform-tools ยท Ubuntu/Debian: sudo apt install adb
# Windows: scoop install adb (or download Android platform-tools)
adb devices # phone shows up? tap "Allow" on its popup โ check "always allow"
adb shell ip route # โ note the phone's Wi-Fi IP (e.g. 192.168.1.23) while still plugged
adb tcpip 5555 # switch to Wi-Fi mode (adb restarts; the USB entry disappears โ normal)
adb connect 192.168.1.23:5555 # use the IP from above; then unplug the cable, forever
```
Phone IP alternatives if ip route prints nothing
Settings โ Wi-Fi โ your network โ details shows the IP; or `adb shell ip addr show wlan0 | grep inet`.
### 3. Start phone-eye
```bash
git clone https://github.com/boheastill/phone-eye && cd phone-eye
pip install -r requirements.txt
# your eyes โ pick ONE:
export PHONE_EYE_VISION_API_KEY= # OpenAI / GLM / any compatible
# (optional: PHONE_EYE_VISION_BASE_URL, PHONE_EYE_VISION_MODEL)
# local & offline: ..._API_KEY=sk-noauth ..._BASE_URL=http://:8080/v1 ..._MODEL=
python server.py # stdio MCP server โ wire into your client:
```
Wire it into your client โ pick yours:
```bash
# Claude Code (easiest):
claude mcp add phone-eye -- python /path/to/phone-eye/server.py
# Codex:
codex mcp add phone-eye --url stdio://python /path/to/phone-eye/server.py # or see codex docs
```
```jsonc
// any MCP client (generic stdio shape):
{ "mcpServers": { "phone-eye": { "command": "python", "args": ["/path/to/phone-eye/server.py"] } } }
```
### Docker (optional โ no Python needed on the host)
The repo ships a `Dockerfile` (Python 3.12 + adb):
```bash
podman build -t phone-eye . # or: docker build -t phone-eye .
# smoke: a JSON-RPC initialize reply on stdout means it boots:
printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}\n' \
| podman run --rm -i phone-eye
```
Use `--network host` so adb reaches a Wi-Fi phone and your vision endpoint:
```jsonc
"phone-eye": { "command": "podman", "args": ["run","--rm","-i","--network","host",
"-e","ANDROID_SERIAL=192.168.1.23:5555","-e","PHONE_EYE_VISION_API_KEY=","phone-eye"] }
```
**Recommended vision models** (any vision-capable chat model works): `gpt-4o-mini` (default),
GLM `glm-4.6v-flash` (cheap), or run a local Qwen-VL via llama.cpp for fully-offline โ
screenshots then never leave your LAN.
## What happens when something breaks
- **"No Android device reachable"** โ the tool already tried reconnecting; run `adb connect :5555`, or replug USB.
- **"No vision server reachable"** โ you haven't set a key; the error message tells you the exact two fixes.
- Phone rebooted โ Wi-Fi adb survives phone reboots on most ROMs; if not, one `adb connect` again.
- Still stuck? **[Open a discussion](https://github.com/boheastill/phone-eye/discussions) โ we answer, and we'll debug your setup with you.** Bug reports and "it works on my X" notes are equally welcome.
## Tools
| Tool | What it does |
|---|---|
| `phone_look(question?)` | Ask a vision model about the live screen; fuses a UI-tree dump for exact text/button bounds |
| `phone_tap(x, y)` | Tap |
| `phone_swipe(x1, y1, x2, y2, ms?)` | Swipe |
| `phone_type(text)` | Type ASCII text |
| `phone_key(key)` | Press a hardware key โ `wake` revives a sleeping phone (the unattended essential), back/home/recents navigate |
| `phone_intent(action, uri?, component?)` | Open any screen by Android intent (deep settings pages, app pages) without coordinates |
| `phone_screenshot()` | Save screenshot to disk, return path |
## Examples
- [Mobile web QA loop](examples/loop-mobile-web-qa.md) โ the agent verifies its own work on a real screen
- [Surviving an OEM setup wizard](examples/device-setup-wizard.md) โ vision handles whatever pops up
- [Form regression check](examples/form-regression.md)
- [Unattended sentinel](examples/unattended-sentinel.md) โ your agent on night watch: wake โ unlock โ intent โ look โ screenshot
Curious how it works โ or want to modify it? Read the [whitepaper](docs/how-it-works.md)
(architecture, failure-classification decision tree, security model, how to add a verb).
Running it as an always-on HTTP service behind your own fleet? [docs/fleet.md](docs/fleet.md).
## Why "see", isn't this just adb?
UI-tree-only tools are blind to game canvases, images, and anything the accessibility tree can't show. Vision-only tools drift on coordinates. `phone_look` fuses both: the model answers *what is this*, the UI tree supplies *exactly where*. The day we dogfooded it, it discovered a USB-debugging popup on its own screen, read the buttons, and tapped "Allow" by itself โ [the story](https://github.com/deepseek-ai/deepseek-harness/discussions/4743).
## License
MIT. Verified on Redmi K40 Gaming / Android 13 โ add your device to the table via PR.
---
**Author**: [Bohea](https://boheastill.com) โ independent industrial software engineer (Shenzhen). Operator HMIs ยท device integration ยท machine data into your customer's ERP. More runnable demos & engineering notes on the site.