jev-mobile-pilot
Drives Android emulators and (optionally, when explicitly allowed) physical Android devices through adb: lists available devices, reads the on-screen accessibility tree as compact element lists, and performs taps, text input, key presses, scrolling, back navigation and app launches, including multi-step goals executed by the Jev model.
Drives iOS Simulators on macOS through AXe: lists booted simulators, observes the current screen's accessibility tree with element refs, and executes taps, typing, key presses, scrolling, back and app launches (by bundle ID), plus autonomous multi-step task execution and screenshots.
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., "@jev-mobile-pilotOpen Settings on the iOS simulator and check the iOS version."
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.
jev-mobile-pilot
Let coding agents drive iOS Simulators and Android devices without spending their own tokens on every tap. Code reads the screen's accessibility tree, TypeSafe's Jev model picks each step, and your agent only plans and answers the occasional question.
Platforms: iOS Simulator (macOS with Xcode, through AXe) and Android emulators and devices (through adb). Real iPhones are not supported. Real Android phones work only if the person who starts the server allows them.
Screen text leaves your machine. Each decision sends the labels and values on the screen, your goal, hints and param names to TypeSafe's API. Screenshots are never sent to Jev. See What is sent where.
This is an independent project. It is not affiliated with or endorsed by TypeSafe, Apple or Google.
your agent ──mobile_task("Open Settings > General > About")──▶ jev-mobile-pilot
│ loop:
│ read the accessibility tree → compact list of elements
│ Jev: which element next? is the goal done?
│ safety check → tap / type / scroll
◀── done · 2 steps · 5 Jev calls · $0.00017 · 6 s ──────────────┘Why
General device tools for agents (below) leave every step to the calling model: read the screen, pick a button, tap, read again. On a long flow that is many turns of a large model. jev-mobile-pilot hands those small decisions to Jev, which answers typed questions (pick one of these options, yes or no) in a fraction of a second for a fraction of a cent, and comes back to your agent only when it is unsure, blocked, or about to do something it cannot undo.
Related MCP server: phone-harness
Requirements
Node.js 22 or later
A TypeSafe API key in the environment variable
TYPESAFE_API_KEYFor iOS: macOS with Xcode, an iOS Simulator runtime and a booted simulator, and AXe (tested with 1.8.0):
brew install cameroncooke/axe/axeFor Android:
adbfrom the Android SDK platform-tools (tested with 37.0.1) on yourPATH(or setADB_PATH) (brew install --cask android-platform-toolson macOS), and an emulator or a device with USB debugging on. Tested on macOS with Android 16.
You need only the tools for the platform you use. Keep the key in your shell profile or a secrets manager, not in a repository or an MCP config that is committed.
Install
jev-mobile-pilot is not published to npm. Build it from source:
git clone https://github.com/heyk1njo/jev-mobile-pilot.git
cd jev-mobile-pilot
npm ci
npm run build
node dist/cli.js doctordoctor checks AXe and adb and their versions, the devices that are ready, the API key (with one tiny Jev call)
and the safety settings. One of AXe or adb is enough; the other shows as a warning.
Claude Code
claude mcp add -s project jev-mobile-pilot -- node /absolute/path/to/jev-mobile-pilot/dist/cli.js mcpClaude Code starts the server with its own environment, so an exported TYPESAFE_API_KEY is enough.
Other MCP clients
Run node /absolute/path/to/jev-mobile-pilot/dist/cli.js mcp as a stdio server. If your client does not pass its
environment to MCP servers, set TYPESAFE_API_KEY in the client's server config and keep that file out of git.
Use it from an agent
The MCP server has eight tools:
Tool | What it does | Calls Jev |
| List simulators, emulators and devices with their ID, platform and state | no |
| The current screen, one line per element, with refs | no |
| One action: | no |
| The three elements that best match a description | yes |
| Run a multi-step goal until it is done or needs you | yes |
| Continue a task that asked a question or paused | yes |
| A PNG for your agent to look at (not sent to Jev) | no |
| Check the setup | one tiny call |
Every tool but mobile_devices and mobile_doctor takes an optional device: a simulator UDID or an adb serial.
With only one device ready, it can be left out. launch takes a bundle ID on iOS and a package name on Android.
A typical call:
{
"goal": "Start a new contact and type the name into the First name field",
"params": { "name": { "value": "Taro" } },
"hints": ["Contacts has a + button at the top right"]
}Write the goal, plan and hints in English. Values to type go in params, never in the goal; mark passwords
and tokens "secret": true. For flows that cross several screens, pass a plan of milestones:
[{ "do": "Open General", "done_when": "The General screen is shown" }, ...].
Write done_when as something visible on the screen ("The Profile tab is selected", "A General heading is shown"),
not a conclusion such as "the user's own profile is shown": Jev only sees the screen's text and controls.
mobile_observe output looks like this (an illustrative screen):
app: Settings (pid 4242) · 402x874 · screen 9f2c41d0a7b3
e1 button "Settings"
- heading "General"
e2 button "About"
e3 button "Software Update"
e4 switch "Background App Refresh" value="1"
e5 button "Transfer or Reset iPhone" (off-screen below)Task results
mobile_task and mobile_answer run for up to about 45 seconds and return one of these statuses, the latest screen,
cost so far, and the path of a trace file:
Status | Meaning | What to do |
| Jev judged the goal reached from the screen | Check the screen before relying on it |
| A question for you (see below) | Answer with |
| The 45-second window ran out | Call |
|
|
|
| Something went wrong that a retry will not fix | Read the message and the trace |
Questions carry a kind:
Kind | When |
| Jev is not confident which element is next; the options are listed with probabilities |
| The next step looks irreversible and irreversible steps are allowed |
| The next step looks irreversible and they are not allowed |
| Nothing on the screen matches, even after scrolling |
| The screen stopped changing |
| A login wall, permission prompt or similar needs a person |
| Another app came to the front |
| The device tool reported an error; the action is not sent again on the same screen unless you pick it |
mobile_answer takes pick (an option or any ref on the returned screen), hint, plan, more_steps,
confirm or abort. confirm: true only answers a pending confirm question. With nothing but task_id, it
reads the screen again and goes on.
Use it from the command line
jev-mobile-pilot devices
jev-mobile-pilot observe --device <ID>
jev-mobile-pilot run "Open Settings > General > About"
jev-mobile-pilot run "Type the name into the First name field of a new contact" --param name=Taro
jev-mobile-pilot run "Sign in" --param user=me@example.com --secret password=MY_APP_PASSWORD --submit password(Use node dist/cli.js in place of jev-mobile-pilot unless you ran npm link.)
With more than one device ready, pass --device. Add --allow-physical to drive a real Android phone. run answers a paused result itself and stops at any question.
Exit codes: 0 done, 1 failed or error, 2 needs input, 3 budget exhausted. jev-mobile-pilot --help lists every option.
Safety
Irreversible steps are refused by default. Before each tap and each Return key press (a
submitparam,mobile_actwithkey: "return", or text that contains a newline, which presses Return as it is typed), a word list (pay, buy, send, delete, discard, sign out, like, follow, comment, and Japanese equivalents) and a Jev yes/no question judge whether the step can be undone. If Jev cannot answer, the step counts as irreversible. Text fields are exempt.Only the person who starts the server can allow them, with
--allow-irreversibleorJEV_MOBILE_PILOT_ALLOW_IRREVERSIBLE=1. No tool argument can. Even then each such step returns aconfirmquestion, andrunon the command line never confirms.Real Android phones are off by default. They are listed, but every other tool refuses them unless the server is started with
--allow-physicalorJEV_MOBILE_PILOT_ALLOW_PHYSICAL=1. A device counts as a real phone unless its system properties say it is an emulator, and the device list is read again on every call. A real phone is signed in to real accounts: a wrong tap can message, follow or buy for real, and its screen text goes to TypeSafe. Prefer an emulator.An action whose result is unknown (the device tool timed out or failed) is not sent again unless you pick it.
One call at a time per device, and one unfinished task per device: starting another fails and names the task to finish or abort first. Budgets cap steps and spend for every task, and the 45-second window is checked mid-step.
This is a guard rail for a test device, not a security boundary. Use test accounts and no real payment methods.
What is sent where
Data | Goes to |
Screen text (up to 40 lines of text and 40 visible controls per decision; | TypeSafe's API |
Values of non-secret params, after they are typed (they appear on screen) | TypeSafe's API |
Secret params | Nowhere. They are typed on the device and replaced with |
Screenshots | Only to your agent, when it calls |
Traces (one JSON Lines file per task) | Your disk, |
On Android the status bar and notifications (com.android.systemui) are left out. Windows over the app, such as
dialogs and the keyboard, are read as part of the screen and sent like the rest of it.
Add --redact REGEX (or JEV_MOBILE_PILOT_REDACT, one pattern per line) to replace matching text with [redacted]
before it is sent or written. Do not point the tool at screens showing data you may not share with a third party.
Cost
jev-mobile-pilot estimates cost as input tokens × $0.042 per million and stops at budget_usd. Check TypeSafe's
current pricing; the estimate can differ from your bill. In testing on iOS 27 simulators, short tasks
(two or three steps, about five Jev calls) cost about $0.0002 and took 6 to 18 seconds.
Limitations
Both platforms:
typesends ASCII only. Japanese and other text that needs an input method cannot be typed.There is no way to clear a field;
typeappends.Secret values shorter than 4 characters are not masked in screen text, because they would match ordinary words.
doneis Jev's judgment from the screen text. Check the screen.The on-screen keyboard is removed from the screen view.
Only what the accessibility tree exposes is visible. Custom-drawn views without accessibility are not.
iOS, found with AXe 1.8.0, Xcode 27 and iOS 27:
AXe's default tap does not register, so taps use
--tap-style physical.The home button does not work; use
launchwith a bundle ID to switch apps.backis a drag from the left edge, because AXe's edge swipe does not work.React Native views with a label but no accessibility role are shown as
cellwith a warning. SettingaccessibilityRolein the app fixes both the warning and the guess.Default iOS traits come back empty from AXe, so kinds come from element types.
Android, found with platform-tools 37.0.1 and Android 16 on a Pixel:
Reading the screen (
uiautomator dump) takes about 2.5 seconds, and fails while the screen keeps animating; it is retried twice.backis the system Back key.The keyboard is found with
dumpsys input_methodanddumpsys window. If it is shown but its frame cannot be read, the lower 45% of the screen is treated as keyboard.Whether
uiautomator dumpincludes windows over the app (dialogs, the keyboard) may depend on the Android version.Text goes through the active keyboard app. If it is set to a mode that converts input, such as Gboard's Japanese kana mode, ASCII comes out converted or full-width. Switch the keyboard to an English layout first.
input textturns%sinto a space and has no escape for it, so text containing%sis refused.Text is passed to the device's
input textcommand. On the device, other processes can see that command line while it runs, so prefer test credentials there too.
Related projects
AXe: the CLI this project uses to read and drive the iOS Simulator.
XcodeBuildMCP, ios-simulator-mcp, mobile-mcp and agent-device: general device and simulator control for agents. The calling model decides each step. They cover far more (building, real iPhones, recording).
Aben25/jev-sim: mobile-simulator computer use with Jev, iOS and Android.
jevtest (YAML-defined flows on Android emulators) and droidrun/mobile-jev (an Android agent with Jev).
jev-mobile-pilot is narrower: the accessibility tree only, Jev for each step, and a task API that stops and asks instead of guessing.
Development
npm ci
npm test # unit and engine tests with a fake device and a fake Jev
npm run typecheck
npm run buildTests need no simulator, no device, no AXe, no adb and no API key. See CONTRIBUTING.md for the manual check on a real simulator or emulator, and SECURITY.md to report a vulnerability.
License
MIT. Jev and TypeSafe are names of their owners. Apple, iOS and Xcode are trademarks of Apple Inc. Android is a trademark of Google LLC.
This server cannot be deployed
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.
Let your AI agent control real iPhones: screenshots, tap, swipe, type and open apps.
Launch and verify high-fidelity mobile simulations from an AI agent.
Let AI agents control Android and iOS devices on Mobilerun Cloud: screen, input, apps, workflows.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables LLM agents to drive real Android devices or emulators by reading the live accessibility tree, acting on UI nodes instead of coordinates, verifying every action, and typing Unicode text.23 npm2Apache 2.0
- AlicenseNot gradedqualityAmaintenanceControls Android and iOS devices from AI agents through a token-efficient MCP server, enabling observation, tapping, typing, gestures, navigation, assertions, and safety-gated actions.1MIT
- AlicenseAqualityCmaintenanceEnables agents and LLMs to interact with native iOS and Android applications and devices via structured accessibility snapshots or coordinate-based taps, supporting simulators, emulators, and real devices.3959,714 npmApache 2.0
- AlicenseAqualityBmaintenanceEnables agents to execute multi-step iOS and Android UI workflows in a single tool call, using Jev to match accessibility controls and backends like XcodeBuildMCP or Appium/UIAutomator2 to perform and verify actions.39 npmMIT