ios-device-mcp
Drive a real iPhone/iPad over USB through MCP to build, install, launch, inspect, and interact with apps and device UI.
List and select connected devices and installed apps.
Build Xcode schemes and install/uninstall apps.
Launch, relaunch, terminate apps, and capture/read/filter console logs.
Take screenshots and inspect the accessibility tree.
Tap, double-tap, long-press, swipe, scroll, find/wait for elements, and type text.
Press hardware buttons, set orientation, and open URLs/deep links.
Run Siri interactions, App Shortcuts/App Intents, and Action/Camera button actions.
Set media volume and record Mac microphone audio, including around taps.
List and pull files from app data containers.
Start, stop, and rebuild the on-device XCUITest driver.
Target multiple devices by passing a device per call.
Provides tools for driving a real iPhone or iPad over USB for iOS app development testing: building and installing apps, launching with console log capture, reading the accessibility tree, performing taps, swipes and typing, taking screenshots, and recording audio.
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., "@ios-device-mcp launch my app on the iPhone and show me the login screen's accessibility tree"
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.
ios-device-mcp

An MCP server that drives a real iPhone or iPad (not the simulator) over USB for app development testing. It can build and install your app, launch it with console log capture, read the accessibility tree, tap, swipe, type and take screenshots. It can also talk to Siri and press the Action button, so an agent can run your App Shortcuts and App Intents on the phone.
More about it: individuation.dev/projects/ios-device-mcp.
How it works
devicectl(Xcode's CoreDevice CLI) lists devices and installs, uninstalls and launches apps.runner/is a small XCUITest bundle whose single test never ends. It runs an HTTP server on the device (port 22087) and maps requests to XCUITest calls (XCUICoordinate.tap,typeText,snapshot(),XCUIScreen.screenshot()…).src/is the Node MCP server. It builds the runner withxcodebuild build-for-testing, starts it withxcodebuild test-without-building, and reaches it through the device's CoreDevice tunnel address (fd…::1). The tunnel works over USB and Wi-Fi with no port forwarding.
The driver starts automatically the first time a UI tool runs, takes about 5–8 s to start, and stops when the MCP server exits.
Related MCP server: open_phone
Requirements
macOS with Xcode 16+ (tested with Xcode 27.1) and
xcodegen(brew install xcodegen);ffmpegfor audio recording (brew install ffmpeg)Node 20+
An iPhone/iPad that is paired, has Developer Mode on, is unlocked, and is registered in your developer team
Setup
git clone <repo-url> ios-device-mcp && cd ios-device-mcp
npm install && npm run buildYour Team ID is in Xcode > Settings > Accounts (select the team), or on developer.apple.com under Membership. Then register the server with Claude Code for all your projects:
claude mcp add --scope user ios-device \
-e IOS_DEVICE_MCP_TEAM_ID=<TEAMID> \
-- node "$PWD/dist/index.js"Other MCP clients: run node dist/index.js over stdio with the same env vars. See .mcp.example.json for a project-scoped config.
Env var | Purpose |
| Required. Team used to sign the runner |
| Runner bundle ID prefix (default |
| UDID or name of the device to use |
|
|
| Driver port (default 22087) |
| Mac mic name substring for recordings (default: first non-iPhone/iPad input) |
| Where WAVs go (default |
The pre-rename IPHONE_MCP_* names are still read as a fallback.
Device selection: every tool takes a device argument (UDID or name). Without one, a tool uses the device chosen with select_device, then IOS_DEVICE_MCP_DEVICE, then the one device physically connected over USB. It never picks a Wi-Fi-only device implicitly, and with several on USB it asks for a choice.
Several devices at once: each device gets its own driver, so you can drive two phones in one session by passing device per call, or select_device one and pass device only for the other. Runner builds for different devices share one build directory and run one after another.
First run on a device: iOS asks for the passcode to allow UI automation. Keep the device unlocked while the driver starts.
Agent skill (optional)
The repo also ships a skill that teaches your agent how to use these tools well: the build → launch → describe → act → verify loop, element IDs over coordinates, waiting instead of sleeping, and the device-safety rules. Install it with the skills CLI:
npx skills add ChrisThoma/ios-device-mcpOr copy skills/ios-device-mcp into ~/.claude/skills/. The skill only
guides the agent; the MCP server above is what does the work.
Tools
Tool | What it does |
| Show devices (the current default is marked) and installed apps |
| Set the device tools use when they don't pass |
|
|
| Install a |
| Launch/relaunch; |
| Read captured output; |
| Force-quit an app |
| List / copy files from an app's data container (dev-signed apps). |
| JPEG scaled to points, so image pixels equal tap coordinates |
| Compact accessibility tree with |
| Coordinate gestures (points). |
| Record the Mac mic for N seconds |
| Set media volume to a level (0–1, 16 steps), verified on the system volume indicator |
| Find by text / accessibility ID / type, with waiting and |
| Type into the focused field ( |
|
|
| Send text to Siri as if spoken, or tap something it shows, and read back what it shows. Its first-run "Turn on shortcuts" prompt only works from Type to Siri (double-tap the bottom edge) |
| Open a URL or deep link |
| Rotate the interface |
| Manage the on-device driver explicitly ( |
Text matching in tap_element tries exact label/ID/value matches first, then whole-word matches in short labels. Every result says which element was tapped. Give your app accessibility identifiers and match with id for the most reliable tests.
Testing Siri, App Shortcuts and the Action button
Tested on an iPhone 15 Pro Max with iOS 27.0.1 and Xcode 27.1, where the Action button was pressed as well. The Camera button hasn't been tried on a device yet.
The siri tool gives Siri a phrase as if it had been spoken and returns the text Siri shows. It really runs, so this tests the path from the phrase through your App Intent to what Siri says back:
siri text: "Check MyApp"
→ the text Siri shows, one line per element
siri tap: "Turn On"
→ taps a button or row Siri is showing
press_button name: "action"
→ runs what the Action button is set toSiri's own UI: use
taponsiri. A coordinate tap goes to the app behind Siri and dismisses it.Timing: Siri has no finished signal, so the tool reads the screen after a fixed wait. Raise
wait(default 6 s) for a slow intent.First run of an app's shortcuts: the "Turn on shortcuts with Siri?" prompt doesn't work after text sent this way. It fails with "Something went wrong", with the logs putting the failure inside Siri, before the app. Use Type to Siri once instead:
double_tapthe bottom edge of the screen, tap the "Ask Siri" field so the keyboard shows,type_textthe phrase, then tap the send arrow at the field's right end by its coordinates in a screenshot. Return adds a new line, and while the keyboard is up Siri reports its buttons below the screen, sosiri tap: "Submit"hits a key. When the prompt shows, usesiri tap: "Turn On". After that, text sent withsiriworks.Action button: the tool presses it but doesn't change what it's set to. Choose the action in Settings first.
Known limitations
Foreground app: detected from the last launched app, else by scanning installed apps' states, else SpringBoard.
Taps go to the foreground app: coordinates are anchored to the app in front. Anchored to SpringBoard, a tap on an in-app alert button also reached the view behind the alert. A tap inside a system alert (permission prompt) is anchored to SpringBoard instead: anchored to the app, XCTest's interruption handling answered the alert itself, so "Don’t Allow" granted the permission.
Home button:
XCUIDevice.press(.home)is ignored on some iOS 26 builds, so the driver falls back to activating SpringBoard. The press takes about 2–3 s.Landscape: coordinates are screen points in the current orientation, and landscape has had little testing.
Logs:
os_log/Loggercapture works like Xcode's console: the app is launched withOS_ACTIVITY_DT_MODE(andOS_ACTIVITY_MODE=debug), which mirrors its log messages to stderr. Only messages from the app's own process are captured, not from system daemons or extensions. Private values show in plain text. Lines show[category]but not the subsystem.Recording sync: recordings use
ffmpeg(brew install ffmpeg), and the terminal needs microphone permission. Recording start is measured to about ±70 ms. XCUITest takes about 0.4–0.7 s to deliver a tap, so the result gives a window ("sent at … completed at …"), and in testing the sound always landed inside it.Volume: iOS has no API to set the volume, and the level an app can read goes stale.
set_volumepresses the buttons and reads SpringBoard's volume indicator to verify the level, so the indicator briefly shows on screen.Siri: phrases are sent as text, so this doesn't test how Siri hears them. Anything Siri can do happens for real, including sending messages.
Camera button:
press_buttonwithcamerahasn't been tried on a device yet. The Action button has, on an iPhone 15 Pro Max.No lock/unlock or Face ID simulation.
License
MIT. See LICENSE.
Available Tools
29 toolsbuild_appA
Build an Xcode project/workspace scheme for the device and (by default) install it. Returns the bundle ID. Signing uses the project's own settings.
Inputs: scheme (required), device, install, project, +2 more.
describe_uiA
Accessibility tree of the foreground app (plus any system alert), one element per line: Type "label" #identifier value=… @(centerX,centerY) WxH. Coordinates are in points and can be passed to tap.
Inputs: device, format, bundleId, includeOffscreen.
double_tapB
Double-tap at coordinates (points).
Inputs: x (required), y (required), device.
get_app_logsA
Read console output captured from an app started with launch_app captureLogs=true. os_log/Logger lines look like <timestamp> App[pid:tid] [category] message.
Inputs: bundleId (required), grep, clear, lines, +1 more.
install_appB
Install a built .app (or .ipa) onto the device.
Inputs: path (required), device.
launch_appB
Launch (or relaunch) an app in the foreground. With captureLogs, the app's console output — print/NSLog and os_log/Logger messages — is buffered for get_app_logs.
Inputs: bundleId (required), device, logLevel, arguments, +2 more.
list_app_filesA
List files in an app's data container (Documents, Library, tmp…). Works for development-signed apps only.
Inputs: bundleId (required), device, subdirectory.
list_appsC
List apps installed on the device.
Inputs: device, includeSystem.
list_devicesA
List physical iPhones/iPads known to this Mac, with connection state. The device tools use by default is marked.
Inputs: no named parameters.
long_pressB
Press and hold at coordinates (points).
Inputs: x (required), y (required), device, duration.
open_urlC
Open a URL or deep link (custom scheme or universal link) on the device.
Inputs: url (required), device.
press_buttonA
Press a hardware button. "home" goes to the home screen. "action" and "camera" run whatever the phone has them set to, and fail on a device without that button.
Inputs: name (required), device.
pull_fileB
Copy a file out of an app's data container to the Mac. Can wait for the file to appear, or to be written after the last launch_app. Small text files are returned inline.
Inputs: path (required), bundleId (required), device, destination, +2 more.
record_audioA
Record the Mac microphone for a number of seconds (e.g. to capture what the device plays). Returns the WAV path.
Inputs: seconds (required), audioDevice.
screenshotA
Take a screenshot. By default it is scaled to screen points, so image pixel coordinates equal tap coordinates.
Inputs: device, fullRes.
scrollA
Scroll the content in a direction ("down" reveals content further down) by swiping from the middle of the screen.
Inputs: direction (required), amount, device.
select_deviceA
Choose which device later calls use when they don't pass "device". Lasts for this server session and is shared by every agent using it, so parallel agents on different devices should pass "device" instead. Omit device to clear the choice.
Inputs: device.
set_orientationB
Rotate the device's interface orientation.
Inputs: orientation (required), device.
set_volumeA
Set the device's media volume by pressing the volume buttons toward the level, verifying each step on the system volume indicator. iOS volume has 16 steps, so levels round to the nearest 1/16. Returns the final level.
Inputs: level (required), device.
siriA
Send text to Siri as if it had been spoken, or tap something Siri is showing, then return the text Siri shows. Use tap for Siri's buttons, because a coordinate tap goes to the app behind Siri and dismisses it. Anything Siri can do runs for real, including App Intents. An app's first-run "Turn on shortcuts with Siri?" prompt fails with "Something went wrong" after text sent this way. Use Type to Siri for that once: double_tap the bottom edge of the screen, tap the "Ask Siri" field so the keyboard shows, type_text the phrase, then tap the send arrow at the field's right end by coordinates from a screenshot (return adds a line, and while the keyboard is up Siri reports its buttons below the screen, so tap: "Submit" hits a key). When the prompt shows, use tap: "Turn On". Later phrases work with text.
Inputs: tap, text, wait, device.
start_driverA
Start the on-device XCUITest driver (UI tools start it automatically). The first run on a device builds the runner and iOS asks for the passcode to allow UI automation.
Inputs: device, rebuild.
stop_driverC
Stop the on-device driver.
Inputs: device.
swipeC
Drag from one point to another (points).
Inputs: toX (required), toY (required), fromX (required), fromY (required), +2 more.
tapB
Tap at screen coordinates (points).
Inputs: x (required), y (required), device, recordAudio.
tap_elementB
Find an element by text/identifier/type, waiting for it to appear, and tap its center.
Inputs: id, text, type, exact, +6 more.
terminate_appC
Force-quit an app.
Inputs: bundleId (required), device.
type_textA
Type into the focused text field (tap the field first). Use \n for return.
Inputs: text (required), device, submit, deleteCount.
uninstall_appB
Uninstall an app (and its data) from the device.
Inputs: bundleId (required), device.
wait_for_elementB
Wait until an element appears (or disappears with gone=true).
Inputs: id, gone, text, type, +6 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 29 tools
Most tools target a clearly distinct action: tap, double_tap, long_press, swipe, and type_text don't collide, and app lifecycle tools (build/install/uninstall/launch/terminate) are cleanly separated. The only soft overlap is scroll versus swipe (scroll is a convenience wrapper around swiping from the middle) and tap versus tap_element, but the descriptions make the intended usage clear enough.
Overwhelmingly consistent snake_case verb_noun (type_text, press_button, list_apps, launch_app, get_app_logs, set_orientation) with a few bare single-verb names like tap, swipe, scroll, screenshot, and siri. The deviations are short, well-understood primitives rather than a different convention, so the pattern remains predictable.
29 tools is on the heavy side for a device-automation surface, and a few are infrastructure-ish (start_driver/stop_driver, which the UI tools start automatically, and select_device) that an agent rarely needs to think about. Each tool does map to a real capability, so it's justified but at the upper edge of comfortable scoping.
The surface covers UI interaction, accessibility inspection, screenshots, audio capture, volume, Siri, orientation, app build/install/launch/lifecycle, logs, and app-container file listing/pulling, which is strong coverage for iOS device control. Minor gaps remain: no push/write file into the container, no video capture, and no device status/info (battery, OS version, network) tool.
Maintenance
Related MCP Connectors
Drive real devices from your AI Coding tool. Embed a client SDK (Unity, Godot, Flutter, iOS/macOS, Android, React Native, Web) in your app, then capture screenshots, traverse the UI tree, inject taps and key events, and run automated test tasks on the physical device over a secure relay.
remote debug iOS/Android/Unity/Godot/Flutter/RN/Web on real-device.ui-tree/screenshots/taps,tests.
Let your AI agent control real iPhones: screenshots, tap, swipe, type and open apps.
Build, run, and inspect iOS apps in disposable hosted Simulators from cloud coding agents.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to control real iPhones and simulators on macOS, allowing for UI interaction, testing, and automation.65 npm95MIT
- AlicenseNot gradedqualityCmaintenanceEnables agents to control a physical iPhone from macOS through local phone tools, including USB capture, Bluetooth HID, and AssistiveTouch interactions. It also exposes relay tools for remote access over Streamable HTTP with optional OAuth.MIT
- AlicenseBqualityBmaintenanceDrives a physical iPhone or iPad over Appium and WebDriverAgent (XCUITest) from a Mac, letting an agent inspect the screen, tap, type, swipe, launch apps, and run Shortcuts. Safe-by-default access modes, app allowlists, and elicitation-gated destructive operations keep admin actions behind explicit approval.645 npm1MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to operate a real iPhone from a Mac by reading the screen over USB and sending taps, swipes, typing, and app launches over Bluetooth, with every action returning a fresh screenshot so the agent can verify its work. Runs as a local MCP server, so agents can test iOS apps on physical devices and automate everyday phone tasks.38 npm10MIT