adb-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| ANDROID_HOME | No | Path to the Android SDK. Used to locate adb and emulator. Falls back to ANDROID_SDK_ROOT or platform default. | |
| ANDROID_SDK_ROOT | No | Alternative environment variable for the Android SDK path. Used if ANDROID_HOME is not set. |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
adb-mcp drives Android emulators and devices over adb: boot, observe (screenshot/describe_ui), act (tap/swipe/type/keys), inspect apps, read logcat, run Gradle builds and tests.
React Native / Expo dev builds — in this order, before concluding anything:
adb_reverse {device_port: 8081} BEFORE launching. A dev client that cannot reach Metro does not error; it silently runs its EMBEDDED bundle and ignores every code edit.
app_state {package, source_path} to confirm the process is on Metro, that it is the RIGHT Metro (a dev server from another checkout still holds the port), that its JavaScript is not stale, and that only one process is live.
Absence of a signal is not evidence until the harness is proven to deliver that signal — an empty logcat is often a rotated buffer, and missing logs are often an embedded bundle. describe_ui and wait_for_text see only the current viewport; Android omits off-screen ScrollView content, so scroll before concluding an element is absent.
Batch timing-sensitive flows with run_sequence: a per-step round trip perturbs native timers.
Recipes, as plugin skills or as resources: rn-expo (android://guide/rn-expo), driving (android://guide/driving), getting-started (android://guide/getting-started).
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
| logging | {} |
| resources | {
"listChanged": true
} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| adb_reverseA | 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. |
| app_stateA | 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. |
| avd_snapshotA | 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. |
| boot_emulatorA | 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). |
| build_and_runA | 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. |
| build_statusA | 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. A job stopped with cancel_build reports as failed, cancelled by cancel_build. |
| cancel_buildA | Stop a background gradle_build by job id. Cancellation is asynchronous: build_status then reports the job as failed, with 'cancelled by cancel_build' as the reason. A job that already finished is reported as such and left alone; an unknown or evicted id is an error. |
| cellularA | 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 ":" kbps; network_delay like "umts" or ":" ms). Test offline/roaming/weak-signal and slow-network behaviour deterministically. Every field optional; set at least one. Emulator-only. |
| clear_app_dataA | 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. |
| clear_device_lockB | Remove the secure lock screen, supplying the current credential as old_value. Use to restore a clean state after testing a Keystore flow. |
| clear_logcatA | 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.) |
| connect_wirelessA | 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. |
| describe_uiA | 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. |
| doctorA | 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". |
| dragA | 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. |
| enter_pinA | 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. |
| finger_removeA | 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. |
| fingerprint_touchA | 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. |
| get_app_detailsA | 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. |
| get_coverage_reportA | 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. |
| get_file_coverageA | 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. |
| gradle_buildA | 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 and cancel_build stops it. |
| gradle_project_propertiesA | 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. |
| grant_permissionA | 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. |
| has_biometric_enrolledA | 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_key_comboA | 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_textA | 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. |
| install_appA | Install (or reinstall, -r) an APK from a local file path onto the device. Use to deploy a build you want to test. |
| is_device_secureA | 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. |
| last_crashA | 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. |
| launch_appA | 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. |
| launch_dev_clientA | Launch an Expo dev build straight at a Metro dev server, skipping the Dev Launcher's server-picker screen. Builds the "://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. |
| list_avdsA | 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_devicesA | List attached emulators/devices and their adb state (device = ready, offline, unauthorized). Pass serial to check one device: only that device is returned, or a note listing what is attached (the session default serial is ignored here). 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. |
| list_gradle_projectsA | 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 ':' (e.g. :app:assembleDebug). A single-module build reports no sub-projects. |
| list_gradle_tasksA | 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=":tasks" (e.g. ":app:tasks", from list_gradle_projects) to scope to a submodule instead. |
| list_gradle_variantsA | 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=":tasks" (e.g. ":app:tasks", from list_gradle_projects) to scope to the module that actually builds APKs. |
| list_packagesA | 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. |
| logcatA | 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. |
| long_pressA | Press and hold a coordinate (true device pixels) for a duration — for context menus, drag handles, and long-press actions. |
| open_dev_menuA | 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. |
| open_urlA | 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. |
| phone_callA | 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. |
| prefer_pinA | 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. |
| press_keyA | 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. |
| pull_fileA | Copy a file off the device to a local path (adb pull) — e.g. retrieve a generated file, database, or screenshot. |
| push_fileB | Copy a local file onto the device (adb push) — e.g. seed test data or a file to import. |
| reload_appA | Best-effort: trigger a Metro/JS reload on a React Native dev-client build via the classic .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). |
| render_statsA | 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. |
| revoke_permissionA | 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. |
| rotate_screenA | 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. |
| run_instrumented_testsA | 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. |
| run_sequenceA | 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. |
| run_unit_testsA | 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. |
| scaffold_android_projectA | 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 |
| screenshotA | 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. |
| send_smsA | 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. |
| session_clear_defaultsA | 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_defaultsA | 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. |
| session_show_defaultsA | Show the project_dir/serial defaults currently pinned for this session (set via session_set_defaults). |
| set_batteryA | 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.) |
| set_dark_modeA | Turn the system dark theme on or off (cmd uimode night) — to test light/dark appearances. The mode it found is remembered and put back when the server exits (when it could be read); call it again with the original value to restore it sooner, and doctor lists it while it is changed. |
| set_device_lockA | 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. |
| set_locationA | Set the emulator's mock GPS location (longitude, latitude) — for location-gated features. |
| set_sensorA | 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. |
| set_status_barA | 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. |
| shutdown_emulatorA | Power off a running emulator (adb emu kill). Use when you are done with a device you booted. |
| start_logcat_captureA | 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. |
| start_screen_recordA | Start recording the screen to an mp4 on the device (Android caps a single recording at ~180s). Pair with stop_screen_record. The display must be awake: an asleep display sends no frames and the recording comes out empty (the result warns; stay_awake fixes it). |
| stay_awakeA | 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. |
| stop_appA | Force-stop an app by package name. Pair with launch_app to reset an app to a clean start when reproducing a bug. |
| stop_logcat_captureA | 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. |
| stop_screen_recordA | Stop the screen recording, finalize the mp4, and pull it to a local path. Fails with an explanation if the recording holds no frames (the display was asleep) instead of saving an empty file. |
| swipeA | 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. |
| tapA | 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 |
| tap_elementA | 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). |
| tap_on_textA | 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). |
| uninstall_appA | 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. |
| waitA | 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. |
| wait_for_bootA | Block until a device finishes booting (sys.boot_completed=1). Only needed if you started an emulator elsewhere; boot_emulator already waits by default. |
| wait_for_textA | 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. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
| build-and-test | Mapping a multi-module project before guessing a task name, building a variant, running JVM vs on-device tests, and turning a JaCoCo coverage report into the specific lines that need a test — including why the default jacocoTestReport task often doesn't exist. |
| crash-triage | Using logcat to surface the real 'Caused by:' root cause hidden behind a generic UI error, plus app lifecycle tools for reproducing failures. |
| driving | The core observe→locate→act→re-observe loop, the true-pixel coordinate rule, and the gotchas (overlays eating taps, keyboard covering buttons, settle delays) that waste turns. |
| getting-started | How to list AVDs, boot an emulator, target a device with the serial argument, and run a first interaction. |
| pin-and-lock | Entering digits on native (non-IME) PIN pads, and setting/clearing a secure lock screen required by AndroidKeyStore / Keystore-backed crypto flows. |
| rn-expo | The ordered recipe for driving a dev build (adb_reverse before launch, app_state before believing anything), why "metro" does not mean the RIGHT metro, staleness after a git checkout, and the Expo traps that produce success-shaped failures. |
TDQS
Scored across 81 tools
Most tools have a clearly distinct verb+resource target, and the descriptions explicitly disambiguate the risky clusters (tap vs tap_on_text vs tap_element, logcat vs start/stop_logcat_capture, wait vs wait_for_text vs wait_for_boot, Gradle build vs build_and_run). Remaining soft spots are among the Gradle inspection tools (list_gradle_projects/tasks/variants, gradle_project_properties) and the session defaults trio, which an agent could still mix up.
The surface is overwhelmingly consistent snake_case with a verb_noun pattern (install_app, launch_app, set_dark_mode, start_screen_record). A handful of bare-noun or single-verb outliers (doctor, logcat, cellular, screenshot, tap, swipe, drag) deviate but remain readable and never mix casing conventions.
81 tools is far beyond a well-scoped set, even for a broad ADB/emulator/build domain; selection burden and description volume are high. Several clusters could be consolidated (three session-defaults tools, three Gradle-listing tools, gradle_build/build_and_run/build_status/cancel_build, start/stop capture pairs), though each tool does map to a real operation rather than being filler.
Coverage is extensive: device/emulator lifecycle, UI inspection and input, permissions, locks/biometrics, sensors/network/battery, logging, file push/pull, recording, Gradle build/test/coverage, and session defaults. Minor gaps remain around clipboard, notification injection, locale/date configuration, and app-private file/database inspection, but none block core device-driving workflows.