Mobile MCP
Drives iOS and Android simulators, emulators and real devices from an assistant
Drives a real iPhone from a Mac through Apple's iPhone Mirroring: see the screen, tap, swipe and type
By Jeanfrancois Arcand (individual) Swift Local · stdio TestingDeveloper tools
64 popularity score ★ 283 ↓ 2.0k/wk
Opens the app, which asks before it adds the server.
claude mcp add mirroir -- npx -y mirroir-mcp{
"mcpServers": {
"mirroir": {
"command": "npx",
"args": [
"-y",
"mirroir-mcp"
]
}
}
}{
"mcpServers": {
"mirroir": {
"command": "npx",
"args": [
"-y",
"mirroir-mcp"
]
}
}
}{
"servers": {
"mirroir": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"mirroir-mcp"
]
}
}
}[mcp_servers.mirroir]
command = "npx"
args = ["-y", "mirroir-mcp"]gemini mcp add --scope user mirroir npx -- -y mirroir-mcpOpens the app, which asks before it adds the server. Then replace each <NAME> in its settings with your value.
git clone https://github.com/jfarcand/mirroir-mcp.git
cd mirroir-mcp
./mirroir.shclaude mcp add mirroir -- '<ABSOLUTE_PATH_TO_CLONE>/.build/release/mirroir-mcp'{
"mcpServers": {
"mirroir": {
"command": "<ABSOLUTE_PATH_TO_CLONE>/.build/release/mirroir-mcp",
"args": []
}
}
}{
"mcpServers": {
"mirroir": {
"command": "<ABSOLUTE_PATH_TO_CLONE>/.build/release/mirroir-mcp",
"args": []
}
}
}{
"servers": {
"mirroir": {
"type": "stdio",
"command": "<ABSOLUTE_PATH_TO_CLONE>/.build/release/mirroir-mcp",
"args": []
}
}
}[mcp_servers.mirroir]
command = "<ABSOLUTE_PATH_TO_CLONE>/.build/release/mirroir-mcp"
args = []gemini mcp add --scope user mirroir '<ABSOLUTE_PATH_TO_CLONE>/.build/release/mirroir-mcp'| Set together: vision | |
|---|---|
MIRROIR_SCREEN_DESCRIBER_MODE |
Optional: auto, ocr or vision. auto resolves to local OCR, so vision must be set explicitly. |
MIRROIR_AGENT |
Optional: Agent used for vision mode and diagnosis. Empty by default, which means embacle in vision mode. |
MIRROIR_AGENT_TRANSPORT |
Optional: auto uses the embedded embacle runtime when linked, http forces HTTP to the agent's base URL. |
MIRROIR_OCR_BACKEND |
Optional: Local recognition backend: auto, vision, yolo or both. auto adds YOLO only when a model is installed. |
This is a native macOS server, written in Swift, that operates an actual iPhone through the iPhone Mirroring window on a Mac. It works from what is on screen. describe_screen runs Apple's Vision text recognition locally and hands back each text element with coordinates you can tap. Pass query and it lists only the elements holding that text. The other tools turn coordinates into mouse and keyboard events aimed at the mirroring window, so the phone receives gestures and keystrokes as if a person were using the Mac. tap also accepts a piece of visible text and finds it by OCR. Helpers open apps through Spotlight, toggle network switches in Settings, force-quit an app or record the screen to a .mov file.
The tool added most recently is run_steps. You hand it a list of short lines in the skill grammar ("launch: Weather", "tap: Get", "wait_for: Results") and it plays them in one call. Every line is checked against the permission policy before anything moves, and the run halts at the first step that fails.
On top of single actions sits a skills system. A skill is a SKILL.md or YAML workflow the assistant follows step by step. Skills can be generated by exploring an app. Once compiled, they replay without screen reading, which is how they run in CI. save_compiled can also write the walk out as a mirroir-run scenario. A YOLO CoreML model placed in ~/.mirroir-mcp/models/ adds icon detection to the text results. macOS windows can be driven too, as an experimental extra target.
There are 39 tools in all, and 12 of them are exposed with no permissions file. Linking embacle won't switch describe_screen to AI vision. In mirroir_mcp.swift the default auto mode resolves to local OCR, because the vision path places elements near the bottom of the screen too high. Vision runs only when screenDescriberMode is set to vision. It then uses the agent named in the agent setting, or embacle if none is given, and drops back to OCR when that name can't be resolved.
| Runtime | Node.js |
|---|---|
| Runs on | macOS |
| Also |
|
macOS asks for Screen Recording and Accessibility on first use and both must be granted. Running `npx -y mirroir-mcp doctor` after install names anything still missing. The npx command needs Node.js, and the npm package downloads a prebuilt macOS binary from the project's GitHub releases. A .mcpb bundle from the releases page carries the Apple Silicon binary and needs no Node.js. Building from source needs the Swift toolchain. Vision mode needs embacle-ffi from Homebrew (brew tap dravr-ai/tap, brew install embacle-ffi), a rebuild, screenDescriberMode set to vision, and a signed-in GitHub Copilot or Claude Code CLI. pinch and rotate need a built-in trackpad or a Magic Trackpad.
With no permissions.json, only the 12 read-only tools are listed; tap, type_text, launch_app and every other tool that changes something stay hidden until allowed in ~/.mirroir-mcp/permissions.json or the server runs with --dangerously-skip-permissions. iPhone Mirroring exposes one pointer touch, so touch-only games cannot get two independent fingers; pinch and rotate arrive as a trackpad gesture that games reading raw touches ignore, and the project aims at game QA, tutorials and demos, not real-time play. set_network depends on a Settings layout that varies by locale and iOS version. Non-US keyboards need `mirroir configure`. macOS window support is experimental.
Suits iOS developers and QA people who want an assistant to run smoke tests or walk through app flows on a physical iPhone without a jailbreak or an on-device agent, and who already work on a Mac with iPhone Mirroring. It needs macOS 15 and a paired phone, so Linux and Windows hosts are out, as are Android devices. Since it acts through screen recognition and synthetic input rather than app internals, fast real-time games and tests that need true multi-touch are a poor fit.
10 only read, 29 change things. A ? marks an optional parameter.
screenshotsettle_ms?: integerscale?: numberregion?: arraydescribe_screenscroll?: booleanomit_screenshot?: booleanquery?: stringtext_only?: booleansettle_ms?: integerstart_recording changesoutput_path?: stringstop_recording changestap changestext?: stringx?: numbery?: numbercursor_mode?: enumdouble_tap changesx: numbery: numbercursor_mode?: enumlong_press changesx: numbery: numberduration_ms?: numbercursor_mode?: enumswipe changesfrom_x: numberfrom_y: numberto_x: numberto_y: numberduration_ms?: numbercursor_mode?: enumdrag changesfrom_x: numberfrom_y: numberto_x: numberto_y: numberduration_ms?: numbercursor_mode?: enumtouch changesaction: enumx?: numbery?: numberduration_ms?: numberpinch changesx: numbery: numberscale: numberduration_ms?: numberrotate changesx: numbery: numberdegrees: numberduration_ms?: numberhold_keys changeskeys: arrayduration_ms?: numberdrag?: objecttype_text changestext: stringpress_key changeskey: stringmodifiers?: arrayshake changeslaunch_app changesname: stringopen_url changesurl: stringpress_home changespress_app_switcher changespress_back changesspotlight changesscroll_to changeslabel: stringdirection?: enummax_scrolls?: integerreset_app changesname: stringmeasure changesaction: stringuntil: stringmax_seconds?: numbername?: stringrun_steps changessteps: arrayset_network changesmode: enumget_orientationstatuscheck_healthlist_skillsget_skillname: stringgenerate_skill changesaction: enumapp_name?: stringgoal?: stringgoals?: arrayarrived_via?: stringaction_type?: stringmax_depth?: integermax_screens?: integermax_time?: integerstrategy?: enumfresh?: booleanseed?: integerskip_calibration?: booleanexplorer?: enumemit?: booleanoutput_dir?: stringlist_targetsswitch_target changestarget: stringcalibrate_componentcomponent_path: stringtarget?: stringscroll?: booleanclassify_screentarget?: stringrecord_step changesstep_index: integertype: stringlabel?: stringtap_x?: numbertap_y?: numbermatch_strategy?: stringelapsed_ms?: integerscroll_count?: integerscroll_direction?: stringsave_compiled changesskill_name: stringemit?: booleanoutput_dir?: string