Under the Hood

How iphone-use works

One daemon per phone runs on your Mac. It reads the screen as an accessibility tree (text, not pixels), sends taps and typing through a small XCTest runner it installs on the iPhone, and checks that the screen has settled before it answers. Agents and people go through the same daemon, and one of them holds the phone at a time.

architecture
 Claude Code / Codex / any MCP client ──MCP──┐
 scripts and other agents ─────────────HTTP───
                                             β–Ό
                          iphone-use daemon on your Mac ──USB or Wi-Fi──▢ XCTest runner on the iPhone
                                             β–²
 you, in a browser or the iOS app β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The daemon

The daemon runs as a per-user LaunchAgent and listens on 127.0.0.1:44321 behind a password. It offers the same control in three forms: an MCP server for agents, an HTTP API under /agent/* for scripts, and a web page plus a native iOS app for people (Connect).

The XCTest device runner

iphone-use drives the phone through its own XCTest UI-test bundle (runner/). It replaced WebDriverAgent in v0.14.0 and speaks the same WebDriverAgent HTTP API. It does not use iPhone Mirroring. The runner is built for speed:

iphone-use setup builds and signs the runner with your Xcode team, starts it on the phone, and pins two loopback relays: 8100 for control and 9100 for the screen stream. The relays run over macOS's own usbmuxd (iphone-use relay), so the daemon only ever talks to localhost and no Homebrew packages are needed.

USB and Wi-Fi

ℹ️ Maintainer measurements
From the guide: a tap costs about 330–380 ms over the Wi-Fi tunnel against about 280 ms over USB. On an iPhone 17 Pro Max (iOS 27) an unlocked cold connect is drivable in 13–22 s. On an iPhone X (iOS 16.5) a legacy setup that rebuilt the runner took 141 s; a warm restart of an iPhone 12 mini (iOS 15.4.1) takes about 12 s.

Request signing

The runner's ports on the phone refuse (401, no work done) any request not signed with the token setup generates at each runner launch: HMAC-SHA256 over the method, target, body, a timestamp and a one-time nonce. The token stays on the Mac (mode 0600) and is never sent, so a captured request can be neither replayed nor altered. A runner launched without a token refuses everything. Signing does not encrypt; see Privacy & Security.

Lifecycle: idle release and pre-warm

Giving the phone back

Live view: browser and iOS app

The /phone page shows the live screen as H.264 encoded on the phone by the runner (MJPEG where the browser lacks WebCodecs). Click taps, drag swipes, hold long-presses, the wheel scrolls, and typing goes to the phone through your own input method. An unchanged screen is not re-encoded.

ModePictureStill screenScrolling
performancehalf size, up to 30 fps~0.04 Mbit/s~1.5 Mbit/s
qualitynative size (1320Γ—2868 on a 17 Pro Max), ~50 fps~0.1 Mbit/s~6 Mbit/s

Auto mode watches in quality from this Mac or its LAN and in performance from anywhere else. On screens an app hides from capture (banking, payments), you see a live wireframe of the accessibility tree instead of a blank picture, and can still tap through it.

The native SwiftUI iOS app (minimum deployment target iOS 15) pairs by scanning a QR code on the Mac's page and plays the H.264 feed with the hardware decoder. It is not in the App Store: iphone-use setup-app builds it with your own signing team.

On-phone daemon and local control

The daemon on the phone is the same program as on the Mac, linked into the runner. With a paid developer team, the iOS app's packet-tunnel extension starts the runner on the phone itself, so the phone serves taps, typing, screen reads and the agent API with no Mac at run time (iOS 17.4 or later; the first pairing still needs the USB cable once). A free Apple ID gets the remote app, which drives phones that a Mac serves. Goals handed to the daemon as agent tasks keep running there while another app is in front.

More than one phone

One daemon drives one phone. A named instance (./install.sh --instance lab --udid <UDID>) gets its own copy of the app, state directory, launchd labels, token and ports, and leaves the default install alone. Each phone has its own daemon and runner, so two phones driven at the same time run as fast as either one alone. MCP calls can pick an installed instance per call with instance.

Simulators (preview)

The runner also runs on an iOS Simulator, with no phone, Apple ID or signing: iphone-use simulator start "iPhone 16 Pro" builds and starts everything, then IPHONE_USE_INSTANCE=sim points an MCP client at it. Simulators have no camera, App Store apps, real push notifications or Face ID, so tasks that need those only work on a phone.

Speed

The project's own measurements, from its README (October 2026, same iPhone 13 over USB):

iphone-use runneragent-device
Read the screen (accessibility tree)0.08–0.135 s~0.7 s
Tap, then wait for the screen to settle1.0 s2.8–3.3 s

Through the full agent API, a tap by label that returns the settled change takes about 1.9 s on an iPhone 17 Pro Max; the WebDriverAgent-based release took 4.2 s. The live view streams at 27–28 fps. These are maintainer measurements, not an independent benchmark.

Deeper reading: the full guide, the device runner and the architecture notes.