mirroir-mcp

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

Connect your client

Local (npm)

Add in one click

Opens the app, which asks before it adds the server.

Or add it by command or config

Claude CodeTerminal
claude mcp add mirroir -- npx -y mirroir-mcp
Claude Desktopclaude_desktop_config.json
{
  "mcpServers": {
    "mirroir": {
      "command": "npx",
      "args": [
        "-y",
        "mirroir-mcp"
      ]
    }
  }
}
Cursor~/.cursor/mcp.json
{
  "mcpServers": {
    "mirroir": {
      "command": "npx",
      "args": [
        "-y",
        "mirroir-mcp"
      ]
    }
  }
}
VS Code.vscode/mcp.json
{
  "servers": {
    "mirroir": {
      "type": "stdio",
      "command": "npx",
      "args": [
        "-y",
        "mirroir-mcp"
      ]
    }
  }
}
Codex~/.codex/config.toml
[mcp_servers.mirroir]
command = "npx"
args = ["-y", "mirroir-mcp"]
Gemini CLITerminal
gemini mcp add --scope user mirroir npx -- -y mirroir-mcp
Local (from source)

Add in one click

Opens the app, which asks before it adds the server. Then replace each <NAME> in its settings with your value.

Or add it by command or config

FirstTerminal
git clone https://github.com/jfarcand/mirroir-mcp.git
cd mirroir-mcp
./mirroir.sh
Claude CodeTerminal
claude mcp add mirroir -- '<ABSOLUTE_PATH_TO_CLONE>/.build/release/mirroir-mcp'
Claude Desktopclaude_desktop_config.json
{
  "mcpServers": {
    "mirroir": {
      "command": "<ABSOLUTE_PATH_TO_CLONE>/.build/release/mirroir-mcp",
      "args": []
    }
  }
}
Cursor~/.cursor/mcp.json
{
  "mcpServers": {
    "mirroir": {
      "command": "<ABSOLUTE_PATH_TO_CLONE>/.build/release/mirroir-mcp",
      "args": []
    }
  }
}
VS Code.vscode/mcp.json
{
  "servers": {
    "mirroir": {
      "type": "stdio",
      "command": "<ABSOLUTE_PATH_TO_CLONE>/.build/release/mirroir-mcp",
      "args": []
    }
  }
}
Codex~/.codex/config.toml
[mcp_servers.mirroir]
command = "<ABSOLUTE_PATH_TO_CLONE>/.build/release/mirroir-mcp"
args = []
Gemini CLITerminal
gemini mcp add --scope user mirroir '<ABSOLUTE_PATH_TO_CLONE>/.build/release/mirroir-mcp'

Settings 4

Set together: vision
MIRROIR_SCREEN_DESCRIBER_MODE Optional: auto, ocr or vision. auto resolves to local OCR, so vision must be set explicitly. Default auto. For describe_screen.
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. Default auto.
MIRROIR_OCR_BACKEND Optional: Local recognition backend: auto, vision, yolo or both. auto adds YOLO only when a model is installed. Default auto. For describe_screen.

What it does

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.

What it needs

RuntimeNode.js
Runs onmacOS
Also
  • macOS 15 or later
  • An iPhone paired with the Mac through iPhone Mirroring
  • Screen Recording and Accessibility permissions granted to the server

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.

Limits

  • Not covered With no permissions.json only the 12 read-only tools are listed. tap, type_text, run_steps and the other 24 changing tools stay hidden until allowed or --dangerously-skip-permissions is given.
  • Not covered iPhone Mirroring gives one pointer, so touch games cannot get two independent fingers. Its makers aim it at game QA, tutorials and demos, not real-time play.
  • Not covered pinch and rotate arrive as a trackpad gesture, which games that read raw touches ignore.
  • Limit set_network relies on the Settings layout, which differs by locale and iOS version.
  • Limit Keyboards other than US need a one-time `mirroir configure` to type correctly.
  • Early release Driving macOS windows as targets is experimental.
  • Limit AI vision mode is opt-in: auto uses local OCR even with embacle linked, and an agent name that cannot be resolved falls back to OCR.
  • Limit run_steps refuses the whole list when any one step is not allowed by the permission policy.
  • Platform Runs only on macOS 15+ with iPhone Mirroring, so no Linux, Windows or Android.

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.

Who it suits

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.

Tools 39

10 only read, 29 change things. A ? marks an optional parameter.

screenshot
Captures the mirrored iPhone screen as a PNG image, optionally cropped or scaledsettle_ms?: integerscale?: numberregion?: array
describe_screen
Lists on-screen text elements with tap coordinates, optionally scrolling, filtering by text or omitting the imagescroll?: booleanomit_screenshot?: booleanquery?: stringtext_only?: booleansettle_ms?: integer
start_recording changes
Begins a video recording of the mirrored screenoutput_path?: string
stop_recording changes
Ends the recording and gives back the .mov file path
tap changes
Taps on visible text found by OCR, or at a point in the mirroring windowtext?: stringx?: numbery?: numbercursor_mode?: enum
double_tap changes
Taps twice quickly, for zooming or selecting textx: numbery: numbercursor_mode?: enum
long_press changes
Presses and holds at a point, 500 ms by defaultx: numbery: numberduration_ms?: numbercursor_mode?: enum
swipe changes
Swipes from one point to another, 300 ms by defaultfrom_x: numberfrom_y: numberto_x: numberto_y: numberduration_ms?: numbercursor_mode?: enum
drag changes
Drags slowly between two points for icons and sliders, 1000 ms by defaultfrom_x: numberfrom_y: numberto_x: numberto_y: numberduration_ms?: numbercursor_mode?: enum
touch changes
Keeps one finger down across calls with begin, move, end or cancelaction: enumx?: numbery?: numberduration_ms?: number
pinch changes
Two-finger zoom gesture around a point by a scale factorx: numbery: numberscale: numberduration_ms?: number
rotate changes
Two-finger rotation gesture around a point by a number of degreesx: numbery: numberdegrees: numberduration_ms?: number
hold_keys changes
Holds keys down for a set time, optionally while dragging with a mouse buttonkeys: arrayduration_ms?: numberdrag?: object
type_text changes
Types a string into the phone as keystrokestext: string
press_key changes
Sends a special key such as return, escape or an arrow, with optional modifierskey: stringmodifiers?: array
shake changes
Triggers the shake gesture used for undo and developer menus
launch_app changes
Opens an app by name through Spotlight, unless blockedApps lists itname: string
open_url changes
Opens a web address in Safari on the phoneurl: string
press_home changes
Returns to the home screen
press_app_switcher changes
Shows the app switcher
press_back changes
Goes back by finding and tapping the back chevron
spotlight changes
Opens Spotlight search
scroll_to changes
Scrolls until a given text label is visible, upward by defaultlabel: stringdirection?: enummax_scrolls?: integer
reset_app changes
Force-quits an app from the app switchername: string
measure changes
Performs an action and times how long until a label appearsaction: stringuntil: stringmax_seconds?: numbername?: string
run_steps changes
Runs a list of skill-grammar steps in one call and stops at the first failuresteps: array
set_network changes
Turns airplane mode, Wi-Fi or cellular on or off through Settingsmode: enum
get_orientation
Reports portrait or landscape and the window size
status
Shows connection state, window geometry and readiness
check_health
Checks mirroring, Accessibility and screen capture setup
list_skills
Lists skill files from the project and global folders
get_skill
Reads one skill file with variables filled in from the environmentname: string
generate_skill changes
Explores an app step by step to produce skill files, optionally emitting a runnable planaction: enumapp_name?: stringgoal?: stringgoals?: arrayarrived_via?: stringaction_type?: stringmax_depth?: integermax_screens?: integermax_time?: integerstrategy?: enumfresh?: booleanseed?: integerskip_calibration?: booleanexplorer?: enumemit?: booleanoutput_dir?: string
list_targets
Lists configured automation targets and their state
switch_target changes
Chooses which target later calls act ontarget: string
calibrate_component
Tests a component definition against the current screen and reports the resultcomponent_path: stringtarget?: stringscroll?: boolean
classify_screen
Groups the screen's text elements into UI components using a modeltarget?: string
record_step changes
Stores one compiled step while a skill runsstep_index: integertype: stringlabel?: stringtap_x?: numbertap_y?: numbermatch_strategy?: stringelapsed_ms?: integerscroll_count?: integerscroll_direction?: string
save_compiled changes
Writes the stored steps to a .compiled.json file, or emits them as a replay scenarioskill_name: stringemit?: booleanoutput_dir?: string

Similar servers