Skip to main content

app-automating

An MCP server that drives Android emulators and real devices — and remembers how it got there.

It gives a model three things: control of the Android SDK (create, boot, stop and delete emulators), control of the screen through Appium (tap, type, swipe, scroll, screenshot, read the view hierarchy), and a navigation memory that records every route taken so the next run can look up how it reached a screen instead of rediscovering it.

CI License: MIT Python 3.12+


What you get

28 tools across three modules. Every tool name is prefixed with its module, so a flat tool list stays grouped.

Module Tools What it does
android_ 10 Lists devices, creates and renames AVDs, downloads system images, boots emulators headed or headless, stops and deletes them
appium_ 13 Starts and ends sessions, taps by coordinate or locator, types, swipes, scrolls, presses keys, screenshots, dumps the view hierarchy, checks the host, installs drivers
navigation_memory_ 5 Answers "where am I", "how do I get from here to there", searches remembered screens, replays a run, forgets one

The navigation memory is not a separate step. When it is enabled — it is by default — it wraps the Appium session and gesture ports at the composition root, so every interaction records itself with no extra tool call. The cost is one extra page-source capture per gesture; AT_NAVIGATION_MEMORY_RECORD_INTERACTIONS=false turns it off and gives that latency back, leaving the read tools working against whatever was learned before.

Prerequisites

The server itself is pure Python, but it is a remote control for other people's tools. What you need depends on which tools you intend to call — you can install just the Android half, or just the Appium half, and the rest still works.

For You need Checked by
Anything Python 3.12+
android_* The Android SDK, with adb, emulator, avdmanager and sdkmanager. Set ANDROID_SDK_ROOT android_get_all_devices
Booting an emulator Hardware acceleration (KVM on Linux, Hypervisor.Framework on macOS) and a downloaded system image android_get_installed_emulators
appium_* Node.js 18+, the appium CLI, and the uiautomator2 driver appium_check_environment
navigation_memory_* Nothing. It is a local SQLite file

Call appium_check_environment once everything is in place — it reports exactly what is still missing and how to install it, rather than failing on your first gesture.

Supported platforms

Platform Status
Linux Supported and tested
Windows Not supported natively — use WSL2, below
macOS Should work; POSIX throughout, but not tested by the maintainers

Native Windows is not a packaging gap, it is a real one: the process handling in infrastructure/*/process.py uses os.killpg, os.getpgid and signal.SIGKILL, none of which exist on Windows, so every timeout and shutdown path would fail there. WSL2 is the supported route, and it is a good one — see Windows (WSL2).


Installing the prerequisites

Ubuntu / Debian

Tested on Ubuntu 24.04 LTS. Every block below can be pasted as-is.

1. Python and uv

sudo apt update
sudo apt install -y python3 python3-venv curl unzip
curl -LsSf https://astral.sh/uv/install.sh | sh

uv manages its own Python, so a distro Python older than 3.12 is not a problem — uv will fetch 3.12 when it needs one.

2. A JDK

sdkmanager and avdmanager are Java programs and will not start without one. JDK 17 or 21; 21 is what this project is developed against.

sudo apt install -y openjdk-21-jdk-headless
java -version

3. The Android SDK command-line tools

You do not need Android Studio. Note the latest/ directory in the last step — sdkmanager refuses to run unless it sits at cmdline-tools/latest/bin/sdkmanager, and unzipping the archive gives you cmdline-tools/bin/ instead. Getting this wrong is the single most common setup failure.

export ANDROID_SDK_ROOT="$HOME/Android/Sdk"
mkdir -p "$ANDROID_SDK_ROOT/cmdline-tools"

cd /tmp
curl -O https://dl.google.com/android/repository/commandlinetools-linux-13114758_latest.zip
unzip -q commandlinetools-linux-13114758_latest.zip

# The archive unpacks to ./cmdline-tools/ — it has to end up under latest/
mv cmdline-tools "$ANDROID_SDK_ROOT/cmdline-tools/latest"

Check the newest build number at developer.android.com/studio under "Command line tools only" if that URL has moved on.

4. Set the environment variables

cat >> ~/.bashrc <<'EOF'

# Android SDK
export ANDROID_SDK_ROOT="$HOME/Android/Sdk"
export ANDROID_HOME="$ANDROID_SDK_ROOT"
export PATH="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/emulator:$PATH"
EOF

source ~/.bashrc

5. SDK packages, and the licences

sdkmanager --licenses          # answer y to each; nothing installs until you do
sdkmanager "platform-tools" "emulator" "platforms;android-34"

6. A system image

An AVD cannot be created without one, and this project never downloads one for youandroid_download_image exists precisely so that the download is an explicit choice.

# google_apis (not playstore) pairs with any device profile
sdkmanager "system-images;android-34;google_apis;x86_64"

Use arm64-v8a instead of x86_64 on ARM hardware.

7. Hardware acceleration (KVM)

Without this an emulator boots by software emulation and is unusably slow — slow enough that it will exceed the boot timeout rather than merely annoy you.

# Does the CPU support it at all? A number greater than 0 means yes.
egrep -c '(vmx|svm)' /proc/cpuinfo

sudo apt install -y qemu-kvm
sudo usermod -aG kvm "$USER"
# Log out and back in, then confirm — this must print your username in the group:
ls -l /dev/kvm && groups | tr ' ' '\n' | grep -x kvm

If you are inside a VM, enable nested virtualisation on the host first.

8. Node.js, Appium and the driver

The distro's nodejs package is usually too old; nvm avoids sudo npm entirely.

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
nvm install --lts

npm install -g appium
appium driver install uiautomator2

9. Verify

adb --version                    # SDK platform-tools
emulator -version                # SDK emulator
avdmanager list device | head    # the JDK works
appium --version
appium driver list --installed   # uiautomator2 should be listed

[!WARNING] Do not apt install adb. The distro package puts an adb on your PATH that is routinely a different major version from the SDK's emulator, and the two fight over the adb server — the symptom is devices appearing and vanishing at random. On the machine this project is developed on, /usr/bin/adb is 34.0.4-debian while the SDK's is 37.0.1. This server resolves SDK-local tools ahead of PATH specifically to survive that, but your own shell will not, so prefer the SDK's platform-tools on your PATH as step 4 does. If you already installed it: sudo apt remove adb.


Windows (WSL2)

Native Windows does not work — see Supported platforms for the specific reason. Run the whole stack inside WSL2 instead.

[!NOTE] These steps are the standard WSL2 route, but the maintainers develop on Linux and have not tested them end to end. The nested-virtualisation step in particular is the one that varies between machines. Corrections via issue or PR are very welcome.

1. Install WSL2 with Ubuntu

In PowerShell as Administrator:

wsl --install -d Ubuntu-24.04
wsl --update

Reboot when prompted, then set your Linux username and password.

2. Enable nested virtualisation, so the emulator can use KVM

This is the step that decides whether you get a usable emulator or a slideshow. It needs Windows 11 (or Windows 10 build 19041+ with a recent WSL2). In PowerShell, create or edit %UserProfile%\.wslconfig:

[wsl2]
nestedVirtualization=true
memory=8GB
processors=4

Then, back in PowerShell:

wsl --shutdown

Reopen Ubuntu and confirm KVM is really there:

egrep -c '(vmx|svm)' /proc/cpuinfo   # must be > 0
ls -l /dev/kvm                       # must exist

If /dev/kvm is missing, nested virtualisation did not take effect and the emulator will not be usable — fix that before going further.

3. Everything else, inside WSL2

Follow the Ubuntu / Debian steps above from inside the WSL2 shell. The Android SDK, Node, Appium and this server all live in WSL2, not on Windows.

Do not install the Android SDK on the Windows side and try to reach it from WSL2 through /mnt/c. Windows .exe tools cannot be driven the way this server drives them, and the path translation will bite you.

4. A physical Android phone over USB

WSL2 has no direct USB access, so a cable alone will not do. Either use an emulator inside WSL2, or share the device with usbipd-win:

winget install usbipd
usbipd list                      # find your phone's BUSID
usbipd bind --busid <BUSID>
usbipd attach --wsl --busid <BUSID>

Then adb devices inside WSL2 should list it.

5. Point your MCP client at WSL2

A client running on Windows must launch the server through WSL:

{
  "mcpServers": {
    "app-automating": {
      "command": "wsl",
      "args": ["-d", "Ubuntu-24.04", "--", "bash", "-lc", "uvx app-automating"]
    }
  }
}

bash -lc matters: it loads your login profile, which is where step 4 of the Ubuntu instructions put ANDROID_SDK_ROOT. Without it the server starts and reports no Android SDK on a machine that plainly has one.


macOS

Untested by the maintainers, but the code is POSIX throughout and nothing in it is Linux-specific.

brew install --cask temurin          # a JDK
brew install --cask android-commandlinetools
brew install node
npm install -g appium
appium driver install uiautomator2

export ANDROID_SDK_ROOT="$(brew --prefix)/share/android-commandlinetools"
sdkmanager --licenses
sdkmanager "platform-tools" "emulator" "system-images;android-34;google_apis;arm64-v8a"

Use arm64-v8a on Apple Silicon and x86_64 on Intel. Acceleration is Hypervisor.Framework and needs no setup.

Nothing here reaches the network except android_download_image, which shells out to sdkmanager, and appium_install_driver, which shells out to npm.

Install

As a tool (recommended)

uv tool install app-automating

Or run it without installing anything permanent:

uvx app-automating

From the repository

For an unreleased commit:

uvx --from git+https://github.com/dkmostafa/app-automating app-automating

From a clone, for development

git clone https://github.com/dkmostafa/app-automating
cd app-automating
uv sync
uv run app-automating

Connect it to an MCP client

The server speaks STDIO and only STDIO. It refuses every other transport at the source, so it cannot be accidentally exposed on a socket — the tools it fronts drive every attached device with no authentication of any kind.

Claude Code

claude mcp add app-automating -- uvx app-automating

Claude Desktop, Cursor, and other clients that read a JSON config

Add this to the client's MCP configuration file:

{
  "mcpServers": {
    "app-automating": {
      "command": "uvx",
      "args": ["app-automating"],
      "env": {
        "ANDROID_SDK_ROOT": "/home/you/Android/Sdk"
      }
    }
  }
}

From a clone instead of an install:

{
  "mcpServers": {
    "app-automating": {
      "command": "uv",
      "args": ["--directory", "/path/to/app-automating", "run", "app-automating"],
      "env": {
        "ANDROID_SDK_ROOT": "/home/you/Android/Sdk"
      }
    }
  }
}

ANDROID_SDK_ROOT is worth setting explicitly. An MCP client launches the server without your shell profile, so a variable that is set in .bashrc will not be there — which shows up as "no Android SDK" on a machine that plainly has one.

Configuration

Every setting is an environment variable prefixed AT_, and every one has a working default. .env.example documents all of them, with the reasoning for each default; copy it to .env and edit if you want to pin something.

The settings you are most likely to touch:

Variable Default What it is for
ANDROID_SDK_ROOT / ANDROID_HOME auto-detected Where the Android SDK lives
AT_EMULATOR_LAUNCH_ARGS empty Flags added to every emulator launch, e.g. -gpu swiftshader_indirect on a machine with no usable GPU
AT_EMULATOR_BOOT_TIMEOUT_SECONDS 300 How long to wait for a booting AVD
AT_APPIUM_MANAGE_SERVER true Launch an Appium server when nothing is listening, and kill it on shutdown
AT_APPIUM_SERVER_PORT 4723 Where the Appium server listens, on loopback only
AT_NAVIGATION_MEMORY_DATABASE_PATH $XDG_DATA_HOME/app-automating/navigation_memory.db The navigation memory. :memory: for one that dies with the process
AT_NAVIGATION_MEMORY_RECORD_INTERACTIONS true Whether every gesture records itself

Configuration is read in exactly one place per module — the composition root — and handed down explicitly. Nothing deeper in the code reads the environment.

How it is built

Three modules under src/app_automating/modules/, one per surface, each a vertical slice split into the same four layers with a fixed direction of dependency:

presentation  ->  application  ->  infrastructure  ->  domain
       \____________________________________________^
  • domain/ — entities, the failure vocabulary, and the ports. No I/O, no frameworks, importable on a machine with no Android SDK.
  • infrastructure/ — the adapters: host binaries, devices, the filesystem, the database.
  • application/ — the services, and the di.py that wires them. The only layer allowed to name a concrete adapter.
  • presentation/ — the module's own MCP tools, their schemas, their rendering and their error translation.

src/app_automating/server.py is the server and nothing else: it owns the single STDIO FastMCP instance, builds each module's services through that module's di.py, registers the module's tools, and defines no tool of its own.

This is not decoration — it is enforced. Each module carries a tests/unit/test_architecture.py that walks the AST of every file in it and fails the build on a layer violation, a missing tool docstring section, an unregistered module, or a mock in the wrong place. The rules themselves are written down in CLAUDE.md and .claude/rules/.

Tests

uv run pytest -m unit          # pure code: no SDK, no device, <1s
uv run pytest -m integration   # the real toolchain: adb, avdmanager, Appium, a real boot
uv run pytest                  # everything

Tests live beside the code they cover, one test file per source file — there is no top-level tests/ tree.

[!WARNING] The integration suite drives this host for real. It creates AVDs, boots emulators, starts Appium servers and writes real files. It namespaces everything it creates, cleans up after itself even after a failure, and fails the run if it touched something it did not own — but it will take over your display and cost a minute of your machine. It never downloads anything, and it skips with a reason rather than failing on a host that cannot run it.

CI runs the unit and architecture tests only. There is no Android SDK and no device on a GitHub runner, so the integration tests skip there by design — they are yours to run.

Contributing

See CONTRIBUTING.md. The short version: the four-layer rules are enforced by tests rather than by review, so read .claude/rules/ before adding a layer, a component, or a dependency between two of them.

Security

An Appium server drives every attached device with no authentication of any kind, which is why this server binds it to loopback and speaks STDIO only. To report a vulnerability, see SECURITY.md.

License

MIT.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

app_automating-0.1.1.tar.gz (332.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

app_automating-0.1.1-py3-none-any.whl (230.8 kB view details)

Uploaded Python 3

File details

Details for the file app_automating-0.1.1.tar.gz.

File metadata

  • Download URL: app_automating-0.1.1.tar.gz
  • Upload date:
  • Size: 332.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for app_automating-0.1.1.tar.gz
Algorithm Hash digest
SHA256 39e967ad1d410e1c46b66c58f714894cae4e942fd49901befd50f607cbdb08ac
MD5 9998d6d180276600d5cd982be7a6dd81
BLAKE2b-256 f4fd927d5f4323a745dd5147e903a6f2a8ac06fca194e0f3375bb8c575646f38

See more details on using hashes here.

Provenance

The following attestation bundles were made for app_automating-0.1.1.tar.gz:

Publisher: release.yml on dkmostafa/app-automating

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file app_automating-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: app_automating-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 230.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for app_automating-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 25758e74f5468c427f0ea145c3834ae7eef889f8b630ca7b5c956a3b6f20f050
MD5 e0a14ab8af3008aba553a2f032f9b346
BLAKE2b-256 f61a07c6fc2fd0d1c811133ac802702e13cd9d809a9ad9beeb04e43718862ffc

See more details on using hashes here.

Provenance

The following attestation bundles were made for app_automating-0.1.1-py3-none-any.whl:

Publisher: release.yml on dkmostafa/app-automating

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page