apkscope
Reads and drives a running Android app as text via adb, without any image input. Provides tools to inspect the on-screen semantics/accessibility tree (element classes, flags, pixel bounds, centres and labels), audit layouts for undersized tap targets, overlapping controls and clipped off-screen nodes, render the screen as a coloured glyph/ASCII image with crop and palette controls, and tap elements by their label. This lets an agent inspect and interact with Android UIs, including Flutter screens whose semantics tree is normally hidden without a bound accessibility service.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@apkscopedump the tappable elements on the current Android screen"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
apkscope
See and drive a running Android app as text, for an agent whose model rejects image input.
Most LLM-based coding agents cannot look at a screenshot: the model refuses image content, and the usual "describe this image" helpers call a hosted vision API that bills per request or rate-limits you to nothing. This MCP server exists so such an agent can still work on an Android UI — reading what is on screen, measuring it, and tapping it.
Everything is converted into characters the model can read.
Why it exists
Developed while building a Flutter app (SahayakAI) with an agent that had no image input at all. Two capabilities made UI work possible:
Reading the semantics tree. Flutter publishes its accessibility tree only while an accessibility service is bound. With none bound,
uiautomator dumpagainst a Flutter screen returns a bareFrameLayoutwith no children.apkscopeenables a stock system service for the duration of the read and restores the previous setting afterwards.Rendering pixels as glyphs. Enough to judge layout, spacing, colour and gross breakage — not a substitute for vision, but far better than nothing.
Related MCP server: waydroid-mcp
Install
pip install -e .Requires adb on PATH, or $ANDROID_HOME / $ANDROID_SDK_ROOT set, or
$APKSCOPE_ADB pointing at the binary.
MCP setup
{
"mcp": {
"apkscope": {
"type": "local",
"command": ["apkscope-mcp"],
"environment": { "ANDROID_HOME": "C:\\Users\\you\\AppData\\Local\\Android\\Sdk" }
}
}
}Or run it directly to check it starts:
apkscope-mcpTools
tool | mutates device | what it gives you |
| no | every labelled element: class, flags, pixel bounds, centre, label |
| no | tap targets under 48dp, overlapping controls, off-screen nodes |
| no | the screen rendered as text, in one of three modes |
| yes | taps the element whose label matches |
screen_tree — start here
The primary way to read a Flutter UI. Labels, roles and exact bounds are precise and cheap; a pixel render is approximate and token-heavy.
View [-FE--] 530x98 @( 540, 842) Good morning
View [CFE--] 477x283 @( 283, 1750) Lesson Plan
Button [CFE--] 126x126 @( 765, 202) NetworkFlags: C clickable, F focusable, E enabled, k checked, s selected.
Only C means tappable. Flutter also marks plain text focusable, so F
alone is not a control — that distinction is what keeps headings out of the
tap list.
Pass tappable=True for just the tap-target table.
audit_layout — turn observation into findings
Reports TAP-SMALL (below Material's 48dp guidance, converted using the
device's real density), OVERLAP (two controls sharing more than a third of the
smaller one) and, with check_clip=True, CLIPPED off-screen nodes.
Full-bleed wrappers are excluded. Flutter routinely wraps a whole screen in one
GestureDetector (a "tap anywhere to speak" surface); without that exclusion
every other control is reported as overlapping it.
screenshot — pixels as text
Three modes:
mode | output | cost | good for |
| one glyph per pixel, plus a legend mapping glyph → true RGB | ~2k tokens | layout, colour, shape |
| luminance ASCII ( | cheap | structure only |
| truecolour half-blocks | very high (~40 chars/pixel) | detail crops only |
Keep colors at 8 or below. On a light-themed app a large palette gets
spent on imperceptible differences between near-identical backgrounds
(#f3f1ec vs #f7f5f0) and the one meaningful hue is lost. Fewer bins force
the quantiser to group the near-whites.
The legend always reports RGB from the unaltered image, so colour is never misrepresented.
crop accepts all, top, mid, bot, or 'a,b' fractions of height. A
phone screen is tall and mostly empty near the bottom; cropping a band is the
difference between a legible render and a hundred rows of background.
tap — drive the UI
Resolves a label to coordinates through the semantics tree, which survives layout changes and screen-size differences in a way hardcoded pixels do not.
Refuses an ambiguous label and lists the candidates, because a silent wrong tap
navigates somewhere unexpected and costs more to debug than asking. Pass
first=True when you know what you want.
CLI
The same operations without an MCP host:
apkscope tree --taps
apkscope audit --clip
apkscope shot --cols 72 --colors 8 --crop 0.3,0.6
apkscope shot --mode ramp --crop top
apkscope tap "Continue with Google"audit exits non-zero when it finds something, so it works in a CI check.
Honest limits
A glyph render is not vision. It conveys structure, colour, alignment, spacing and obvious breakage. It does not reliably convey small body text, subtle typography, or whether a screen matches a design intent. For fine visual judgement a human still has to look.
screen_tree and audit_layout are the higher-value tools for UI work —
exact, cheap, directly actionable. Reach for screenshot to catch layout
problems, not to proofread.
The tree only shows what Flutter marks semantic. Text painted directly to a
canvas is invisible to it, and to uiautomator generally.
Tests
pip install -e ".[dev]"
pytest97 tests, no device required. They pin down the bugs this tool actually hit while being built: an inverted luminance ramp, a palette legend that crashed on low-colour crops, a whole-screen overlap false positive from Flutter's full-bleed wrapper, and unlabelled icon buttons being invisible to the tap-target filter.
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Control real Android and iOS devices with LLM agents — tap, swipe, type, automate flows.
Cloud Android phones for AI agents: create a phone, read the screen, tap, type, install APKs, park.
Disposable cloud Android emulators for coding agents: run an APK or PR build, tap, type, screenshot.
Drive real devices from your AI Coding tool. Embed a client SDK (Unity, Godot, Flutter, iOS/macOS, Android, React Native, Web) in your app, then capture screenshots, traverse the UI tree, inject taps and key events, and run automated test tasks on the physical device over a secure relay.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables an agent to inspect and interact with Android emulators or physical devices via ADB, capturing UI snapshots, tapping nodes, typing text, and reading app logs.10MIT
- FlicenseNot gradedqualityBmaintenanceEnables agents to interact with Android via text-based UI trees instead of screenshots, supporting taps, swipes, input, macro recording, and device control through an MCP server and CLI.-
- FlicenseBqualityCmaintenanceEnables AI assistants to observe, reason about, and control connected Android devices via ADB, providing tools for screenshots, UI hierarchy parsing, semantic element clicking, gestures, text input, and app lifecycle management.13-
- AlicenseNot gradedqualityBmaintenanceEnables LLM agents to drive real Android devices or emulators by reading the live accessibility tree, acting on UI nodes instead of coordinates, verifying every action, and typing Unicode text.41 npm1Apache 2.0