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

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

- Trust score: 38/100 (low)
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-08-22

## Components

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

## Channel facts

- Registry: `oci`
- Package: `ghcr.io/iksnerd/adb-mcp:0.22.2`
- 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-08-22.

- **Supply Chain Security**: 0/100
  - Malware scan not yet available for this package.
  - Known CVEs could not be checked: this artifact ships no SBOM, so there is no dependency list to read. Publishing one would let us assess it.
  - Install-script risk not yet assessed.
  - Dependency health could not be checked: this artifact ships no SBOM, so there is no dependency list to read. Publishing one would let us assess it.
- **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 5 days ago).
  - Publishes a security disclosure policy (SECURITY.md).
- **Schema Quality & AI Usability**: 82/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 13576 tokens (~163/item across 83 items; 78 tools + 5 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 0/100
  - Stability not yet verified: not enough scan history yet (needs a 30-day window).
- **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.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

**Unverified: 2 categories.** Categories scored 0 because we could not verify them: 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.22.2. 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.22.2
```

### Cursor

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

### VS Code

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

### Codex

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

### 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.22.2"
      ],
      "enabled": true
    }
  }
}
```

### Hermes

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

### Netclaw

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

### Other

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

## 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-08-16 (score 38)

First indexed and scored.

## MCP tools (78)

### `adb_reverse` (~208 tokens)

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` (~182 tokens)

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. Optionally pass source_path to compare the newest host source mtime with the latest epoch-timed Metro/HMR marker and flag stale JavaScript after git checkout/stash operations. 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 compares its newest mtime with the latest Metro/HMR marker to flag stale JavaScript.

### `avd_snapshot` (~129 tokens)

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` (~191 tokens)

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.

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.
- `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` (~220 tokens)

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 but in a single call. If several APKs exist under build/outputs (multi-flavor projects, leftover androidTest APKs), the newest non-test one is installed — the artifact the build just produced.

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.

### `cellular` (~334 tokens)

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)

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)

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)

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 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` (~419 tokens)

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 hierarchy. 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` (~97 tokens)

Diagnose the local toolchain: 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)

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 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)

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` (~226 tokens)

Simulate a fingerprint-sensor touch on an EMULATOR (adb emu finger touch). With a fingerprint enrolled, this satisfies a BiometricPrompt — drive the app's REAL biometric unlock path instead of cancelling into the PIN fallback every run. finger_id must match an enrolled finger (default 1). Check has_biometric_enrolled FIRST: with nothing enrolled this just sits on "Touch the sensor" forever. GOTCHA: the command reports OK even when the id matches nothing — if the prompt doesn't resolve, the enrolled id differs (re-enrollments increment it): try finger_id 2..5, send a second touch after ~1s, or re-enroll deterministically at session start (Settings > Security > Fingerprint, calling this tool for each wizard touch). Emulator-only; physical devices cannot inject biometrics.

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` (~85 tokens)

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.

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.

### `get_coverage_report` (~386 tokens)

Run a JaCoCo coverage-report Gradle task in project_dir (default 'jacocoTestReport', which runs the JVM unit tests first) and summarize the resulting code coverage: overall line/branch/method/class percentages plus a per-package breakdown, worst-covered package first. In a multi-module build every module's report is merged into one set of totals, and the merged report files are listed. IMPORTANT — 'jacocoTestReport' is NOT a task the Android Gradle plugin defines; it exists only if the project declares it. If the task is not found, run list_gradle_tasks and look for AGP's built-in 'createDebugUnitTestCoverageReport' (present when a build type sets enableUnitTestCoverage = true) and pass it as task=; both write the same JaCoCo XML and both are read. Covers JVM unit tests only, not instrumented (on-device) tests — there is no on-device analogue. Pass json=true for structured JSON instead of the text form.

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` (~409 tokens)

Same JaCoCo run as get_coverage_report (default task 'jacocoTestReport', same fallback to AGP's 'createDebugUnitTestCoverageReport' when that task doesn't exist), scoped to one source file: line/branch coverage, the exact missed and partially-covered line numbers, plus per-method detail — the per-function breakdown get_coverage_report's package-level summary doesn't carry. Use it to find WHICH lines to write a test for, after get_coverage_report says which package is weakest. file matches by suffix (e.g. "Foo.kt" or "com/example/Foo.kt") across every module of a multi-module build; an ambiguous bare filename returns every matching file, a miss lists what coverage data does exist so you can correct it. Same JVM-unit-test-only scope as get_coverage_report. Pass json=true for structured JSON instead of the text form.

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` (~192 tokens)

Build the app with Gradle (default task assembleDebug) in project_dir, and report the produced APK path(s). project_dir must contain the Gradle wrapper (gradlew). Runs on the host, not a device.

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. Ignored by gradle_build…
- `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.

### `gradle_project_properties` (~190 tokens)

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 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)

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)

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)

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 (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` (~64 tokens)

Report whether a secure lock screen is set (KeyguardManager.isDeviceSecure). 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)

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 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` (~249 tokens)

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 tcp: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 tcp: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 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` (~71 tokens)

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.

Input parameters:

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

### `list_gradle_projects` (~140 tokens)

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` (~215 tokens)

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:

- `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. Ignored by gradle_build…
- `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.

### `list_gradle_variants` (~218 tokens)

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 variants for the :app module. Defaults to the root project's tasks.

### `list_packages` (~72 tokens)

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)

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)

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 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 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)

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)

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` (~274 tokens)

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.

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)

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)

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)

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.

### `revoke_permission` (~104 tokens)

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 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` (~203 tokens)

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 instead of the text form.

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. Ignored by gradle_build…
- `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 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` (~246 tokens)

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. Note that 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 instead of the text form.

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. Ignored by gradle_build…
- `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)

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)

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)

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)

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)

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)

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

### `set_battery` (~235 tokens)

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)

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 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 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 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)

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)

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)

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 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)

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)

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 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 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/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 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)

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` (~317 tokens)

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. 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 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` (~89 tokens)

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.

Input parameters:

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

### `wait_for_boot` (~77 tokens)

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)

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-0-22-2#diagnostics

## Score history

- 2026-08-22: 38
- 2026-08-21: 38
- 2026-08-20: 38
- 2026-08-19: 38
- 2026-08-18: 38
- 2026-08-17: 38
- 2026-08-16: 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:0.22.2).

### 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 38 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 78 tools: adb_reverse, app_state, avd_snapshot, boot_emulator, build_and_run, and 73 more. Their descriptions and schemas cost roughly 13,364 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 22 August 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

## Links

- 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-0-22-2.xml
- Changelog JSON feed: https://verifymcp.io/servers/iksnerd-adb-mcp/ghcr-io-iksnerd-adb-mcp-0-22-2.json
- HTML version of this page: https://verifymcp.io/servers/iksnerd-adb-mcp/ghcr-io-iksnerd-adb-mcp-0-22-2
