mobile-mcp
Allows control and automation of an Android phone over Wi-Fi ADB, exposing mobile-mcp tools to interact with the device, including tapping, typing, installing/uninstalling apps, opening URLs or app deep links, and other device actions.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mobile-mcpopen Chrome on my phone and take a screenshot"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mobile-mcp-image
Container image for mobile-next/mobile-mcp, which has no published image,
bundled with adb so it can drive an Android phone over Wi-Fi adb from a Kubernetes Pod or any container host.
Unmodified upstream:
@mobilenext/mobile-mcpfrom npm, every dependency pinned bypackage-lock.json(version + sha512). This repo only adds an entrypoint.adb: Google Android SDK Platform-Tools, pinned by sha256.
Runtime:
gcr.io/distroless/nodejs22-debian13— no shell, no package manager; runs as uid 1000.Image:
ghcr.io/mouse9527/mobile-mcp-image:sha-<short commit>(linux/amd64; built only frommain; nolatest).
Component | Version |
mobile-mcp | 1.0.12 (mobilecli 1.0.21) |
platform-tools (adb) | 37.0.1 |
Node.js | 22 (distroless, Debian 13) |
What the entrypoint does
Refuses to start unless
MOBILEMCP_AUTHis set (mobile-mcp otherwise serves without authentication), orMOBILEMCP_AUTH_DISABLED=1explicitly allows that (for use behind an authenticating proxy). Setting both is an error.Copies
$ADB_KEY_DIR/adbkeyto~/.android/adbkeywith mode 0600, so the phone's pairing survives restarts. adb derives the public key from the private key; noadbkey.pubis needed.Starts the adb server with mDNS off (
ADB_MDNS=0; container networks usually drop multicast).If
PHONE_ADDRis set, checks everyKEEPALIVE_SECONDSwhether the phone is connected and runsadb connecton each ofPHONE_ADB_PORTSuntil one works. It logs one line only when the state changes.Starts mobile-mcp's Streamable HTTP server on
MCP_LISTEN(/mcp), in the same process.
Telemetry (PostHog, scarf.sh) is off by default (MOBILEMCP_DISABLE_TELEMETRY=1).
Related MCP server: phone-mcp-server
Environment
Variable | Default | Meaning |
| — (required unless disabled) | Bearer token clients must send |
| unset |
|
| empty | Phone host name or IP. Empty = do not connect (the MCP server still starts) |
|
| Ports to try, space separated. |
|
| Reconnect check interval (minimum 5) |
| empty | Directory containing |
|
|
|
|
| Must be writable (adb key copy, mobilecli daemon socket) |
| unset | mobile-mcp: set |
|
| mobile-mcp telemetry switch |
| unset | mobile-mcp: also append each tool call, with timestamps, to this file |
Writable paths needed with a read-only root filesystem: $HOME (default /tmp/home) — mount an emptyDir on /tmp.
Run
# generate a key once and keep it; pairing is bound to it
openssl genrsa -traditional -out adbkey 2048
docker run --rm -p 8931:8931 \
-e MOBILEMCP_AUTH="$(openssl rand -hex 32)" \
-e PHONE_ADDR=<phone host or IP> \
-e ADB_KEY_DIR=/keys -v "$PWD/adbkey:/keys/adbkey:ro" \
ghcr.io/mouse9527/mobile-mcp-image:sha-<short commit>Pair the phone once from inside the container (Android 11+: Developer options → Wireless debugging → Pair device with pairing code):
docker exec -it <container> adb pair <phone>:<pairing port> # enter the 6-digit code
docker exec -it <container> adb connect <phone>:<debug port>
docker exec -it <container> adb -s <phone>:<debug port> tcpip 5555 # switch to a fixed portAfter a phone reboot tcpip 5555 is lost: re-enable Wireless debugging and run the last two commands again with the new port.
Lease gateway (optional, PHONE_LEASE=1)
When several agents share one phone through an MCP gateway that opens a new upstream session for every tool call
(LiteLLM does), per-session locking is useless and multi-step tasks interleave (agent A reads the screen, agent B
opens another app, agent A taps). With PHONE_LEASE=1 the entrypoint puts a small gateway (gateway.js, same
process, no dependencies) on MCP_LISTEN and moves mobile-mcp to LEASE_INNER_LISTEN (default 127.0.0.1:8932).
mobile-mcp itself is unchanged; with the variable unset nothing changes.
One holder at a time. A holder's tool call takes the phone if it is free and nobody is queued; every call extends the lease to now +
LEASE_IDLE_S. Others wait FIFO inside their call for up toLEASE_WAIT_S, then get a tool error (isError) saying the phone is busy and their queue position (keptLEASE_TICKET_S, while someone holds the phone). When the lease ends, the first caller still waiting in a call gets it, reservedLEASE_GRACE_Sfor its next call; queued callers that are not waiting any more are skipped, so the phone is never parked for someone who left. State is in memory: a restart frees the phone.Who is the holder (first match):
x-phone-holderheader (set by the client's MCP config) >x-openwebui-chat-id(Open WebUI) > the client's MCP session id at the gateway in front, forwarded asmcp-session-id(mobile-mcp is stateless, so it is on every request) > nothing (call-level exclusion only, never keeps the phone). A client session seen for the first time only getsLEASE_SESSION_FIRST_IDLE_S, so callers that open a new session per call do not wait behind themselves. LiteLLM: listmcp-session-id,x-openwebui-chat-id,x-phone-holderin the server'sextra_headers.Extra tools in
tools/list:lease_status,lease_hold(seconds, keep the phone while waiting for the user, capped atLEASE_HOLD_MAX_S),lease_release.LEASE_TOKENS=1also addslease_acquireand an optionalleaseargument on every tool, for callers that cannot be identified otherwise (off by default: it changes tool schemas).Logs: one JSON line per event on stderr (
"src":"lease"): time, event, a sha256 prefix of the holder, how it was identified, tool name, wait time. Never arguments, results or raw holder ids.
Variable | Default |
|
|
|
|
|
|
|
|
|
|
|
|
| unset (off) |
|
|
Security notes
Anyone holding the Bearer token can fully control the phone (tap, type, install / uninstall apps, open any URL scheme). Keep the endpoint on a private network and restrict who can reach it.
The adb server listens only inside the container; do not publish adb ports.
Upgrading
mobile-mcp: change the version in
package.json, then regenerate the lockfile from the public npm registry:npm install --package-lock-only --ignore-scripts --omit=dev --registry https://registry.npmjs.org. Check the upstream changelog for new tools and update the expected tool count insmoke.sh.platform-tools: change
PLATFORM_TOOLS_VERSIONandPLATFORM_TOOLS_SHA256in the Dockerfile. Google's repository manifest (https://dl.google.com/android/repository/repository2-3.xml) lists sha1 only; download the zip, check its sha1 against the manifest, then record its sha256.Base images: update the digests in
NODE_IMAGE/RUNTIME_IMAGE; keep both on the same Debian release.
CI
.github/workflows/image.yml: build linux/amd64 → audit.sh (no adb keys, credentials or .git in the image;
file list and hashes uploaded as an artifact) → smoke.sh (refuses to start without a token, 401 without token,
33 tools, adb ≥ 37, uid 1000, no shell, telemetry off; lease gateway with PHONE_LEASE=1: queueing, release, FIFO,
hold, holder identity, first-call probation — smoke/lease_test.py) → push to ghcr from main only.
License
MIT for the files in this repository (LICENSE). The image also contains third-party software under its own
licenses; see NOTICE.md.
This server cannot be deployed
Maintenance
Related MCP Connectors
Give your AI agent a real cloud Android phone: push APKs, drive it, get screenshots and logs.
Cloud Android phones for AI agents: create a phone, read the screen, tap, type, install APKs, park.
Control real Android and iOS devices with LLM agents — tap, swipe, type, automate flows.
Disposable cloud Android emulators for coding agents: run an APK or PR build, tap, type, screenshot.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables AI models to control Android devices via ADB through natural language commands, supporting screen analysis and automated actions.333 npmMIT
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to control Android phones via MCP and HTTP. Supports screen capture, taps, swipes, text input, and app management.6AGPL 3.0
- FlicenseBqualityAmaintenanceEnables an AI agent to control an Android phone over a private Tailscale network via accessibility UI automation, screenshots, gestures, notifications, clipboard, intents, SAF file access, and optional Termux/Shizuku shells without root or ADB.65-
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to control a physical Android device or Docker-hosted emulator over ADB by taking screenshots and performing taps, swipes, key presses, text input, shell commands, app launches, and device info queries. It runs as a local MCP stdio server alongside a web UI and REST API for remote access.1MIT