Design baseline for an MCP server that lets LLM clients such as Claude, Codex, and ChatGPT interact with Android devices through a constrained UI automation runtime.
The canonical operator path uses the official MCP Python SDK transports. All MCP clients that implement the standard protocol work against these paths.
SDK stdio (recommended for Claude Desktop, Cursor, and other MCP clients):
python3 -m mobile_mcp.bootstrap \
--config configs/runtime.default.json \
--register-demo-device \
--transport sdk-stdioSDK streamable-HTTP (recommended for production HTTP deployments):
python3 -m mobile_mcp.bootstrap \
--config configs/runtime.default.json \
--register-demo-device \
--transport sdk-http \
--http-port 8000SDK streamable-HTTP with SQL stores:
python3 -m mobile_mcp.bootstrap \
--config configs/runtime.sql.example.json \
--migrate \
--register-demo-device \
--transport sdk-http \
--http-port 8000The SDK HTTP server exposes one endpoint:
POST /mcp— MCP protocol endpoint (streamable-HTTP,application/jsonortext/event-stream)GET /metrics— Prometheus scrape endpoint when metrics are enabled
Run migrations before boot when using SQL stores:
MOBILE_MCP_DB_URL=sqlite:///runtime_state/runtime.db \
python3 -m mobile_mcp.bootstrap --migrate --config configs/runtime.default.jsonVerify startup with:
python3 -m mobile_mcp.bootstrap --config configs/runtime.default.json --print-toolsFor manual Inspector debugging, see MCP Inspector Check.
Install the runtime into a local virtual environment first:
python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install -U pip
python -m pip install -e .For Codex and Claude Code, use the canonical SDK stdio transport:
codex mcp add mobile-mcp -- \
/absolute/path/to/mobile-mcp/.venv/bin/python \
-m mobile_mcp.bootstrap \
--config /absolute/path/to/mobile-mcp/configs/runtime.default.json \
--register-adb-devices \
--transport sdk-stdioclaude mcp add --scope user mobile-mcp -- \
/absolute/path/to/mobile-mcp/.venv/bin/python \
-m mobile_mcp.bootstrap \
--config /absolute/path/to/mobile-mcp/configs/runtime.default.json \
--register-adb-devices \
--transport sdk-stdioIf you want a cwd-independent plain command that does not rely on an editable install already
being active inside the venv, use /usr/bin/env and set PYTHONPATH explicitly:
[mcp_servers.mobile_mcp]
command = "/usr/bin/env"
args = [
"PYTHONPATH=/absolute/path/to/mobile-mcp/src",
"/absolute/path/to/mobile-mcp/.venv/bin/python3",
"-m",
"mobile_mcp.bootstrap",
"--config",
"/absolute/path/to/mobile-mcp/configs/runtime.default.json",
"--register-adb-devices",
"--transport",
"sdk-stdio"
]
startup_timeout_sec = 60If you prefer raw uv, keep it as a normal command and make the writable cache explicit:
If your MCP host launches servers inside a restricted sandbox and raw uv cannot write
~/.cache/uv, use the checked-in launcher instead:
[mcp_servers.mobile_mcp]
command = "/absolute/path/to/mobile-mcp/scripts/mobile-mcp-launch"
args = [
"--config",
"/absolute/path/to/mobile-mcp/configs/runtime.default.json",
"--register-adb-devices",
"--transport",
"sdk-stdio"
]
startup_timeout_sec = 60scripts/mobile-mcp-launch prefers /absolute/path/to/mobile-mcp/.venv/bin/python3, exports
the repo src/ onto PYTHONPATH, and otherwise falls back to uv --no-sync with
UV_CACHE_DIR=/tmp/mobile-mcp-uv-cache, which is friendlier to sandboxed Codex and Inspector
launchers.
If you want a deterministic local device instead of a connected Android device, replace
--register-adb-devices with --register-demo-device.
Auto-register connected real devices from local adb:
python3 -m mobile_mcp.bootstrap \
--config configs/runtime.default.json \
--register-adb-devices \
--transport sdk-stdioIf adb discovery is temporarily unavailable during bootstrap, the runtime now logs a warning
and still starts with zero registered adb devices. Pass --adb-discovery-required to keep the
previous fail-fast behavior.
Run one bounded live operator check against a connected device:
python3 -m mobile_mcp.operator_checks \
--config configs/runtime.default.json \
--check-clipboardRun paired live acceptance checks (lease isolation + paired workflow evidence):
python3 -m mobile_mcp.operator_checks \
--config configs/runtime.default.json \
--multi-device \
--pair-key chat-syncRun MCP client acceptance checks (initialize/list_tools/call_tool) across SDK stdio and SDK HTTP:
python3 -m mobile_mcp.client_acceptance \
--config configs/runtime.default.json \
--register-bridge-devices \
--require-deviceLegacy transports remain supported but are not the canonical operator path:
--transport stdio— line-oriented JSON-RPC over stdin/stdout (custom envelope)--transport http— HTTP with/mcp/toolsand/mcp/call(custom envelope)
Legacy stdio: provide one JSON tool call per line:
{"name":"devices.list"}
{"name":"exit"}Legacy HTTP endpoints:
GET /mcp/toolsreturns tool metadataPOST /mcp/calldispatches one tool call payload
The repository scaffold mirrors the ADR layers under src/mobile_mcp/:
mcp/for the control planesessions/anddevices/for orchestration and leasingobservation/,actions/, andbackends/for executionworkflow/for retry, recovery, and loop policycapabilities/,cache/,artifacts/, andmemory/for dynamic capability, semantic cache, evidence, and agent continuity
Future work must pass the ADR gate before it is considered compliant:
python3 scripts/adr_guard.py- GitHub workflow in
.github/workflows/adr-gate.yml - PR checklist in
.github/pull_request_template.md - ADR impact template in
docs/templates/adr-impact-assessment.md
Execution order and state are tracked separately from the roadmap:
TODO.mdis the explicit task queue derived from the roadmapTASK.lockis the current workspace state snapshot that must be updated as work progresses