Skip to main content
Glama

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.

IMPORTANT
  • 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_KEY

  • For iOS: macOS with Xcode, an iOS Simulator runtime and a booted simulator, and AXe (tested with 1.8.0): brew install cameroncooke/axe/axe

  • For Android: adb from the Android SDK platform-tools (tested with 37.0.1) on your PATH (or set ADB_PATH) (brew install --cask android-platform-tools on 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 doctor

doctor 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 mcp

Claude 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

mobile_devices

List simulators, emulators and devices with their ID, platform and state

no

mobile_observe

The current screen, one line per element, with refs e1, e2...

no

mobile_act

One action: tap a ref, type text, press a key, scroll, go back, launch an app

no

mobile_find

The three elements that best match a description

yes

mobile_task

Run a multi-step goal until it is done or needs you

yes

mobile_answer

Continue a task that asked a question or paused

yes

mobile_screenshot

A PNG for your agent to look at (not sent to Jev)

no

mobile_doctor

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

done

Jev judged the goal reached from the screen

Check the screen before relying on it

needs_input

A question for you (see below)

Answer with mobile_answer

paused

The 45-second window ran out

Call mobile_answer with only task_id

budget_exhausted

max_steps (default 20) or budget_usd (default 0.05) reached

mobile_answer with more_steps, or stop

failed

Something went wrong that a retry will not fix

Read the message and the trace

Questions carry a kind:

Kind

When

pick

Jev is not confident which element is next; the options are listed with probabilities

confirm

The next step looks irreversible and irreversible steps are allowed

refused

The next step looks irreversible and they are not allowed

not_found

Nothing on the screen matches, even after scrolling

stuck

The screen stopped changing

blocker

A login wall, permission prompt or similar needs a person

app_changed

Another app came to the front

action_failed

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 submit param, mobile_act with key: "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-irreversible or JEV_MOBILE_PILOT_ALLOW_IRREVERSIBLE=1. No tool argument can. Even then each such step returns a confirm question, and run on 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-physical or JEV_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; mobile_find sends up to 250 controls), goal, plan, hints, param names

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 [secret:name] everywhere else

Screenshots

Only to your agent, when it calls mobile_screenshot

Traces (one JSON Lines file per task)

Your disk, $TMPDIR/jev-mobile-pilot/ by default

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:

  • type sends ASCII only. Japanese and other text that needs an input method cannot be typed.

  • There is no way to clear a field; type appends.

  • Secret values shorter than 4 characters are not masked in screen text, because they would match ordinary words.

  • done is 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 launch with a bundle ID to switch apps.

  • back is 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 cell with a warning. Setting accessibilityRole in 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.

  • back is the system Back key.

  • The keyboard is found with dumpsys input_method and dumpsys window. If it is shown but its frame cannot be read, the lower 45% of the screen is treated as keyboard.

  • Whether uiautomator dump includes 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 text turns %s into a space and has no escape for it, so text containing %s is refused.

  • Text is passed to the device's input text command. On the device, other processes can see that command line while it runs, so prefer test credentials there too.

  • 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 build

Tests 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.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Controls Android and iOS devices from AI agents through a token-efficient MCP server, enabling observation, tapping, typing, gestures, navigation, assertions, and safety-gated actions.
    1
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Enables 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.
    39
    59,714 npm
    Apache 2.0
  • A
    license
    A
    quality
    B
    maintenance
    Enables 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.
    3
    9 npm
    MIT