Skip to main content
Glama

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 dump against a Flutter screen returns a bare FrameLayout with no children. apkscope enables 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: android-layout-inspector-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-mcp

Tools

tool

mutates device

what it gives you

screen_tree

no

every labelled element: class, flags, pixel bounds, centre, label

audit_layout

no

tap targets under 48dp, overlapping controls, off-screen nodes

screenshot

no

the screen rendered as text, in one of three modes

tap

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

Flags: 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

palette (default)

one glyph per pixel, plus a legend mapping glyph → true RGB

~2k tokens

layout, colour, shape

ramp

luminance ASCII (@%#*+=-:. )

cheap

structure only

ansi

truecolour half-blocks ▀, two pixels per character

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]"
pytest

97 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

Available Tools

4 tools
audit_layoutA
Read-onlyIdempotent

Check tap-target sizes, overlaps and (optionally) off-screen elements.

Reports TAP-SMALL (a control below min_tap_dp, Material's guidance is 48), OVERLAP (two controls sharing more than a third of the smaller one), and with check_clip CLIPPED (a node extending past the viewport).

Full-bleed wrappers are excluded: Flutter often wraps a whole screen in one GestureDetector, which would otherwise appear to overlap everything.

ParametersJSON Schema
NameRequiredDescriptionDefault
deviceNo
check_clipNo
min_tap_dpNo

TDQS

A4.1/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations cover the safety profile (readOnly, idempotent, non-destructive), and the description adds substantial behavior beyond that: the emitted codes TAP-SMALL, OVERLAP and CLIPPED, the overlap threshold (more than a third of the smaller control), the default Material tap guidance of 48, and the important exclusion of full-bleed wrappers. This is unusually rich disclosure of what the tool detects and why.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action, then codes, then the wrapper exclusion rationale. Every line carries information, though the multi-line formatting is slightly more expansive than strictly necessary for three checks.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, so the description usefully enumerates the report codes an agent will receive, which is the key missing piece. The only gap is the unexplained 'device' parameter, but otherwise an agent has enough to invoke and interpret this diagnostic tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must compensate. It adds real meaning for min_tap_dp (defines the below-threshold condition and cites Material's 48) and check_clip (node extending past the viewport), but the 'device' parameter is never explained anywhere, leaving one of three params undocumented.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a precise verb+resource: it audits layout, enumerating exactly what it checks (tap-target sizes, overlaps, off-screen elements) and what diagnostic codes it reports. An agent can distinguish this from screenshot/screen_tree/tap siblings purely from the description.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied through the checks performed and the conditional 'with check_clip CLIPPED' phrasing, which hints when clipping detection activates. However, there is no explicit statement of when to prefer this over screen_tree or screenshot, nor any when-not guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

screenshotA
Read-onlyIdempotent

Render the current screen as text.

mode: 'palette' (default) one glyph per pixel with a legend mapping glyphs to true RGB -- layout, colour and shape. 'ramp' luminance ASCII, cheapest and structure only. 'ansi' truecolour half-blocks, roughly 40 characters per pixel, so keep it to small crops and low cols.

crop: 'all', 'top', 'mid', 'bot', or 'a,b' as fractions of screen height. cols: output width in characters. squash: vertical scale factor, default 1.0 (true aspect); raise it to compress a tall screen.

Does not convey small body text, subtle typography, or whether a screen matches a design intent -- use screen_tree for exact content.

ParametersJSON Schema
NameRequiredDescriptionDefault
colsNo
cropNo
modeNopalette
colorsNo
deviceNo
squashNo

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, so the safety profile is covered. The description adds genuinely useful behavior beyond that: the relative cost and fidelity of each mode ('cheapest, structure only', 'roughly 40 characters per pixel'), and the explicit limitation that it cannot convey subtle typography or design intent. No return-format or pagination detail is given, keeping it from a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded in one line, followed by a compact per-parameter block and a closing limitation. The mode tests ('layout, colour and shape' / 'structure only') are terse and each sentence carries information, though the run-on mode paragraph is slightly dense.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, so the description is right to characterize output fidelity and limits. For a 6-parameter renderer it covers the parameters that matter most and explains the output's nature, with only 'colors' and 'device' left unexplained.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must carry the load, and it does for four of six parameters: mode (all three values explained), crop (all named values plus the 'a,b' fraction format), cols (output width), and squash (scale factor with default 1.0 semantics). 'colors' and 'device' are never mentioned, leaving a gap that prevents a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource: 'Render the current screen as text.' It immediately distinguishes itself from the sibling screen_tree by stating its output is a rendered approximation rather than exact content. An agent can identify the tool's role without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Names screen_tree as the alternative and states the condition that selects it ('exact content'), and warns that this tool does not convey small body text or design-intent matching. It also gives the mode-specific guidance to keep 'ansi' to small crops. No explicit when-not for the other siblings (tap, audit_layout), so not a full 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

screen_treeA
Read-onlyIdempotent

List every labelled element on screen with its bounds.

The primary way to read a Flutter UI: Flutter publishes its semantics tree only while an accessibility service is bound, which this tool arranges temporarily and then reverts.

Each line gives class, flags, pixel size, centre coordinate and label. Flags: C=clickable, F=focusable, E=enabled, k=checked, s=selected. Only elements marked C are real controls -- Flutter also marks plain text focusable, so do not treat F alone as tappable.

Pass tappable=True to get just the tap-target table.

ParametersJSON Schema
NameRequiredDescriptionDefault
deviceNo
tappableNo
max_depthNo

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations cover the safety profile (readOnly, idempotent, non-destructive), and the description adds material side effects they cannot express: it temporarily binds an accessibility service to make Flutter publish its semantics tree and then reverts it. It also warns that F alone does not mean tappable, which prevents a real mis-invocation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the core purpose, then the mechanism, then the output format and flags. Every sentence earns its place, and the flag legend is necessary because there is no output schema to carry it.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description correctly documents the return format (class, flags, pixel size, centre coordinate, label) and the flag legend. The remaining gaps are the device and max_depth parameters, which an agent still has to guess at.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the full burden, yet it only explains one of three parameters (tappable, with its effect on output). device and max_depth are never mentioned, leaving their format and purpose (e.g. depth truncation) entirely undocumented.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opening sentence gives a specific verb and resource ('List every labelled element on screen with its bounds') and the second sentence positions it precisely as the Flutter semantics-tree reader. This distinguishes it from sibling screenshot (pixels) and audit_layout (layout diagnostics) without needing to name them.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

States it is 'the primary way to read a Flutter UI,' which tells the agent when to reach for this over screenshot, and explains the tappable=True shortcut. It stops short of explicit when-not-to-use guidance or naming sibling alternatives for non-Flutter surfaces.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tapA

Tap the on-screen element whose label contains label.

Refuses an ambiguous label and lists the candidates, because a silent wrong tap navigates somewhere unexpected. Pass first=True to take the first match.

Returns the resolved label and coordinates actually tapped.

ParametersJSON Schema
NameRequiredDescriptionDefault
firstNo
labelYes
deviceNo

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare the safety profile (readOnlyHint=false, idempotentHint=false, openWorldHint=true), and the description adds genuinely non-redundant behavior: it refuses ambiguous labels, lists the candidates instead, and explains why ('a silent wrong tap navigates somewhere unexpected'). It also states the return content. Device targeting behavior is not disclosed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action, then the ambiguity/override rule, then the return value. Every sentence is useful, though the parenthetical rationale about unexpected navigation is slightly verbose for its value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, so the description helpfully states what is returned ('the resolved label and coordinates actually tapped'). Combined with the ambiguity behavior and the `first` override, an agent has nearly everything needed, with only device selection left unexplained.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must carry parameter meaning. It clarifies `label` as a substring match ('contains') and explains `first`, but the third parameter `device` is never mentioned, leaving a real gap at zero baseline coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb+resource: 'Tap the on-screen element whose label contains `label`.' This is unambiguous and clearly distinct from the read-only siblings (screenshot, screen_tree, audit_layout), so an agent can route to it without inspecting the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a concrete use-condition for the optional parameter ('Pass first=True to take the first match') and explains the default behavior of refusing ambiguous labels. It doesn't name alternatives, but the siblings are complementary rather than competing tools, so the guidance is adequate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 4 tool updatesv0.1.0
    • First observedaudit_layout
    • First observedscreen_tree
    • First observedscreenshot
    • First observedtap

TDQS

A4.1/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a clearly distinct role: screenshot renders pixels visually, screen_tree extracts semantic structure, audit_layout checks geometry heuristics, and tap performs an action. The descriptions explicitly cross-reference each other (e.g. screenshot points to screen_tree for exact content), eliminating overlap.

Naming Consistency3/5

All names are lowercase snake_case, but the grammatical pattern is mixed: 'screenshot' and 'screen_tree' are output nouns, 'audit_layout' is verb_noun, and 'tap' is a bare verb. Readable and non-chaotic, but not a predictable single convention.

Tool Count4/5

Four tools is a lean, well-scoped set covering the read/audit/interact loop for a Flutter screen. It is slightly thin (no gesture, input, or navigation helpers), but nothing feels redundant or out of place.

Completeness3/5

Read and audit coverage is solid, and tap handles basic interaction, but the surface lacks text input, scrolling/swiping, back navigation, and app launch/wait operations. These are notable gaps for a UI inspection/automation domain that will force agents to work around them.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    C
    maintenance
    Enables AI agents to measure the real geometry of a live Android UI over adb and uiautomator, reporting overlapping views, clipping, touch targets under 48dp, occluded or unlabeled controls, and portrait/landscape diffs. Analysis also runs against saved uiautomator dumps, so layouts can be checked in CI without a device attached.
    6
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI agents to visually perceive and directly control Android devices via ADB and screen-control tooling, including screenshots, taps, swipes, typing, app management, UI inspection, file transfer, shell commands, and audio/video streaming.
    154 npm
    MIT
  • A
    license
    B
    quality
    C
    maintenance
    Enables any MCP assistant to see and operate an Android phone or emulator over plain adb, reading the accessibility tree to tap elements by name and also supporting screenshots, typing, swiping, app launching and installation, and logcat — with nothing installed on the device and no root required.
    13
    MIT