Supports Android emulator automation for Expo apps, including launching, UI interaction through Maestro tools (tap, input, screenshots), and view hierarchy inspection.
Provides session-based automation for Expo/React Native development including dev server management (start, stop, reload), device binding, and Metro bundler log access for iOS simulator, Android emulator, and web browser targets.
Supports iOS simulator automation for Expo apps, including launching, UI interaction through Maestro tools (tap, input, screenshots), and view hierarchy inspection.
Enables automation of React Native app development through Expo integration, including hot reloading, device management, and UI testing capabilities.
Click on "Install 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., "@Expo MCP Serverlaunch expo on ios simulator and take a screenshot"
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.
expo-mcp
MCP server for Expo/React Native app automation with Maestro integration.
Features
Session-Based Architecture:
start_sessionlaunches Expo, binds a device, and acquires a lease — no manual device ID managementDevice Lease with TTL: 2-minute lease auto-renewed on every device tool call; expires after inactivity so other instances can use the device
Cross-Instance Coordination: Multiple MCP instances can run simultaneously without device conflicts
Expo Dev Server Management: Start/stop/reload Expo development server
Maestro Integration: Full UI automation tools (tap, input, screenshot, etc.)
Installation
npx -y expo-mcpWith pnpm:
pnpx expo-mcpUsage with Claude Code
Add to your .mcp.json:
{
"mcpServers": {
"expo-mcp": {
"command": "npx",
"args": ["-y", "expo-mcp"]
}
}
}Monorepo Setup
Use a positional argument to specify the app directory:
{
"mcpServers": {
"expo-mcp": {
"command": "npx",
"args": ["-y", "expo-mcp", "apps/mobile"]
}
}
}Specific Device
Pin a specific simulator or emulator with --device-id:
{
"mcpServers": {
"expo-mcp": {
"command": "npx",
"args": ["-y", "expo-mcp", "--device-id=6D192F60-1234-5678-ABCD-000000000000"]
}
}
}Tool Filtering
Exclude specific tools with --exclude-tools:
{
"mcpServers": {
"expo-mcp": {
"command": "npx",
"args": ["-y", "expo-mcp", "apps/mobile", "--exclude-tools=list_devices"]
}
}
}Or expose only specific tools with --tools:
{
"mcpServers": {
"expo-mcp": {
"command": "npx",
"args": ["-y", "expo-mcp", "--tools=start_session,stop_session,take_screenshot"]
}
}
}CLI Reference
Usage: expo-mcp [app-dir] [options]
Arguments:
app-dir Path to Expo app directory (default: cwd)
Options:
--device-id=<id> Specific device to use (iOS simulator UUID or Android serial)
--exclude-tools=tool1,tool2 Exclude specific tools from the MCP server
--tools=tool1,tool2 Only expose specific tools
-h, --help Show help message
-v, --version Show version numberQuick Start
# 1. Start session (launches Expo + binds device + acquires lease)
start_session({ target: "ios-simulator" })
# 2. Use tools directly (no device_id needed!)
take_screenshot()
tap_on({ text: "Login" })
input_text({ text: "hello@example.com" })
press_key({ key: "Enter" })
scroll({ direction: "down" })
swipe({ direction: "left" })
# 3. Run Maestro flows
run_maestro_flow({ flow_yaml: "- assertVisible: Welcome" })
check_maestro_flow_syntax({ flow_yaml: "- tap: Login" })
# 4. Reload app after code changes
reload_app()
# 5. Check logs if needed
get_logs({ level: "error" })
# 6. Stop session when done
stop_session()Tools
Lifecycle Tools
Tool | Description |
| Get session status (server state, device info, lease remaining time) |
| Start Expo server, connect device, and acquire device lease |
| Stop Expo server and release all resources |
| Hot reload the app on connected device |
| Get Metro bundler logs (filterable by level and source) |
| Press a key (Enter, Backspace, Home, Lock, Tab, Volume Up/Down) |
| Scroll the screen in a direction (default: down) |
| Swipe by direction or precise start/end coordinates |
start_session Options
Option | Type | Description |
|
| Target platform to launch |
| string | Specific device (iOS UUID or Android serial). Auto-detected if omitted |
|
| Connection mode |
| number | Server port (default: 8081, auto-increments if busy) |
| boolean | Clear Metro bundler cache |
| boolean | Development mode (default: true) |
| boolean | Minify JavaScript |
| number | Max Metro workers |
| boolean | Offline mode |
| string | Custom URI scheme |
| string | iOS simulator name (e.g., "iPhone 16 Pro") |
| boolean | Clean simulator state before launch (default: false) |
| object | Run a Maestro flow after app loads ( |
Maestro Tools
All Maestro tools work automatically once a session is active — device_id is injected from the session:
Tool | Description |
| Capture screen (auto-resized for LLM context) |
| Tap on UI element by text, id, or coordinates |
| Type text into focused field |
| Press back button |
| Run Maestro YAML flow inline |
| Run Maestro flow files from project directory |
| Validate Maestro YAML flow syntax without running it |
| Get UI element tree of the current screen |
| List all available devices (works without an active session) |
Note: Device tools require an active session. Call
start_sessionfirst.list_devicesandcheck_maestro_flow_syntaxcan be called anytime.
Device Lease System
The device lease prevents one MCP instance from holding a device indefinitely:
Acquire:
start_sessionacquires a 2-minute device leaseAuto-Renew: Every device tool call (
take_screenshot,tap_on, etc.) resets the 2-minute timerExpire: If no device tool is called for 2 minutes, the lease expires and the device becomes available
Re-acquire: Call
start_sessionagain to re-acquire (server stays running, no restart needed)Check:
get_session_statusshows remaining lease time
Multiple MCP instances coordinate via a file-based registry (/tmp/expo-mcp/instances/), so two instances cannot claim the same device simultaneously.
Environment Variables
Variable | Description | Default |
| Path to Expo app directory (CLI positional arg takes precedence) | Current working directory |
| Path to Maestro CLI |
|
| Comma-separated list of tools to expose ( | All tools |
| Comma-separated list of tools to exclude ( | None |
| Max log lines to keep in memory | 400 |
| Expo authentication token (only needed if offline mode is disabled) | None |
How It Works
Session Start:
start_sessionstarts Expo dev server, waits for device connection, and acquires a leaseDevice Binding: Connected device ID is stored in the session with a 2-minute TTL
Automatic Injection: All Maestro device tools automatically use the session's device ID
Lease Renewal: Every device tool call resets the lease timer
Session End:
stop_sessioncleans up everything, or the lease expires after inactivity
Non-Interactive Environments (CI/CD, AI Agents)
This MCP server automatically enables --offline mode when running in CI environments (CI=1). This allows the app to work without requiring an EXPO_TOKEN.
What Offline Mode Does
Skips Expo server communication (manifest signing)
Does NOT affect your app's network features (API calls, fetch, etc.)
Tunnel mode (
--tunnel) is not available in offline mode
If You Need Expo Account Features
For features requiring Expo authentication, disable offline mode and provide EXPO_TOKEN:
{
"mcpServers": {
"expo-mcp": {
"env": {
"EXPO_TOKEN": "your-token-here"
}
}
}
}Then call start_session with offline: false:
start_session({ target: "ios-simulator", offline: false })Requirements
Node.js >= 18
Xcode (for iOS Simulator)
Android Studio (for Android Emulator)
Maestro CLI (for UI automation)
License
MIT