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) and src/server.ts (boots an Express app).
  • Device‑specific logic lives in src/ios.ts, src/android.ts, and src/iphone-simulator.ts. Each module exports functions such as installApp, launchApp, tap, swipe, and snapshot that wrap platform tools (xcrun, adb, go-ios, WebDriverAgent).
  • src/mobile-device.ts defines the MobileDevice class that normalises the platform‑agnostic commands and is instantiated by the request handlers in src/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

  1. Process startnpm start (or node dist/server.js after npm run build) runs src/server.ts.
  2. Express bootstrapserver.ts creates an express() instance, applies JSON body parsing, and registers routes defined in src/index.ts via app.use('/mcp', router).
  3. Router → handler – Each MCP method maps to a controller function in src/index.ts. For example, the POST /mobile_tap route calls mobilecli.tap(request.body).
  4. CLI façadesrc/mobilecli.ts is a thin façade that validates input, logs the call, and forwards to the appropriate device implementation: If request.body.platform === 'ios' it imports src/ios.ts and invokes iosTap(...). If 'android' it imports src/android.ts and invokes androidTap(...).
  5. Device modulesios.ts and android.ts each instantiate a MobileDevice (from mobile-device.ts) configured with the target UDID, then call low‑level helpers: iOS uses xcrun simctl (simulator) or go-ios + WebDriverAgent (real device). Android uses adb commands.
  6. 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-ios tunnel) 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.tsindex.tsmobilecli.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:

  1. Start the MCP server on a hosted macOS runner.
  2. Use an LLM‑driven agent to call mobile_launch_app, mobile_tap, and mobile_get_snapshot in sequence, driving the app on an iPhone simulator.
  3. After each step, the agent parses the structured snapshot returned by mobile_get_snapshot to 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 strictnesseslint.config.mjs present, but tsconfig.json does not enforce noImplicitAny or strictNullChecks.
  • 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.