The Problem
Mobile teams must juggle separate tooling for iOS (XCUITest, simulators) and Android (Espresso, emulators) to automate UI flows or scrape data. Maintaining two code‑bases, handling device‑specific drivers, and exposing a stable API to LLM‑powered agents quickly becomes a bottleneck, especially when the same automation logic needs to run on real phones, simulators, or CI‑hosted emulators.
What This Does
mobile-mcp implements a Model Context Protocol (MCP) server that abstracts iOS and Android devices behind a single JSON‑over‑HTTP interface.
- Core entry points are
src/index.ts(exports the public API) andsrc/server.ts(boots an Express app). - Device‑specific logic lives in
src/ios.ts,src/android.ts, andsrc/iphone-simulator.ts. Each module exports functions such asinstallApp,launchApp,tap,swipe, andsnapshotthat wrap platform tools (xcrun,adb,go-ios, WebDriverAgent). src/mobile-device.tsdefines theMobileDeviceclass that normalises the platform‑agnostic commands and is instantiated by the request handlers insrc/server.ts.- Utility helpers (
src/image-utils.ts,src/logger.ts,src/utils.ts) provide screenshot handling, logging and generic helpers used across the stack.
The server therefore offers a uniform set of MCP methods—e.g. mobile_list_available_devices, mobile_tap, mobile_get_snapshot—that LLM agents can call without any knowledge of the underlying OS.
How It Is Wired
- Process start –
npm start(ornode dist/server.jsafternpm run build) runssrc/server.ts. - Express bootstrap –
server.tscreates anexpress()instance, applies JSON body parsing, and registers routes defined insrc/index.tsviaapp.use('/mcp', router). - Router → handler – Each MCP method maps to a controller function in
src/index.ts. For example, the POST/mobile_taproute callsmobilecli.tap(request.body). - CLI façade –
src/mobilecli.tsis a thin façade that validates input, logs the call, and forwards to the appropriate device implementation: Ifrequest.body.platform === 'ios'it importssrc/ios.tsand invokesiosTap(...). If'android'it importssrc/android.tsand invokesandroidTap(...). - Device modules –
ios.tsandandroid.tseach instantiate aMobileDevice(frommobile-device.ts) configured with the target UDID, then call low‑level helpers: iOS usesxcrun simctl(simulator) orgo-ios+ WebDriverAgent (real device). Android usesadbcommands. - Side‑effects – The only external effects are: Filesystem – screenshots saved to a temporary directory via
image-utils.ts. Network – optional device tunnel setup (e.g.,go-iostunnel) but the server itself does not open outbound connections beyond the HTTP API. * Device I/O – all commands are executed via child processes (child_process.exec) inside the platform modules.
The wide‑blast component is src/mobilecli.ts; any change to request validation or logging propagates to every MCP endpoint because every route funnels through it. The rest of the graph is a shallow tree: router → CLI façade → platform module → MobileDevice → OS command.
No circular imports are present; the module hierarchy is strictly top‑down from server.ts → index.ts → mobilecli.ts → platform files.
How To Use It
# Clone the exact repo
git clone https://github.com/moses-y/mobile-mcp.git
cd mobile-mcp
# Install dependencies (npm is the declared package manager)
npm ci
# Build the TypeScript sources
npm run build # defined in package.json, outputs to dist/
# Start the MCP server (default port 3000)
npm start # runs node dist/server.js
Configuration – device discovery and connection details are read from server.json (present in the repo root). The file contains keys such as androidSdkPath, iosXcodePath, and optional tunnel settings; adjust them to match your local environment.
API consumption – send JSON RPC calls to http://localhost:3000/mcp. Example using curl:
curl -X POST http://localhost:3000/mcp/mobile_list_available_devices \
-H "Content-Type: application/json" \
-d '{}'
The response follows the MCP spec and includes an array of device descriptors (udid, platform, type).
Real‑World Use
A CI pipeline that validates a new mobile‑checkout flow could:
- Start the MCP server on a hosted macOS runner.
- Use an LLM‑driven agent to call
mobile_launch_app,mobile_tap, andmobile_get_snapshotin sequence, driving the app on an iPhone simulator. - After each step, the agent parses the structured snapshot returned by
mobile_get_snapshotto verify UI state, without needing image‑recognition models.
await fetch('/mcp/mobile_launch_app', {method:'POST', body:JSON.stringify({udid:'SIM-123', bundleId:'com.example.app'})});
await fetch('/mcp/mobile_tap', {method:'POST', body:JSON.stringify({udid:'SIM-123', selector:'loginButton'})});
const snap = await fetch('/mcp/mobile_get_snapshot', {method:'POST', body:JSON.stringify({udid:'SIM-123'})});
Code Health & Issues
- Low – No test coverage for
src/server.ts– 11 test files exist, but none target the HTTP entry point; changes to routing may go unchecked. - Low – Missing explicit TypeScript lint config for strictness –
eslint.config.mjspresent, buttsconfig.jsondoes not enforcenoImplicitAnyorstrictNullChecks. - Low – No CI step for end‑to‑end device integration – GitHub Actions workflow (
.github/workflows/build.yml) builds and lints only; it does not spin up simulators/emulators to run integration tests.
Static analysis found no broken imports, duplicate files, or missing license headers; the repository includes standard compliance files (LICENSE, CODE_OF_CONDUCT.md, SECURITY.md).
The Bottom Line
mobile-mcp delivers a clean, single‑API façade for iOS and Android automation, backed by a modest codebase that separates platform concerns cleanly. It is ready for internal tooling or LLM‑driven agents, but production teams should add integration tests that exercise real devices and tighten TypeScript strictness before extensive deployment.