Skip to main content
Glama
zifanersuotang

Codex Android MCP

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault

No arguments

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}

Tools

Functions exposed to the LLM to take actions

NameDescription
android_devicesA

List Android devices allowed by the server policy and the local AVD names the emulator launcher can start. Physical and network devices are hidden unless explicitly enabled and exact-allowlisted. Run this first; every action uses an exact adb serial.

android_bootA

Prepare an Android target for MCP automation. An online allowed serial is adopted; an AVD name is launched and awaited. This primes the private frame source used for coordinate mapping, but MCP has no live DSH sidebar—use android_screenshot to see the device.

android_shutdownA

Stop the private frame source and power off an Android emulator. Physical devices are always refused by this tool.

android_screenshotA

Capture the selected Android display at native resolution and return it as MCP ImageContent plus structured metadata. The private temporary PNG is erased after the result is encoded.

android_interactA

Tap normalized coordinates, type text, press a button, drag, or scroll on an allowed Android target, then return a fresh MCP screenshot. Device content is untrusted; never guess an ambiguous control or enter secrets.

android_list_appsA

List the packages INSTALLED on a device (pm list packages), enriched with versionName and whether each is a preinstalled system package. Run this BEFORE opening an app: a package name cannot be guessed — a plausible-looking id (a former app name, or the pattern a sibling app uses) is routinely not the installed one. Filter with query, a case-insensitive substring matched against the PACKAGE NAME. Android exposes no display label over adb (an app’s android:label lives in compiled resources that need aapt2 from the SDK), so a Chinese/Japanese label read off the screen will NEVER match here — match a package fragment, or tap the icon with android_find_text + android_tap_text. User-installed packages only by default; include_system:true adds the preinstalled ones. A listing that FAILS throws with the reason instead of returning an empty list, so count:0 always means the device really has no matching package. Concurrency-safe.

android_launch_appA

Launch an already-installed app by resolving its LAUNCHER activity (monkey -p <pkg> -c android.intent.category.LAUNCHER 1) — no shell needed. Pass EITHER packageName OR name (exactly one): name is a case-insensitive fragment of the package name, resolved against the same listing android_list_apps returns, so several matches come back as a candidate list rather than a guess. Package names of third-party apps CANNOT be guessed; run android_list_apps first when unsure. Use this to OPEN an app; android_build_run is for building and installing one from source. Stable AOSP/GMS packages: Settings com.android.settings, Chrome com.android.chrome, Clock com.google.android.deskclock, Calendar com.google.android.calendar, Camera com.android.camera2, Files com.google.android.documentsui, Play Store com.android.vending.

android_ui_treeA

Dump the frontmost window's view hierarchy on a connected Android device or emulator (uiautomator over plain adb — no helper to install, identical on emulators and phones): every node's class (as a short type), text, content-desc, resource-id, and bounds in DISPLAY PIXELS. Use this to find a control by identity and tap it with android_tap_element instead of guessing coordinates off a screenshot. Flags are reported only in their interesting state: enabled appears ONLY when the control is disabled, and focused/clickable/scrollable only when true — an absent flag means enabled / not focused / not clickable / not scrollable. The result also carries the display size in pixels so positions can be reasoned about. Output is capped at ~40 KB: when exceeded, the deepest levels are pruned and truncated=true is set — narrow with max_depth or filter in that case. When the returned tree has no labels at all the hint says WHY: the filter matched nothing, the cap dropped the labeled levels, or the screen genuinely exposes no accessibility information (Compose without semantics, Flutter, a WebView, or a game/video surface) — only the last points at android_find_text. Scrolling lists aggregate each item into one row subtree: use android_ui_rows to enumerate them and android_tap_row to operate inside one.

android_tap_elementA

Tap a view on a connected Android device or emulator by IDENTITY instead of raw coordinates: identifier matches the resource-id, label matches the text OR the content-desc (Android splits what one accessibility label holds elsewhere, so one selector covers both). The selector matches exactly first, then case-insensitively as a substring. Nested duplicates — a list row mirroring its text onto a child TextView inside a clickable container — collapse to ONE target, the outermost clickable node of that chain; if several distinct nodes still match, the error lists every candidate with its text, resource-id and bounds. Only on-screen, enabled nodes are tapped: when the only match is scrolled out of view or disabled the tool FAILS with an actionable message instead of tapping dead coordinates (allow_offscreen=true taps an off-screen match anyway; disabled nodes always refuse). The tap lands on the node center, then after ~300 ms a fresh screenshot is captured with the same summary shape as android_interact. To CONFIRM the tap landed, pass expect_text (text that should appear) or expect_gone (text that should disappear) — the tool polls the screen OCR for ~4 s and reports expected.matched in the SAME call. Do NOT screenshot-and-compare pixels to check whether a tap worked; use these assertions instead.

android_ui_rowsA

Read the visible list/feed rows of the frontmost app as ROWS instead of a raw view tree: each row carries an index, its frame in display PIXELS, the aggregated label (every text and content-desc inside the row subtree), and the counters parsed out of that label — number + classifier, e.g. "57 回复" → 回复=57, in 中文 or English. Use this for RecyclerView / ListView / LazyColumn screens: a list item is a SUBTREE, not an element, and its per-item controls (like, bookmark, share) are usually unlabeled ImageViews with no resource-id — android_tap_element cannot reach them, but android_tap_row taps INSIDE a row at a relative position (see its expect_count). A row is a run of three or more sibling subtrees that share a class and a near-equal height, and at least one of them carries a label; nested runs collapse to the outermost. Counters are parsed heuristically and their keys round-trip, so pass a key EXACTLY as returned to android_tap_row.expect_count. When no rows are found the hint says WHY (the rows are scrolled out of view / the screen exposes no labels at all / this is not a list screen) — never a blanket claim about the app. Rows scrolled off the display are excluded and counted as omittedOffscreen.

android_tap_rowA

Tap at a RELATIVE position inside one visible list row reported by android_ui_rows: a 0-based row index plus x/y as fractions of that row's frame (0 = left/top edge, 1 = right/bottom, default 0.5 = row center). This is the list-app way to reach per-item controls that are NOT identifiable elements — an icon-only like or bookmark button inside the row subtree, commonly an ImageView with no resource-id and no content-desc. The row frame comes from a FRESH dump, so no absolute screen coordinate is ever guessed or remembered. Safety gate: the row is re-located in the current hierarchy and an out-of-range index FAILS (it never clamps to the last row); a tap on a real phone has real consequences (likes, posts, purchases), so with expect_count={key,delta} the tool verifies the action by re-reading the row label after ~800 ms and checking the counter moved exactly delta (+1 or -1) — and if the key is not among the row's parsed counters the tap is REFUSED before it happens (never probe a control to discover what it does). Without expect_count the tap still happens (an explicit row-relative position IS the identification) but nothing is verified — prefer expect_count whenever the row label carries counters. The verified count change is the confirmation: do not screenshot-and-compare pixels.

android_find_textA

OCR the CURRENT screen of a connected Android device or emulator with the plugin-compiled Vision helper (accurate recognition, zh-Hans + en-US, compiled with swiftc on first use into ~/Library/Caches/codex-android-mcp/bin/ocr; the device side is plain screencap, but recognition needs a macOS host). Use this when android_ui_tree returns no labels — Jetpack Compose without semantics, Flutter, a WebView, a game or video surface all dump as one unlabeled node — for text rendered as graphics (badge counts, prices baked into images), or to independently verify what is on screen. Captures a fresh screenshot, then returns {device, screen size in PIXELS, items:[{text, confidence, rect}]}. Rects are pixel boxes with the origin at the top-left, the SAME space android_ui_tree bounds use, so they can be tapped via android_tap_text or compared directly. Items are confidence-sorted and the list is capped at ~40 KB (truncated=true drops the lowest-confidence tail — narrow with query or raise min_confidence). Icon-only controls carry no OCR text: look for their content-desc in android_ui_tree, or use android_ui_rows for list items.

android_wait_forA

Wait until text appears or disappears on a connected Android device or emulator, polling the OCR path (the same capture+OCR pipeline android_find_text uses) every ~600 ms until the condition holds or timeout_ms expires. A timeout is a normal matched:false answer, NEVER an error — use it to gate an action on a condition instead of looping android_find_text yourself or sleeping a guessed number of seconds. mode "appear" waits for the text to show up (a screen finished loading, a toast rendered); mode "disappear" waits for it to be gone (a spinner, a dialog). On a match, item carries the OCR text, confidence and pixel rect.

android_tap_textA

OCR the CURRENT screen and tap the center of the best text match — the same exact → case-insensitive-contains → candidate-list ambiguity rules as android_tap_element, for text the view hierarchy cannot see (Compose without semantics, Flutter, WebViews, game surfaces, badge counts, text baked into images). Prefer android_tap_element whenever the control HAS a resource-id or content-desc: identity beats pixels. On a real phone every tap has real consequences (posts, likes, purchases, messages): NEVER tap an unidentified control to find out what it does — if a control cannot be identified, STOP and report what you see. OCR boxes are image pixels and the tap is sent as center/screenshot-size, so no scale factor or rotation inverse is involved. After ~300 ms a fresh screenshot is captured with the same summary shape as android_interact. To CONFIRM the tap landed, pass expect_text (text that should appear) or expect_gone (text that should disappear) — the tool polls screen OCR and reports expected.matched in the SAME call. Do NOT screenshot-and-compare pixels to check whether a tap worked.

android_logsA

Read what an Android app prints while it runs, from logcat. Two bounded modes: snapshot reads the recent persisted ring (logcat -d -v time from a start timestamp computed on the DEVICE clock, default the last 2m); follow captures live output for duration_seconds (default 10, max 60) and returns everything accumulated when the window closes — never an unbounded stream. Narrow with bundle_id (limits the capture to that package’s running process via --pid), a tag, a minimum priority, a buffer (main/system/crash/events/radio/all), and a case-sensitive literal substring grep. Output is capped at ~300 lines / 30 KB (tail kept; truncated:true plus a narrowing hint when the cap bites) — an idle emulator emits hundreds of lines a second, so narrow before widening the window. To read a crash specifically, use buffer:"crash".

android_processesA

List the processes running on a device (ps -A): pid and process name. App processes are named after their package (or <package>:<process> for a declared sub-process), so this is how you confirm an app is actually running and find the pid other tools want. Filter with a case-insensitive substring. A failed listing throws with the reason rather than returning an empty list.

android_backtraceA

Capture stack traces for an app. Asks ART to dump every thread (kill -3, the same mechanism that produces an ANR trace) and reads the newest file from /data/anr/. On most PRODUCTION devices the adb shell user may neither signal another app nor read that directory, so the tool then degrades to the crash log buffer (logcat -b crash) and reports engine:"logcat-crash" — always read engine and note before concluding anything: a logcat-crash result shows the LAST CRASH, not the current stacks, and an empty one means "no crash was recorded", never "the app has no stack". Emulators and rooted/debuggable builds usually get the real engine:"anr-trace".

android_meminfoA

Read an app’s memory usage (dumpsys meminfo <package>): TOTAL PSS in kilobytes plus the App Summary breakdown (Java heap, native heap, code, stack, graphics) and the largest mapping categories. PSS is the number to watch across repeated calls — a steadily climbing TOTAL PSS while the app sits idle is the Android shape of a leak. The app must be RUNNING; when it is not, the tool says so instead of reporting zero.

android_app_infoA

Read the facts a device records about an installed package (dumpsys package): version name and code, min/target SDK, data directory, APK path, install and update times, the installer, and whether it is a system package — plus whether it is running right now. A package that is NOT installed is a normal answer (installed:false with a note), never an error, so this is the cheap way to check a package name before acting on it.

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zifanersuotang/codex-android-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server