# adb-mcp: Android emulator and device control over adb (oci · ghcr.io/iksnerd/adb-mcp)

Drive an Android emulator or device over adb: screenshot, inspect UI, tap, type, build and test.

- Trust score: 46/100 (low)
- Change this week: +5
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-11-05

## Components

- oci · `ghcr.io/iksnerd/adb-mcp`: 46/100 (this document), [markdown](https://verifymcp.io/servers/iksnerd-adb-mcp/ghcr-io-iksnerd-adb-mcp.md), [page](https://verifymcp.io/servers/iksnerd-adb-mcp/ghcr-io-iksnerd-adb-mcp)

## Channel facts

- Registry: `oci`
- Package: `ghcr.io/iksnerd/adb-mcp`
- Version: `0.24.5`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-11-05.

- **Supply Chain Security**: 0/100
  - No malware scan is available for this kind of package: the supply-chain vendors we use do not cover it. This is a permanent gap in our coverage, not a finding about the package.
  - Known CVEs could not be checked: this artifact ships no SBOM or dependency manifest, so there is no dependency list to read.
  - Install-script risk not yet assessed.
  - Dependency health could not be checked: this artifact ships no SBOM or dependency manifest, so there is no dependency list to read.
- **Provenance & Transparency**: 35/100
  - Source repository is publicly reachable at the declared URL.
  - Provenance check failed: no build-provenance attestation is published.
  - License check failed: no license is declared.
  - Actively maintained (last published 1 days ago).
  - Publishes a security disclosure policy (SECURITY.md).
- **Schema Quality & AI Usability**: 81/100
  - 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 14605 tokens (~169/item across 86 items; 80 tools + 6 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 47/100
  - Stability observed for 14 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 100/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 100% of tool parameters carry a description.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - All 7 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation.
  - An AI judge read all 82 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

**Unverified: 1 category.** A category scored 0 because we could not verify it: a data source with nothing on this package, evidence we could not reach, or a check we could not run. We only credit what we can confirm.

## Install

### How do I install the adb-mcp: Android emulator and device control over adb server?

adb-mcp: Android emulator and device control over adb runs locally as a container image, launched with docker run --rm -i ghcr.io/iksnerd/adb-mcp:0.24.5. Ready-made configuration for Claude, Cursor, VS Code, Codex and 3 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add iksnerd-adb-mcp -- docker run --rm -i ghcr.io/iksnerd/adb-mcp:0.24.5
```

### Cursor

```json
{
  "mcpServers": {
    "iksnerd-adb-mcp": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "-i",
        "ghcr.io/iksnerd/adb-mcp:0.24.5"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "iksnerd-adb-mcp": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "-i",
        "ghcr.io/iksnerd/adb-mcp:0.24.5"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add iksnerd-adb-mcp -- docker run --rm -i ghcr.io/iksnerd/adb-mcp:0.24.5
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "iksnerd-adb-mcp": {
      "type": "local",
      "command": [
        "docker",
        "run",
        "--rm",
        "-i",
        "ghcr.io/iksnerd/adb-mcp:0.24.5"
      ],
      "enabled": true
    }
  }
}
```

### Hermes

```yaml
mcp_servers:
  iksnerd-adb-mcp:
    command: "docker"
    args: ["run", "--rm", "-i", "ghcr.io/iksnerd/adb-mcp:0.24.5"]
```

### Netclaw

```json
{
  "McpServers": {
    "iksnerd-adb-mcp": {
      "Transport": "stdio",
      "Command": "docker",
      "Arguments": [
        "run",
        "--rm",
        "-i",
        "ghcr.io/iksnerd/adb-mcp:0.24.5"
      ]
    }
  }
}
```

### Other

```json
{
  "mcpServers": {
    "iksnerd-adb-mcp": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "-i",
        "ghcr.io/iksnerd/adb-mcp:0.24.5"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-11-04 (score 46, +1)

No change was recorded against any check on this day. Stability & Change Management went from 40 to 43. That category is still filling its 30-day observation window: 12 days of observed history at the previous scan, 13 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-11-03 (score 45, 0)

- [functional] Package version: 0.24.4 → 0.24.5

### 2026-11-02 (score 45, +1)

No change was recorded against any check on this day. Stability & Change Management went from 33 to 37. That category is still filling its 30-day observation window: 10 days of observed history at the previous scan, 11 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-11-01 (score 44, +2)

- [security regression] Tool safety: pass → unverified
- [security regression] Stability: 0.30 → unverified
- [functional regression] Schema quality: 100 → unverified
- [functional regression] Capabilities: pass → unverified
- [functional regression] Tool coverage: 100 → unverified
- [functional] Destructive annotations: All 7 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation.
- [functional] Package version: 0.24.3 → 0.24.4

### 2026-10-30 (score 42, +1)

No change was recorded against any check on this day. Stability & Change Management went from 23 to 27. That category is still filling its 30-day observation window: 7 days of observed history at the previous scan, 8 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-27 (score 41, +1)

No change was recorded against any check on this day. Stability & Change Management went from 13 to 17. That category is still filling its 30-day observation window: 4 days of observed history at the previous scan, 5 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-25 (score 40, +1)

No change was recorded against any check on this day. Stability & Change Management went from 7 to 10. That category is still filling its 30-day observation window: 2 days of observed history at the previous scan, 3 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-24 (score 39, 0)

- [functional] Package version: 0.24.1 → 0.24.3

## MCP tools (80)

### `adb_reverse` (~208 tokens)

ADB reverse

Forward a DEVICE TCP port to a HOST port (adb reverse) so the emulator/device can reach a server on this machine — the canonical use is tcp:8081 for Metro. CRITICAL for RN/Expo dev clients: if the app can't reach its dev server it may SILENTLY fall back to the embedded bundle and ignore every code edit you make — set this up before a dev-client session, and suspect it whenever edits seem to have no effect (app_state confirms whether the running process is on Metro or the embedded bundle). remove=true undoes the forward.

Input parameters:

- `device_port` (integer, required): TCP port on the DEVICE to forward, e.g. 8081 for Metro.
- `host_port` (integer): TCP port on the HOST to forward to. Defaults to device_port.
- `remove` (null|boolean): Remove the forward for device_port instead of creating it.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `app_state` (~278 tokens)

App state

Report an app's RUNTIME state: installed?, running?, foreground?, top activity, pid(s), main-process uptime, install/update times, and — for React Native/Expo — whether it is serving a live METRO bundle or its baked-in EMBEDDED one. When it is on Metro it also identifies WHICH dev server: port, pid, and the project root that server is running in. That matters because "metro" does not mean the RIGHT metro — a dev server left running from another checkout still holds port 8081, so the app connects happily and runs that branch's code while this call looks healthy. Pass source_path to get an explicit stale_verdict (stale | current | undetermined) with its reason, never silence. Run this FIRST when JS edits seem to have no effect.

Input parameters:

- `package` (string, required): Application package name.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `source_path` (string): Optional host file or directory containing the app's source. When supplied, app_state ALWAYS returns a stale_verdict (stale | current | undetermined) with the reason — it compares the newest source m…

### `avd_snapshot` (~129 tokens)

AVD snapshot

Manage emulator AVD snapshots (adb emu avd snapshot): action=save|load|delete a named snapshot, or list them. Save a known-good state, then load it to reset the device deterministically between runs — faster than a wipe_data cold boot. Emulator-only.

Input parameters:

- `action` (string, required): One of: "save", "load", "delete", or "list".
- `name` (string): Snapshot name. Required for save/load/delete; ignored for list.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `boot_emulator` (~256 tokens)

Boot emulator

Boot an AVD by name and return its device serial (e.g. emulator-5554). Launches the emulator detached so it outlives this call, and waits for full boot (sys.boot_completed) by default. Use the returned serial for later tools if you boot more than one device. Booting can take 30-120s on a cold start. If an emulator of this AVD is already running it returns that serial instead of launching a second one; no_snapshot and wipe_data then error, because they cannot apply to a running emulator (stop it with shutdown_emulator first).

Input parameters:

- `avd` (string, required): AVD name to boot (see list_avds).
- `no_snapshot` (null|boolean): Cold boot without loading a saved snapshot. Default true when launching; if the AVD is already running it is ignored unless set true, which errors.
- `timeout_s` (integer): Boot timeout in seconds. Default 180.
- `wait_for_boot` (null|boolean): Wait until fully booted before returning. Default true.
- `wipe_data` (null|boolean): Factory-reset the AVD on this boot (-wipe-data). Default false. Use to start from a pristine device.

### `build_and_run` (~236 tokens)

Build and run

One-shot build → install → launch: runs Gradle (default task assembleDebug) in project_dir, installs the resulting APK on the device, and launches package. Equivalent to gradle_build + install_app + launch_app in a single call, and it blocks for the whole build. A failed build returns the same summary and full-log path as gradle_build. If several APKs exist under build/outputs (multi-flavor projects, leftover androidTest APKs), the newest non-test one is installed.

Input parameters:

- `args` (null|array): Extra arguments passed to Gradle (e.g. --stacktrace, -Pflavor=free).
- `package` (string, required): Application package name to install and launch, e.g. com.example.app.
- `project_dir` (string): Path to the Android project root containing the Gradle wrapper (gradlew). Optional if session_set_defaults has pinned a project_dir for this session.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `task` (string): Gradle task to run. Defaults to assembleDebug.

### `build_status` (~75 tokens)

Build status

Report a background gradle_build (started with background=true): running with elapsed time, or the final summary (passed/failed, error count, first errors, APK paths, full-log path). Jobs live only as long as this server process.

Input parameters:

- `job_id` (string, required): Job id returned by gradle_build with background=true.

### `cellular` (~334 tokens)

Cellular

Shape the emulated cellular radio (adb emu gsm / network): data and voice registration state (unregistered/home/roaming/searching/denied/off/on), signal strength (0-4), and mobile-data throughput/latency (network_speed like "lte"/"edge" or "<up>:<down>" kbps; network_delay like "umts" or "<min>:<max>" ms). Test offline/roaming/weak-signal and slow-network behaviour deterministically. Every field optional; set at least one. Emulator-only.

Input parameters:

- `data` (string): Mobile-data registration state: one of unregistered, home, roaming, searching, denied, off, on. Omit to leave unchanged.
- `network_delay` (string): Latency: a named profile (none, gprs, edge, umts) or raw "<min>:<max>" in ms. Omit to leave unchanged.
- `network_speed` (string): Data throughput: a named profile (gsm, gprs, edge, umts, hsdpa, lte, evdo, full) or raw "<up>:<down>" in kbps. Omit to leave unchanged.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `signal` (null|integer): Signal strength 0-4 (0 = no bars, 4 = full). Omit to leave unchanged.
- `voice` (string): Voice registration state: one of unregistered, home, roaming, searching, denied, off, on. Omit to leave unchanged.

### `clear_app_data` (~81 tokens)

Clear app data

Wipe an app's data and cache (pm clear) to reset it to a first-launch state — the fastest way to reproduce onboarding/permission flows from scratch.

Input parameters:

- `package` (string, required): Application package name (e.g. com.example.app).
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `clear_device_lock` (~75 tokens)

Clear device lock

Remove the secure lock screen, supplying the current credential as old_value. Use to restore a clean state after testing a Keystore flow.

Input parameters:

- `old_value` (string, required): The current credential, needed to clear the lock.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `clear_logcat` (~114 tokens)

Clear logcat

Empty the device's logcat ring buffer (adb logcat -c). The sharpest isolation primitive for a press→observe loop: clear, perform ONE action, then logcat — every line you read was caused by that action. Without it, a filter hit may be minutes old and an empty result may just mean the buffer rotated. (For reaching BACK in time instead, use logcat's since param.)

Input parameters:

- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `connect_wireless` (~149 tokens)

Connect wireless

Connect to a device over Wi-Fi/TCP (adb connect), optionally pairing first (adb pair) with the 6-digit code from Android 11+ Wireless debugging. Pass host:port; for pairing also pass the pairing address + code shown on the device.

Input parameters:

- `host_port` (string, required): Device address to connect to, host:port (e.g. 192.168.1.42:5555).
- `pair_address` (string): Pairing address host:port (Android 11+ Wireless debugging), if different from host_port. Only needed when pairing.
- `pairing_code` (string): 6-digit pairing code shown on the device. Provide to pair before connecting.

### `describe_ui` (~524 tokens)

Describe UI

Read the on-screen UI hierarchy as a list of elements, each with its text, content_desc, resource_id, class, clickable flag, pixel bounds, and a precomputed center in TRUE DEVICE PIXELS. This is your source of truth for AIMING: pass an element's center straight to tap. Never guess coordinates from the screenshot (it is downscaled and you will miss). The response header states the FOCUSED WINDOW (if it's a system overlay — biometric prompt, permission dialog — the elements belong to that overlay, not your app) and how many nodes the filter hid. Default filter keeps labelled/clickable/id-carrying elements minus redundant wrappers; filter="clickable" returns only tap targets (much smaller); filter="all" returns every bounded node — the only mode where absence proves an element isn't in the CURRENT VIEWPORT's hierarchy. Every mode is viewport-scoped: Android omits a ScrollView's off-screen children from the accessibility tree entirely, so scroll before concluding an element does not exist, and never use this to COUNT a long list (it will undercount) — render_stats reports the whole view tree. Canvas-drawn (RN/Flutter/Skia) content appears in NO mode.

Input parameters:

- `compact` (null|boolean): Return one line per element (center, bounds, flags, labels) instead of JSON — ~10x fewer tokens, same aiming information. Use for repeated look-drive loops and geometry work.
- `filter` (string): What to include: 'auto' (default — elements with text, content_desc, resource_id, or clickable; identical-bounds label-less wrappers dropped), 'clickable' (tap targets only, the smallest view), or 'a…
- `package` (string): Optional package expected to own the focused window. If another app or SystemUI owns focus, the response calls that out explicitly.
- `query` (string): Case-insensitive substring to match against text, content_desc, and resource_id — return only matching elements. The cheap way to ask 'is X on this screen?'. Combine with filter='all' to prove absenc…
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `doctor` (~109 tokens)

Doctor

Diagnose the local toolchain, and the first call of any session that isn't going well: Android SDK path (and whether it really is an SDK), adb/emulator availability, known AVDs, attached devices, per-device accessibility bridge status, plus the host build tools — a JDK (Gradle needs one) and a system gradle (needed only so scaffold_android_project can generate a wrapper). Run this first when something isn't working, especially a Gradle tool failing with "SDK location not found".

### `drag` (~175 tokens)

Drag

Press-hold-move-release drag from (x1,y1) to (x2,y2) in true device pixels (input draganddrop, Android 11+). Unlike swipe (which flings), this holds at the start first — use it for drag handles, long-press-to-reorder lists, and drag-and-drop targets a quick swipe skips.

Input parameters:

- `duration_ms` (integer): Drag duration in ms. Default 400.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `x1` (integer, required): Start X in true device pixels.
- `x2` (integer, required): End X in true device pixels.
- `y1` (integer, required): Start Y in true device pixels.
- `y2` (integer, required): End Y in true device pixels.

### `enter_pin` (~335 tokens)

Enter PIN

Enter digits on a numeric PIN pad by tapping each key with a settle delay. Use when input_text does nothing because the pad renders its own key views. Visibility is PAD-SPECIFIC — run describe_ui on the pad screen first: a native-view pad (digits listed as Buttons with text) works with the default hierarchy lookup, no extra args. Only CANVAS-DRAWN pads (React Native / Skia SDK pads, whose keys are invisible to describe_ui) need 'grid' (the pad's bounding box; digits placed on a standard 3x4 dialpad) or 'coords' (explicit per-digit x,y) — read those bounds off a screenshot.

Input parameters:

- `coords` (string): Optional explicit per-digit tap points as 'digit:x,y' pairs separated by ';', e.g. '1:540,1600;2:640,1600'. Overrides grid and hierarchy for the digits given. Use when the pad is not a regular grid.
- `digits` (string, required): The digits to enter, e.g. "1234".
- `grid` (null|object): Optional bounding box {x1,y1,x2,y2} of the PIN pad. Provide this for custom-drawn (React Native / Skia) pads whose keys are invisible to describe_ui: digits are placed on a standard 3x4 dialpad grid…
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `finger_remove` (~64 tokens)

Finger remove

Lift the simulated finger off the sensor (adb emu finger remove) — the complement to fingerprint_touch, for flows that watch for the finger-up event. Emulator-only.

Input parameters:

- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `fingerprint_touch` (~193 tokens)

Fingerprint touch

Simulate a fingerprint-sensor touch on an EMULATOR (adb emu finger touch). With a fingerprint enrolled it satisfies a BiometricPrompt, so you can drive the real biometric path instead of cancelling into the PIN fallback. finger_id must match an enrolled finger (default 1). Check has_biometric_enrolled FIRST: with nothing enrolled the prompt sits on "Touch the sensor" forever. GOTCHA: it reports OK even when the id matches nothing, and wrong ids trip a HAL lockout, so enroll deterministically rather than sweeping ids (the pin-and-lock guide: a skill, or the android://guide/pin-and-lock resource). Emulator-only.

Input parameters:

- `finger_id` (integer): Id of the enrolled finger to touch with (must match a finger enrolled in Settings). Default 1.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `get_app_details` (~177 tokens)

Get app details

Report an installed app's version name/code and its launchable activity (dumpsys package + resolve-activity) — to confirm what build is installed and find the activity to launch. Pass apk_path instead of package to read a local .apk (package id, version, launcher activity) with no device attached — handy right after gradle_build.

Input parameters:

- `apk_path` (string): Local path to an .apk to read instead of querying a device: returns its package id, version and launcher activity via aapt2. Needs no device; use it right after a build to get the applicationId.
- `package` (string): Installed application package name (e.g. com.example.app). Required unless apk_path is given.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `get_coverage_report` (~307 tokens)

Get coverage report

Run a JaCoCo coverage-report Gradle task in project_dir (default 'jacocoTestReport') and summarize coverage: overall line/branch/method/class percentages plus a per-package breakdown, worst-covered first; multi-module reports are merged. 'jacocoTestReport' is NOT an Android Gradle plugin task: if it is missing, pass AGP's 'createDebugUnitTestCoverageReport' as task= (find it with list_gradle_tasks; see the build-and-test guide: a skill, or the android://guide/build-and-test resource). JVM unit tests only. Pass json=true for structured JSON.

Input parameters:

- `args` (null|array): Extra arguments passed to Gradle (e.g. --stacktrace).
- `json` (boolean): Return structured JSON instead of the human-readable text summary.
- `project_dir` (string): Path to the Android project root containing the Gradle wrapper (gradlew). Optional if session_set_defaults has pinned a project_dir for this session.
- `task` (string): Gradle task that generates the JaCoCo XML report. Defaults to jacocoTestReport, which is NOT a task the Android Gradle plugin defines — it exists only if the project declares it. If it is missing, ch…

### `get_file_coverage` (~311 tokens)

Get file coverage

Same JaCoCo run and task fallback as get_coverage_report, scoped to one source file: the missed and partially-covered line numbers plus per-method detail. Use it after get_coverage_report names the weakest package. file matches by suffix across every module (an ambiguous name returns every match; a miss lists the files that have data). JVM unit tests only. Pass json=true for structured JSON.

Input parameters:

- `args` (null|array): Extra arguments passed to Gradle (e.g. --stacktrace).
- `file` (string, required): Source file to report coverage for — a filename (e.g. Foo.kt) or package-qualified path (e.g. com/example/Foo.kt). Matched by suffix; an ambiguous bare filename returns every match.
- `json` (boolean): Return structured JSON instead of the human-readable text summary.
- `project_dir` (string): Path to the Android project root containing the Gradle wrapper (gradlew). Optional if session_set_defaults has pinned a project_dir for this session.
- `task` (string): Gradle task that generates the JaCoCo XML report. Defaults to jacocoTestReport, which is NOT a task the Android Gradle plugin defines — it exists only if the project declares it. If it is missing, ch…

### `gradle_build` (~243 tokens)

Gradle build

Build the app with Gradle (default task assembleDebug) in project_dir. project_dir must contain the Gradle wrapper (gradlew). Runs on the host, not a device. Returns a compact summary (status, duration, error count, first errors) and the path of the full log, with credential-shaped values redacted. It also lists the APKs under build/outputs, newest first; that can include APKs left by earlier builds. For builds that take minutes pass background=true: it returns a job id and log path at once, and build_status reports the result.

Input parameters:

- `args` (null|array): Extra arguments passed to Gradle (e.g. --stacktrace, -Pflavor=free).
- `background` (boolean): Start the build detached and return at once with a job id and log path; poll with build_status. Use for builds that take minutes.
- `project_dir` (string): Path to the Android project root containing the Gradle wrapper (gradlew). Optional if session_set_defaults has pinned a project_dir for this session.
- `task` (string): Gradle task to run. Defaults to assembleDebug.

### `gradle_project_properties` (~190 tokens)

Gradle project properties

Dump Gradle's evaluated properties for one module, such as :app or :feature:login. Use after list_gradle_projects when you need the module's namespace, Android SDK settings, build directory, or other effective configuration rather than just its task/variant names. This is the module's FULL effective property set — the same one ./gradlew properties would print, which can include credentials a build.gradle reads from ~/.gradle/gradle.properties or env (e.g. private Maven repo auth). Values for keys that look secret-shaped (password/token/key/credential) are redacted before being returned.

Input parameters:

- `module` (string, required): Gradle module path, e.g. :app or :feature:login.
- `project_dir` (string): Path to the Android project root containing the Gradle wrapper (gradlew). Optional if session_set_defaults has pinned a project_dir for this session.

### `grant_permission` (~92 tokens)

Grant permission

Grant a runtime permission to an app (pm grant), e.g. android.permission.CAMERA — skips the in-app permission dialog so you can drive straight to the feature.

Input parameters:

- `package` (string, required): Application package name.
- `permission` (string, required): Full permission name, e.g. android.permission.CAMERA.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `has_biometric_enrolled` (~145 tokens)

Has biometric enrolled

Report whether any fingerprint is enrolled (and how many), from dumpsys fingerprint. Check this BEFORE a biometric flow: with nothing enrolled, fingerprint_touch can never satisfy a BiometricPrompt — it just sits on "Touch the sensor" — so branch to enrolling one or to the PIN path instead of guessing. Works on emulators and physical devices. Note: the framework exposes only an enrolled COUNT, never which finger id is enrolled, and a wrong fingerprint_touch id trips a HAL lockout after a few tries — so enroll deterministically rather than sweeping ids.

Input parameters:

- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `input_key_combo` (~257 tokens)

Input key combo

Press several keys together as a chord (input keycombination, Android 11+). Use preset="select_all" (or copy/paste/cut/undo/redo/save/find) for a named shortcut, or keys=["ctrl","a"] / ["alt","tab"] to spell one out — modifier(s) first, then the action key; each is a key name (ctrl/alt/shift/meta, a-z, enter, tab, ...) or a raw keycode. For a single key use press_key instead.

Input parameters:

- `keys` (null|array): Keys to press together, modifier(s) first, e.g. ["ctrl","a"] or ["alt","tab"]. Each is a key name (ctrl, alt, shift, meta, a-z, enter, tab, ...) or a raw keycode number. Needs at least 2. Omit if pre…
- `preset` (string): Named combo shortcut (select_all, copy, paste, cut, undo, redo, save, find) that expands to the right chord — use this instead of keys when a name will do.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `input_text` (~107 tokens)

Input text

Type text into the currently focused input field via the IME. Tap the field first so it has focus. Afterwards the soft keyboard may cover buttons lower on screen — dismiss it with press_key escape (or back) before tapping them. For native non-IME PIN pads this does nothing; use enter_pin instead.

Input parameters:

- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `text` (string, required): Text to type into the focused field.

### `install_app` (~76 tokens)

Install app

Install (or reinstall, -r) an APK from a local file path onto the device. Use to deploy a build you want to test.

Input parameters:

- `apk_path` (string, required): Local filesystem path to the .apk to install.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `is_device_secure` (~80 tokens)

Is device secure

Report whether a secure lock screen is set (probed with locksettings; this is what KeyguardManager.isDeviceSecure, which Keystore checks, reflects). Use it to verify set_device_lock worked before running a Keystore-gated flow.

Input parameters:

- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `last_crash` (~150 tokens)

Last crash

Return the most recent app crash from the system DropBox (dumpsys dropbox — JVM/React-Native and native crashes), with the full exception header and stack in one call. Optionally filter to a package. Use this instead of grepping logcat when an app just crashed: DropBox keeps the whole fatal (header + Caused by + frames) together even after it has scrolled out of the logcat ring buffer.

Input parameters:

- `package` (string): Optional package name to filter to (e.g. com.example.app); omit for the most recent crash from any app.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `launch_app` (~108 tokens)

Launch app

Launch an app by package name (starts its LAUNCHER activity) and echo the resolved component on success. Fails with a clear message (not a raw monkey dump) when the package isn't installed or has no launcher activity. Combine with stop_app to restart an app cleanly from a known state.

Input parameters:

- `package` (string, required): Application package name (e.g. com.example.app).
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `launch_dev_client` (~257 tokens)

Launch dev client

Launch an Expo dev build straight at a Metro dev server, skipping the Dev Launcher's server-picker screen. Builds the "<scheme>://expo-development-client/?url=http://host:port" deep link and opens it. Pass scheme (your app.json "scheme"); host/port default to localhost:8081. PREREQUISITE: run adb_reverse {device_port: 8081} first so the device can reach Metro, otherwise the dev client falls back to its embedded bundle. For plain Expo Go (not a dev build) use open_url with the exp:// URL instead.

Input parameters:

- `host` (string): Metro dev-server host as the DEVICE sees it. Default localhost (works once adb_reverse {device_port: 8081} is set).
- `package` (string): Optional package to target the intent at (disambiguates if multiple apps claim the scheme).
- `port` (integer): Metro dev-server port. Default 8081.
- `scheme` (string, required): The app's URL scheme from app.json (e.g. "myapp") — used to build the expo-development-client deep link.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `list_avds` (~45 tokens)

List avds

List the Android Virtual Devices (AVDs) installed on this machine that can be booted. Start here when no emulator is running yet; the returned names feed boot_emulator.

### `list_devices` (~126 tokens)

List devices

List attached emulators/devices and their adb state (device = ready, offline, unauthorized). Use it to confirm a device is up before driving it, or to get the serial when several are attached. Driving a React Native/Expo dev build? Follow the rn-expo guide before you start (a skill, or the android://guide/rn-expo resource) — the ordering (adb_reverse BEFORE launch, app_state before believing anything) is the part that costs sessions.

Input parameters:

- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `list_gradle_projects` (~140 tokens)

List gradle projects

List the Gradle modules (sub-projects) in project_dir (gradlew projects) — the map of a multi-module build, e.g. :app, :core, :feature:login. Use it to find which module to point gradle_build/list_gradle_variants at, or to address a task at one module with '<path>:<task>' (e.g. :app:assembleDebug). A single-module build reports no sub-projects.

Input parameters:

- `project_dir` (string): Path to the Android project root containing the Gradle wrapper (gradlew). Optional if session_set_defaults has pinned a project_dir for this session.

### `list_gradle_tasks` (~152 tokens)

List gradle tasks

List the available Gradle tasks in project_dir (gradlew tasks) — to discover build/test/install targets. In a multi-module build this lists the ROOT project's own tasks by default; pass task="<module>:tasks" (e.g. ":app:tasks", from list_gradle_projects) to scope to a submodule instead.

Input parameters:

- `project_dir` (string): Path to the Android project root containing the Gradle wrapper (gradlew). Optional if session_set_defaults has pinned a project_dir for this session.
- `task` (string): Gradle task to scope to, e.g. ":app:tasks" to list the :app module's tasks or variants. Defaults to the root project's tasks.

### `list_gradle_variants` (~220 tokens)

List gradle variants

List the buildable build variants in project_dir (parsed from the assemble* tasks) — the Android analogue of "list schemes". Each variant V maps to an assembleV / installV Gradle task; pass it as the task= arg to gradle_build/build_and_run to disambiguate a multi-flavor project. Test-only APK tasks (androidTest/unitTest) are excluded. In a multi-module build the ROOT project usually has no variants of its own (no Android plugin applied there) — pass task="<module>:tasks" (e.g. ":app:tasks", from list_gradle_projects) to scope to the module that actually builds APKs.

Input parameters:

- `project_dir` (string): Path to the Android project root containing the Gradle wrapper (gradlew). Optional if session_set_defaults has pinned a project_dir for this session.
- `task` (string): Gradle task to scope to, e.g. ":app:tasks" to list the :app module's tasks or variants. Defaults to the root project's tasks.

### `list_packages` (~72 tokens)

List packages

List installed package names, optionally filtered by substring — to confirm an app is installed and get its exact package name for launch_app/stop_app.

Input parameters:

- `filter` (string): Substring to filter package names.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `logcat` (~444 tokens)

Logcat

Dump recent native log lines — the last N (default 400) or, with since="2m"/"90s", everything from that long ago on the device clock (the right axis when the report is 'I just hit an error'; on a chatty emulator 400 lines can span seconds). Optionally filtered by a case-insensitive substring, a minimum priority (V/D/I/W/E/F — e.g. priority="E" for errors and up), and/or tags (OR'd). This is how you find the REAL reason a native call failed when the UI only shows a generic 'X failed' alert: filter by your app tag or 'Exception'/'Caused by' and read the 'Caused by:' line — that is the root cause. Dumps and exits (does not stream); chatty spam is stripped.

Input parameters:

- `filter` (string): Case-insensitive substring to keep (e.g. an app tag or "Exception").
- `lines` (integer): Number of recent lines to dump. Default 400. Ignored when since is given.
- `priority` (string): Minimum priority to keep: V, D, I, W, E, or F (matches adb's own "*:E"-style filter — E keeps Error and Fatal). Omit for no priority filtering.
- `redact` (boolean): Mask common secrets (token, password, authorization, api key, secret) before returning log lines. Recommended for payment/auth SDKs and debug builds.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `since` (string): Time window instead of a line count: only lines from the last e.g. "2m", "90s", "1h30m" (device clock). The right axis when the report is "I just hit an error" — on a chatty emulator 400 lines can sp…
- `tags` (null|array): Keep only lines whose log tag contains one of these (case-insensitive, OR'd), e.g. ["SessionStore","AuthModule"]. Omit for no tag filtering.

### `long_press` (~99 tokens)

Long press

Press and hold a coordinate (true device pixels) for a duration — for context menus, drag handles, and long-press actions.

Input parameters:

- `duration_ms` (integer): Hold duration in ms. Default 600.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `x` (integer, required): X coordinate in true device pixels.
- `y` (integer, required): Y coordinate in true device pixels.

### `open_dev_menu` (~88 tokens)

Open dev menu

Open the React Native dev menu (KEYCODE_MENU) on the foreground app — the reliable way to reach a dev build's Reload/Debug JS Remotely/etc. options when reload_app's broadcast doesn't apply. Follow with tap_on_text or describe_ui to pick a menu item.

Input parameters:

- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `open_url` (~94 tokens)

Open URL

Open a URL or deep link via an ACTION_VIEW intent (am start) — the way to jump straight to a deep-linked screen. Optionally target a specific package.

Input parameters:

- `package` (string): Optional package to target the intent at.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `url` (string, required): URL or deep link to open (ACTION_VIEW).

### `phone_call` (~148 tokens)

Phone call

Drive an emulated voice call (adb emu gsm). action="call" (default) rings an incoming call from number; "accept"/"cancel"/"busy"/"hold" transition an in-progress call. Use to test call-interruption behaviour and CALL_PHONE flows. Emulator-only.

Input parameters:

- `action` (string): What to do: "call" (default — ring an incoming call), "accept", "cancel" (hang up), "busy", or "hold".
- `number` (string, required): Phone number for the call, e.g. "+15551234567".
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `prefer_pin` (~83 tokens)

Prefer PIN

Try to move a standard BiometricPrompt to its PIN/password fallback by selecting an explicit system button, or sending BACK as the generic cancel path. App-controlled prompts may suppress or rename this option, so confirm the resulting PIN pad with describe_ui before calling enter_pin.

Input parameters:

- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `press_key` (~300 tokens)

Press key

Press a hardware/navigation key by name (enter, back, home, menu, tab, del, escape, up, down, left, right, dpad_center, app_switch, search, power, wakeup, sleep, volume_up, volume_down, ...) or a raw Android keycode number. Handy to submit a form (enter), dismiss the keyboard (escape), or go back (back). To turn the screen ON use wakeup (not power, which toggles and may sleep an awake screen); sleep turns it off. A key can be silently consumed with no effect (e.g. back while a biometric prompt is up) — pass verify_change=true to get ui_changed: true/false instead of guessing. Batching several presses (or a press whose timing matters relative to other actions) belongs in run_sequence, not in separate calls.

Input parameters:

- `key` (string, required): Key name (enter, back, home, menu, tab, del, escape, up, down, left, right, ...) or a raw keycode number.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `verify_change` (null|boolean): Also report whether the UI hierarchy changed after the key press (ui_changed: true/false). Costs two extra hierarchy reads (~2-3s); use when the key may be silently consumed (e.g. back while a biomet…

### `pull_file` (~85 tokens)

Pull file

Copy a file off the device to a local path (adb pull) — e.g. retrieve a generated file, database, or screenshot.

Input parameters:

- `device_path` (string, required): File path on the device to copy off.
- `local_path` (string, required): Local destination path.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `push_file` (~93 tokens)

Push file

Copy a local file onto the device (adb push) — e.g. seed test data or a file to import.

Input parameters:

- `device_path` (string, required): Destination path on the device, e.g. /sdcard/Download/x.json.
- `local_path` (string, required): Local file to copy onto the device.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `reload_app` (~195 tokens)

Reload app

Best-effort: trigger a Metro/JS reload on a React Native dev-client build via the classic <package>.RELOAD_APP_ACTION broadcast. Only works on debug builds of classic (non-bridgeless) RN architectures that register the receiver — on newer RN/Expo dev clients it may silently no-op with no error. If the app doesn't visibly reload, use open_dev_menu then tap_on_text("Reload") instead. PREREQUISITE: the app must be able to reach Metro at all — run adb_reverse {device_port: 8081} first, or a reload lands you back on the EMBEDDED bundle and your edits still won't appear (app_state tells you which bundle the running process is actually serving).

Input parameters:

- `package` (string, required): Application package name (e.g. com.example.app).
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `render_stats` (~229 tokens)

Render stats

Count an app's REAL view tree and recent frame health: total views, render-node kB, per-window breakdown, jank percentage and frame-time percentiles, plus Choreographer's "Skipped N frames" tally. Use this — not describe_ui — to MEASURE a screen: describe_ui is viewport-scoped (Android drops off-screen ScrollView children from the accessibility tree), so it undercounts a long list even at filter="all". The three numbers together are what argues an unvirtualised list: a large view count, render-node kB that grows as you scroll, and skipped frames on the main thread. Pass reset=true to zero the frame counters after reading so the next call measures one specific interaction.

Input parameters:

- `package` (string, required): Application package name.
- `reset` (null|boolean): Zero gfxinfo's frame counters after reading, so the NEXT call measures only what happens in between. Use it to attribute jank to one interaction instead of to the whole session.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `revoke_permission` (~104 tokens)

Revoke permission

Revoke a runtime permission from an app (pm revoke) — to test the denied path or re-trigger the permission-request dialog on next use. Pairs with grant_permission. Note: revoking some permissions kills the app process.

Input parameters:

- `package` (string, required): Application package name.
- `permission` (string, required): Full permission name, e.g. android.permission.CAMERA.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `rotate_screen` (~62 tokens)

Rotate screen

Rotate the emulator to its next orientation (adb emu rotate) — the quick way to exercise landscape/portrait layout and rotation-driven state loss. Emulator-only.

Input parameters:

- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `run_instrumented_tests` (~218 tokens)

Run instrumented tests

Run Gradle instrumented (on-device) tests (default task 'connectedAndroidTest') in project_dir — requires a booted device/emulator. Returns per-suite timing and failing-test stack traces; pass json=true for a structured JSON summary. Runs in the foreground and can take minutes; for a long run use gradle_build with task=connectedAndroidTest and background=true, then read the log.

Input parameters:

- `args` (null|array): Extra arguments passed to Gradle (e.g. --stacktrace, -Pflavor=free).
- `json` (boolean): For run_unit_tests/run_instrumented_tests: return the test summary as structured JSON (per-suite timing, full failure stack traces) instead of the human-readable text summary.
- `project_dir` (string): Path to the Android project root containing the Gradle wrapper (gradlew). Optional if session_set_defaults has pinned a project_dir for this session.
- `task` (string): Gradle task to run. Defaults to the tool's standard task.

### `run_sequence` (~356 tokens)

Run sequence

Run several interaction steps in ONE call — no agent round-trip between them. Use for scripted flows and, crucially, flows driven by NATIVE TIMERS (background-token clear, a biometric prompt that auto-fires on resume) where a round-trip per step would perturb the timing you're testing: e.g. key:home → sleep:19 → launch → sleep:9 → tap_text:Cancel (if_present:biometric) → assert_foreground(package) → describe_ui. Each step has an 'action' (sleep, tap, tap_text, tap_element, key, text, swipe, launch, stop, assert_foreground, wait_text, describe_ui) with its params; an if_present/if_absent guard skips a step unless a selector is (not) on screen — that's how you express a conditional cancel; and optional=true lets a step fail without aborting. Returns a per-step result (ok/skipped/error) with elapsed_ms plus the final hierarchy. A non-optional step error stops the rest.

Input parameters:

- `capture_final` (null|boolean): Append the settled UI hierarchy after the last step (unless the run aborted) so you see the end state in this same response. Default true.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `steps` (null|array, required): Ordered steps to run in one call. Each has an 'action' (sleep, tap, tap_text, tap_element, key, text, swipe, launch, stop, assert_foreground, wait_text, describe_ui) plus that action's params, and op…

### `run_unit_tests` (~258 tokens)

Run unit tests

Run Gradle JVM unit tests (default task 'test') in project_dir and return the result summary, including per-suite timing and failing-test stack traces. On an Android project 'test' runs EVERY variant's unit tests (debug and release both); pass task="testDebugUnitTest" (or ":app:testDebugUnitTest" in a multi-module build) to run just one and roughly halve the time. Pass json=true for a structured JSON summary. Runs in the foreground and blocks until Gradle finishes; for a long run use gradle_build with this task and background=true, then read the log.

Input parameters:

- `args` (null|array): Extra arguments passed to Gradle (e.g. --stacktrace, -Pflavor=free).
- `json` (boolean): For run_unit_tests/run_instrumented_tests: return the test summary as structured JSON (per-suite timing, full failure stack traces) instead of the human-readable text summary.
- `project_dir` (string): Path to the Android project root containing the Gradle wrapper (gradlew). Optional if session_set_defaults has pinned a project_dir for this session.
- `task` (string): Gradle task to run. Defaults to the tool's standard task.

### `scaffold_android_project` (~168 tokens)

Scaffold android project

Create a minimal Kotlin Android application in a new empty directory, including Gradle Kotlin DSL files, an AndroidManifest, a launcher Activity, resources, README, and gitignore. The tool never overwrites a non-empty directory. It also generates the Gradle wrapper when a system `gradle` is on PATH, so the result is immediately buildable with gradle_build; if no gradle is installed the result says so, and `gradle wrapper` has to be run in that directory before any Gradle tool here will work (they all drive ./gradlew).

Input parameters:

- `destination` (string, required): Empty or new directory to create the project in.
- `name` (string, required): Human-readable app name.
- `package` (string, required): Application id/package, e.g. com.example.app.

### `screenshot` (~314 tokens)

Screenshot

Capture the current screen as a PNG so you can SEE the UI state. Call it after every action to confirm the screen changed before acting again — driving blind chains taps onto the wrong screen. The image is auto-downscaled (default max 760px) so it is accepted by the image reader; this is for seeing only — derive tap coordinates from describe_ui, not from this image. Auto-retries an all-black frame (an intermittent capture glitch) and, if it stays black, says why (FLAG_SECURE content like a native PIN pad, or a sleeping display) — when black, use describe_ui instead. Works on multi-display foldables (the default display captures correctly); pass display="cover"/"inner"/an index to grab a specific panel.

Input parameters:

- `display` (string): Which physical display to capture on a multi-display device (a foldable). Omit for the default/built-in screen. Accepts a name alias ('inner'/'primary' or 'cover'/'outer'), an HWC index ('0','1'), or…
- `max_dim` (null|integer): Max width/height of the returned image in pixels. Omit for the default 760; pass 0 (or a negative) to disable downscaling and get the full-resolution image.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `send_sms` (~131 tokens)

Send SMS

Deliver an incoming SMS to the emulator (adb emu sms send) — the standard way to drive OTP / 2FA flows without a second phone. Pass a sender number (from) and the message text (e.g. the code). Emulator-only.

Input parameters:

- `from` (string, required): Sender phone number the SMS appears to come from, e.g. "+15551234567".
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `text` (string, required): Message body (e.g. an OTP code) delivered to the device's SMS inbox.

### `session_clear_defaults` (~41 tokens)

Session clear defaults

Clear any project_dir/serial defaults pinned for this session, reverting to requiring them on every call (serial still falls back to the single-attached-device default).

### `session_set_defaults` (~162 tokens)

Session set defaults

Pin a default project_dir and/or serial for the rest of this session so later calls can omit them — the fix for a multi-module or multi-flavor project (or multiple attached devices) forcing you to repeat the same project_dir/serial on every Gradle or device call. Only the fields you pass are changed; omit a field to leave its current default alone. An explicit project_dir/serial on any individual call always overrides the session default for that one call.

Input parameters:

- `project_dir` (string): Default Android project root to use whenever a tool call omits project_dir. Leave empty to leave the current default unchanged.
- `serial` (string): Default device serial to use whenever a tool call omits serial. Leave empty to leave the current default unchanged.

### `session_show_defaults` (~28 tokens)

Session show defaults

Show the project_dir/serial defaults currently pinned for this session (set via session_set_defaults).

### `set_battery` (~235 tokens)

Set battery

Set the battery level (0-100) and/or charging state — test low-battery UI and charging-only logic deterministically. On an emulator it uses the console (adb emu power); on a PHYSICAL device it forces the values via dumpsys battery, which persist until you call this again with reset=true (or the device reboots). Provide level, charging, or both — or reset=true to restore automatic reporting. (For a fake battery in a clean SCREENSHOT status bar only, use set_status_bar instead.)

Input parameters:

- `charging` (null|boolean): true = plugged into AC, false = on battery. Omit to leave the charging state unchanged.
- `level` (null|integer): Battery charge level 0-100. Omit to leave the level unchanged.
- `reset` (boolean): Restore automatic battery reporting (dumpsys battery reset) and ignore level/charging. On a physical device the forced values persist until this (or a reboot); use it to clean up after testing.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `set_dark_mode` (~69 tokens)

Set dark mode

Turn the system dark theme on or off (cmd uimode night) — to test light/dark appearances.

Input parameters:

- `enabled` (boolean, required): true = dark theme on, false = off.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `set_device_lock` (~156 tokens)

Set device lock

Set a secure lock screen (type: pin [default], pattern, or password). REQUIRED before AndroidKeyStore / Keystore-backed crypto flows, which fail with 'A secure lock screen is required' on a fresh emulator that has no lock. Follow with is_device_secure to confirm.

Input parameters:

- `old_value` (string): The CURRENT credential, required only when a lock is already set and you want to change it (locksettings refuses to overwrite otherwise).
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `type` (string): Lock type: pin (default), pattern, or password.
- `value` (string, required): Credential to set, e.g. "1234".

### `set_location` (~75 tokens)

Set location

Set the emulator's mock GPS location (longitude, latitude) — for location-gated features.

Input parameters:

- `latitude` (number, required): Latitude of the mock GPS fix.
- `longitude` (number, required): Longitude of the mock GPS fix.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `set_sensor` (~205 tokens)

Set sensor

Set an emulated hardware sensor value (adb emu sensor set) — drive accelerometer/gyroscope/orientation (pass x, y, z) or a single-value sensor like light/proximity/temperature/pressure/humidity (pass x only). Use to exercise shake/tilt/rotation handlers or ambient-light/proximity logic. Emulator-only.

Input parameters:

- `sensor` (string, required): Sensor name, e.g. acceleration, gyroscope, magnetic-field, orientation (3 values) or light, proximity, temperature, pressure, humidity (1 value).
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `x` (number, required): First value (the only value for single-axis sensors like light/proximity).
- `y` (null|number): Second value for multi-axis sensors. Omit for single-value sensors.
- `z` (null|number): Third value for multi-axis sensors. Omit for single-value sensors.

### `set_status_bar` (~429 tokens)

Set status bar

Pin a clean status bar via SystemUI demo mode (enabled=true) — fixed clock, chosen signal/battery, no notification icons by default — so screenshots for docs don't leak the wall clock or a random signal state. Optionally set clock (HHMM), battery (0-100), network_type (wifi/mobile/none) with mobile_level/data_type/carrier for mobile, and notifications_visible/notification_icon. Call with enabled=false to restore the live bar.

Input parameters:

- `battery` (null|integer): Fixed battery level 0-100 (shown unplugged). Only when enabled=true; omit to leave unchanged.
- `carrier` (string): Carrier/operator name shown in the status bar. Only used when network_type=mobile.
- `clock` (string): Fixed clock as 4 digits HHMM, e.g. "1200". Only when enabled=true; omit to leave unchanged.
- `data_type` (string): Mobile data-type icon shown next to the signal, e.g. lte, 4g, 5g, 3g, edge, 1x, h, h+, roam. Only used when network_type=mobile.
- `enabled` (boolean, required): true = enter demo mode (clean, fixed status bar); false = exit and restore the live status bar.
- `mobile_level` (null|integer): Mobile signal bars 0-4. Only used when network_type=mobile. Default 4.
- `network_type` (string): Network icon: "wifi" (default, full signal), "mobile", or "none" (hide network icons). Only when enabled=true.
- `notification_icon` (string): Best-effort: an AOSP SystemUI icon resource name to show in the first notification slot. Support varies by SystemUI/Android version. Only when enabled=true.
- `notifications_visible` (null|boolean): Show notification icons in the status bar. Default false (hidden, for clean screenshots). Only when enabled=true.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `shutdown_emulator` (~54 tokens)

Shutdown emulator

Power off a running emulator (adb emu kill). Use when you are done with a device you booted.

Input parameters:

- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `start_logcat_capture` (~101 tokens)

Start logcat capture

Begin streaming logcat into a buffer for this device (optionally clearing first). Pair with stop_logcat_capture to get everything logged DURING a flow — use this instead of the one-shot 'logcat' when you need logs across an interaction.

Input parameters:

- `clear` (null|boolean): Clear the logcat buffer before capturing. Default true.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `start_screen_record` (~59 tokens)

Start screen record

Start recording the screen to an mp4 on the device (Android caps a single recording at ~180s). Pair with stop_screen_record.

Input parameters:

- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `stay_awake` (~143 tokens)

Stay awake

Keep the display from dozing during a driving session (svc power stayon true) — the fix when screenshots keep coming back black with screen_off:true because the device sleeps between steps (common on emulators with a short timeout). enabled=true holds the screen on while charging (emulators always are); enabled=false restores the normal timeout. describe_ui works regardless, but any screenshot/coordinate flow needs the screen on.

Input parameters:

- `enabled` (boolean, required): true = keep the screen on while charging (svc power stayon true); false = restore the normal display timeout.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `stop_app` (~73 tokens)

Stop app

Force-stop an app by package name. Pair with launch_app to reset an app to a clean start when reproducing a bug.

Input parameters:

- `package` (string, required): Application package name (e.g. com.example.app).
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `stop_logcat_capture` (~240 tokens)

Stop logcat capture

Stop the running logcat capture and return what was collected since start, optionally filtered by a case-insensitive substring, a minimum priority (V/D/I/W/E/F), and/or tags (OR'd). Output is capped to the last 500 lines by default (override with tail) so a long capture doesn't blow the token budget — narrow with the filters first.

Input parameters:

- `filter` (string): Case-insensitive substring to keep.
- `priority` (string): Minimum priority to keep: V, D, I, W, E, or F.
- `redact` (boolean): Mask common secrets (token, password, authorization, api key, secret) before returning captured lines.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `tags` (null|array): Keep only lines whose log tag contains one of these (case-insensitive, OR'd).
- `tail` (integer): Keep only the last N lines after filtering (the most recent, where a crash usually is). Default 500; pass a larger number for more, or a huge one to effectively disable the cap.

### `stop_screen_record` (~75 tokens)

Stop screen record

Stop the screen recording, finalize the mp4, and pull it to a local path.

Input parameters:

- `local_path` (string, required): Local path to save the pulled mp4, e.g. /tmp/rec.mp4.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `swipe` (~236 tokens)

Swipe

Swipe/drag from a start point to an end point. Params: x1,y1 (start) and x2,y2 (end) in true device pixels — x and y are accepted aliases for x1 and y1. To SCROLL DOWN a list, swipe from a HIGH y to a LOW y (drag the content up); reverse to scroll up. A longer duration_ms gives a slower, controlled drag; a short one flings.

Input parameters:

- `duration_ms` (integer): Swipe duration in ms. Default 300.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `x` (null|integer): Alias for x1 (start X).
- `x1` (null|integer): Start X in true device pixels (alias: x).
- `x2` (integer, required): End X in true device pixels.
- `y` (null|integer): Alias for y1 (start Y).
- `y1` (null|integer): Start Y in true device pixels (alias: y).
- `y2` (integer, required): End Y in true device pixels.

### `tap` (~346 tokens)

Tap

Tap a single coordinate in true device pixels. Use a center value from describe_ui. If a tap seems to do nothing, the coordinate is almost always stale/misscaled — re-run describe_ui and use a fresh center. Prefer tap_on_text when you know the element's label. When a tap lands but nothing happens, pass identify=true (reports which element the coordinate hit — or that it hit a non-clickable wrapper / no reported element) and/or verify_change=true (reports whether the UI actually changed) to tell the failure modes apart. Note: some native views (e.g. Compose/RN NativeTabs bars) don't respond to coordinate taps at all — use tap_on_text/tap_element with via_accessibility=true there (EXPERIMENTAL, requires `adb-mcp bridge install` once per device).

Input parameters:

- `identify` (null|boolean): Also report which UI element the coordinate lands in (a hit test against the hierarchy read just before tapping). Use when a tap seems to do nothing: it tells you whether the coordinate hit the eleme…
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `verify_change` (null|boolean): Also report whether the UI hierarchy changed after the tap (ui_changed: true/false). Costs two extra hierarchy reads (~2-3s); use when a tap silently doing nothing would send you down the wrong path.
- `x` (integer, required): X coordinate in true device pixels.
- `y` (integer, required): Y coordinate in true device pixels.

### `tap_element` (~398 tokens)

Tap element

Find an element by resource_id and tap its center — the id-addressed sibling of tap_on_text, for elements with no visible label. Runs describe_ui internally (filter=all, so even unlabeled wrapper nodes are findable) and re-resolves the element right before tapping, narrowing the window where a stale coordinate lands on an overlay (e.g. an Expo dev-menu bubble) the a11y tree never reported. Use exact match (partial=false) to avoid hitting the wrong item when ids overlap; verify_change reports whether the tap had any visible effect. If a coordinate tap lands but nothing happens (e.g. a Compose/RN NativeTabs bar), pass via_accessibility=true to dispatch a real accessibility click instead (EXPERIMENTAL — see that param's description).

Input parameters:

- `partial` (null|boolean): Substring match instead of exact. Default true.
- `resource_id` (string, required): Resource id to find and tap, e.g. "com.example.app:id/submit_button" or just "submit_button" (matches by substring by default).
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `verify_change` (null|boolean): Also report whether the UI hierarchy changed after the tap (ui_changed: true/false). Costs two extra hierarchy reads (~2-3s); use when a tap silently doing nothing would send you down the wrong path.
- `via_accessibility` (null|boolean): EXPERIMENTAL. Dispatch a real accessibility click (AccessibilityNodeInfo.performAction(ACTION_CLICK)) through the adb-mcp accessibility bridge instead of a coordinate tap — reaches native views (Comp…

### `tap_on_text` (~358 tokens)

Tap on text

Find an element by its visible text or content-description and tap its center — the one-shot way to press a labelled button/row without computing coordinates yourself. Runs describe_ui internally and prefers a clickable match. Matching folds typographic punctuation to ASCII, so a typed "Don't allow" finds the system dialog's "Don’t allow" (U+2019) and an en/em dash matches a hyphen. Use exact match (partial=false) to avoid hitting the wrong item when labels overlap. If a coordinate tap lands but nothing happens (e.g. a Compose/RN NativeTabs bar), pass via_accessibility=true to dispatch a real accessibility click instead (EXPERIMENTAL — see that param's description).

Input parameters:

- `partial` (null|boolean): Substring match instead of exact. Default true.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `text` (string, required): Text or content-description to find.
- `verify_change` (null|boolean): Also report whether the UI hierarchy changed after the tap (ui_changed: true/false). Costs two extra hierarchy reads (~2-3s); use when a tap silently doing nothing would send you down the wrong path.
- `via_accessibility` (null|boolean): EXPERIMENTAL. Dispatch a real accessibility click (AccessibilityNodeInfo.performAction(ACTION_CLICK)) through the adb-mcp accessibility bridge instead of a coordinate tap — reaches native views (Comp…

### `uninstall_app` (~91 tokens)

Uninstall app

Uninstall an app by package name (adb uninstall). Use to remove a build before a clean install, or to verify first-run behavior after reinstalling. To keep the app but reset it, prefer clear_app_data.

Input parameters:

- `package` (string, required): Application package name (e.g. com.example.app).
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.

### `wait` (~121 tokens)

Wait

Sleep for a number of seconds (fractions ok, capped at 300), then return. For TIME-based conditions where wait_for_text's polling doesn't apply: backgrounding an app long enough to trip a native auth timer, waiting out a cooldown or rate limit, letting a long animation finish. If the sleep sits BETWEEN actions whose timing you are testing, put the whole flow in run_sequence instead — an agent round-trip per step perturbs native timers.

Input parameters:

- `seconds` (number, required): How long to wait, in seconds. Fractions allowed; capped at 300.

### `wait_for_boot` (~77 tokens)

Wait for boot

Block until a device finishes booting (sys.boot_completed=1). Only needed if you started an emulator elsewhere; boot_emulator already waits by default.

Input parameters:

- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `timeout_s` (integer): Timeout in seconds. Default 120.

### `wait_for_text` (~208 tokens)

Wait for text

Poll the UI until an element with the given text/content-description appears (or times out), then return it. Use this after an async action (network load, navigation, animation) instead of a blind wait-then-screenshot — it returns as soon as the element is present, with its tappable center. Note: canvas-drawn (RN/Skia) text never enters the hierarchy, so it will time out on those — screenshot instead.

Input parameters:

- `partial` (null|boolean): Substring match instead of exact. Default true.
- `scroll` (null|boolean): When true, swipe upward through the current scroll container while polling. Useful when Android omits off-screen ScrollView content from the accessibility tree.
- `serial` (string): Target device serial (adb -s). Optional when exactly one device is attached.
- `text` (string, required): Text or content-description to wait for.
- `timeout_s` (integer): How long to wait, in seconds. Default 15.

## Diagnostics

Captured diagnostic sections: Provenance. The full working is on the page: https://verifymcp.io/servers/iksnerd-adb-mcp/ghcr-io-iksnerd-adb-mcp#diagnostics

## Score history

- 2026-11-05: 46
- 2026-11-04: 46
- 2026-11-03: 45
- 2026-11-02: 45
- 2026-11-01: 44
- 2026-10-31: 42
- 2026-10-30: 42
- 2026-10-29: 41
- 2026-10-28: 41
- 2026-10-27: 41
- 2026-10-26: 40
- 2026-10-25: 40
- 2026-10-24: 39
- 2026-10-23: 39
- 2026-10-22: 38

## Common questions

### What is the adb-mcp: Android emulator and device control over adb server?

adb-mcp: Android emulator and device control over adb is listed in the public MCP registry as io.github.iksnerd/adb-mcp. Drive an Android emulator or device over adb: screenshot, inspect UI, tap, type, build and test. This page covers its container image (ghcr.io/iksnerd/adb-mcp).

### Is the adb-mcp: Android emulator and device control over adb server safe to use?

adb-mcp: Android emulator and device control over adb scores 46 out of 100 on VerifyMCP. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the adb-mcp: Android emulator and device control over adb server expose?

adb-mcp: Android emulator and device control over adb exposes 80 tools: adb_reverse, app_state, avd_snapshot, boot_emulator, build_and_run, and 75 more. Their descriptions and schemas cost roughly 14,039 tokens of context every time the server is loaded.

### Is the adb-mcp: Android emulator and device control over adb server still maintained?

adb-mcp: Android emulator and device control over adb is still listed as active in the MCP registry. We last reached this channel on 5 November 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

### What did VerifyMCP test on the adb-mcp: Android emulator and device control over adb server?

VerifyMCP ran 12 checks on adb-mcp: Android emulator and device control over adb, covering Source repository, Provenance, License, Maintenance and 8 more. 4 items could not be completed, a gap on our side rather than a finding about adb-mcp: Android emulator and device control over adb: Malware scan, Known CVEs, Install scripts and Dependency health. One category scores 0 for that reason, because we only credit what we can confirm. Endpoint Security and Transport & Reachability do not apply to a container image, so they were not tested. The score does not measure everything, and the scoring documentation lists what it leaves out.

## Links

- GitHub Container Registry: https://ghcr.io/iksnerd/adb-mcp
- Repository: https://github.com/iksnerd/adb-mcp
- Website: https://adb-mcp.com/
- Changelog RSS feed: https://verifymcp.io/servers/iksnerd-adb-mcp/ghcr-io-iksnerd-adb-mcp.xml
- Changelog JSON feed: https://verifymcp.io/servers/iksnerd-adb-mcp/ghcr-io-iksnerd-adb-mcp/changelog.json
- Trust report (JSON): https://verifymcp.io/servers/iksnerd-adb-mcp/ghcr-io-iksnerd-adb-mcp.json
- HTML version of this page: https://verifymcp.io/servers/iksnerd-adb-mcp/ghcr-io-iksnerd-adb-mcp
