MCP server for React Native / Expo simulator automation — screenshot, logs, UI flows, bundle status
rn-mcp
MCP server that lets Claude Code automate iOS simulators for React Native / Expo development — take screenshots, read Metro logs, tap UI elements by selector, record and replay flows.
macOS only. Requires Xcode + iOS Simulator.
Install
npm install -g github:Bernecian/rn-mcp
claude mcp add rn-mcp --scope user -- rn-mcp
Restart Claude Code after adding.
Requirements
| Feature | Requirement | |---------|-------------| | Screenshots, logs, URLs | Xcode (ships with macOS dev tools) | | Tap / swipe / UI automation | idb |
Install idb (for automation tools)
brew tap facebook/fb
brew install idb-companion
pip3 install fb-idb
Tools
Default (always available)
| Tool | What it does |
|------|--------------|
| screenshot | PNG screenshot of the booted simulator |
| read_logs | Stream Metro / JS logs live from the simulator |
| reload_app | Hot-reload via Metro |
| restart_app | Kill and relaunch the app process |
| open_url | Open a deep link or web URL in the simulator |
| app_status | Check if Metro and the simulator are running |
| bundle_status | Check Metro bundle health |
| run_plan | Run a saved plan (tap/swipe/assert steps by selector) |
| list_plans | List saved plans |
| run_flow | Replay a saved legacy flow |
| list_flows | List saved flows |
Advanced (set RN_MCP_ADVANCED=1)
Raw automation tools are hidden by default to keep agents from reaching for footguns. Set RN_MCP_ADVANCED=1 in the MCP server environment to unlock:
act, automation_tap, automation_swipe, automation_type, automation_key, describe_ui, inspect_region, inspect_node, boot_simulator, list_simulators, save_flow, clear_recording
Usage
Boot a simulator, start Metro, then ask Claude:
- "Take a screenshot"
- "Read the last 50 lines of Metro logs"
- "Tap the Login button"
- "Run the plan called onboarding"
Plans
Plans are selector-based sequences stored in rn-mcp.plans.json. Any element with a React Native testID prop (maps to iOS accessibilityIdentifier) is addressable by selector.
{
"plans": {
"go-to-profile": [
{ "action": "tap", "selector": "tab-profile" },
{ "action": "assert", "selector": "profile-screen" }
]
}
}
Plans file is resolved in this order:
RN_MCP_PLANS_PATHenv — explicit overridecwd/rn-mcp.plans.json— project-local file- Package root
rn-mcp.plans.json— bundled default
Environment variables
| Variable | Default | Description |
|----------|---------|-------------|
| RN_MCP_METRO_URL | http://localhost:8081 | Metro base URL |
| RN_MCP_PLANS_PATH | (auto-resolved) | Path to plans JSON |
| RN_MCP_FLOWS_PATH | ./rn-mcp.flows.json | Path to legacy flows JSON |
| RN_MCP_SESSION_LOG | ./.rn-mcp/session.log.jsonl | JSONL session log path |
| RN_MCP_ADVANCED | — | Set to 1 to unlock advanced tools |
Observability
Every tool call is appended to .rn-mcp/session.log.jsonl:
tail -f .rn-mcp/session.log.jsonl
Each line: { ts, tool, args, durationMs, status: "ok"|"error", note }
Architecture
src/
├── index.ts MCP server + tool registry + session log
├── types.ts ToolDef, ToolResult
├── utils.ts simctl / idb helpers, screenshot, matchNode
├── flows.ts legacy flow storage + runner
├── plans.ts plan storage: selector-based PlanStep[]
├── recording.ts in-memory recording buffer
└── tools/
├── screenshot.ts
├── logs.ts
├── reload.ts
├── restart.ts
├── simulator.ts open_url, list/boot
├── status.ts app_status, bundle_status
├── automation.ts tap (coords), swipe, type, key, describe_ui
├── act.ts single-action + auto-record
├── run_plan.ts batch executor with selector polling
├── list_plans.ts
├── save_flow.ts save recording → plans + flows
├── flow.ts run_flow / list_flows
└── inspect.ts inspect_region, inspect_node
License
MIT