Search

Python SDK

Android Automation in Python: mas SDK Overview

Android automation in Python with the mas package: how a MAS script launches, drives an emulator or cloud device, and where every function lives.

  • Windows
  • Mac
  • Emulator
  • Cloud device
  • Phone
  • Python SDK
  • Studio
Intermediate Updated 7 min read
On this page
  1. The mas package: Android automation in pure Python
  2. How a script is launched
  3. Project layout
  4. The import surface
  5. Module map
  6. A minimal Python Android emulator automation script
  7. Conventions

The mas package is the Python SDK for Android automation in Macro Automation Studio (MAS). A macro is a plain Python script that imports mas, looks at the screen of an Android emulator or cloud device, and taps, types and swipes its way through an app. This page explains what the package is, how MAS launches a script, how a project is laid out, and where each function lives. It is for developers about to write a first macro or read one written by someone else. If you would rather describe the task than write the script, MAS Agent writes this code for you.

The mas package: Android automation in pure Python

  • Pure Python, no dependencies. The package is standard library only, version 0.1.0, and needs Python 3.10 or newer.
  • It ships inside the app. MAS keeps its own Python 3.13 environment and installs the SDK wheel into it. There is nothing to pip install for a project, and import mas works in the Code Editor from the first line. Leave mas out of requirements.txt.
  • It talks to the app, not to adb. Every call is a JSON-RPC 2.0 request to the running app, which owns the device connection. The same script runs unchanged on an emulator over adb or on a cloud device.
  • The device is already bound. MAS starts your script with a device attached to the session. There is no connect or disconnect call; the first mas.* call opens the connection.

How a script is launched

When you click Run in the Code Editor, or start a macro from a device card, MAS runs the entry file as a module:

bash
python -u -m src.app --max_items 20 --headless

The project root is the working directory and is on PYTHONPATH, so src is importable as a package. -u keeps output unbuffered, so print lines reach the run console as they happen. Values from an argument form arrive as command-line flags, one --key value pair per field, with a checkbox sent as a bare --key when it is on.

MAS sets these environment variables for the process:

VariableValuePurpose
MAS_RPC_PORTa local portWhere the app listens for JSON-RPC
MAS_RPC_HOSTlocalhostThe RPC host
MAS_DEVICE_IDthe device idThe device this run is bound to
MAS_SESSION_TOKENa tokenPairs the script’s connection with the run
PYTHONPATHthe project rootMakes src importable
PYTHONUNBUFFERED1Unbuffered output

On the first mas.* call the SDK connects over TCP, registers the session token, and from then on sends one request per call and waits for the reply. The connect timeout is 5 seconds and the read timeout is 30 seconds. In a cloud run the app sets MAS_RPC_SOCKET instead, and the SDK talks over a unix socket; the script does not change.

When the script exits, MAS records the exit code with the run. Zero is a success; anything else marks the run as failed, and a macro.failed webhook event fires if you have an endpoint subscribed. See Errors for how to exit deliberately.

Project layout

A code project is a folder MAS creates for you. Only src/app.py is required.

FileWho writes itWhat it is
src/app.pyYouThe entry point. Runs as the module src.app
src/script_args.pyThe UI BuilderGenerated from the argument form. Parses the command line at import time; do not hand-edit it
ui.xmlThe UI BuilderThe argument form the app renders before a run
*.uibprojThe UI BuilderThe argument form design
*.uibrtThe UI BuilderThe runtime dashboard design; the first match in the folder is used
requirements.txtYouExtra packages your script needs. Never mas

Template images are not files in the project. They live in your Image Library in the cloud, each with an integer ID, and you crop them in Asset Lab. The script names them in one mas.images call, which the publisher also reads to bundle the right images with a Marketplace listing.

Read form values through the generated module:

python
from src.script_args import args

target = args.general.target_url    # tab "General", key "target_url"
limit = args.general.max_items      # Number Input widgets parse as int or float
headless = args.advanced.headless   # Checkbox widgets parse as bool

The UI Builder page covers the form designer.

The import surface

Everything is exported flat on mas. Functions, types, constants and exceptions all live at the top level:

python
import mas
from mas import Region, KeyCode, ColorConversion

mas.click(540, 960)
mas.key_press(KeyCode.BACK)
if mas.get_app_state("com.example.game") == mas.NOT_RUNNING:
    mas.open_app("com.example.game")

The one sub-namespace is mas.ui, which drives the runtime dashboard: mas.ui.set_text, mas.ui.set_progress, mas.ui.batch and so on. It is imported with mas; there is no separate import.

Module map

PageWhat it coversFunctions
InteractionTouch and keysclick, swipe, input_text, key_press, zoom_in, zoom_out
VisionScreenshots, template matching, OCR, image namestake_screenshot, find_object, find_objects, find_any_object, find_object_retry, find_any_object_retry, read_text, wait_for_object, images
ElementsThe accessibility treefind_element, find_elements, find_element_retry, read_element, scroll_to_element, read_page, dump_hierarchy, host_capabilities
Apps and deviceApps, device facts, logging, webhooksopen_app, close_app, get_current_app, get_app_state, is_app_focused, get_screen_size, get_device_info, get_host_machine_id, log, get_clipboard, webhook
StorageState between runssave, retrieve, retrieve_all, clear, get_current_device_port
Runtime UILive dashboardsmas.ui.*
ErrorsExceptions and handlingMASError, RPCError, ElementsUnavailable and the coded subclasses
API referenceEvery signature on one pageAll of the above, plus RPCClient and the types

A minimal Python Android emulator automation script

This script opens a game, waits for its home screen, taps Play, and counts the run in storage. It exits with 1 when a screen never appears, so the run shows as failed.

python
import mas

images = mas.images({"play_button": 42, "home_screen": 43})


def main() -> int:
    info = mas.get_device_info()
    mas.log(f"Running on {info.name} ({info.screen_width}x{info.screen_height})")

    mas.open_app("com.example.game")
    home = mas.find_object_retry(images.home_screen, total_tries=5, time_sleep=2.0)
    if home is None:
        mas.log("Home screen did not appear", level="error")
        return 1

    play = mas.find_object_retry(images.play_button)
    if play is None:
        mas.log("Play button not found", level="warning")
        return 1
    mas.click(play.x, play.y, delay_ms=1000)

    state = mas.retrieve("daily_play")
    runs = state.get("runs", 0) + 1
    mas.save("daily_play", {"runs": runs})
    mas.log(f"Done, run number {runs}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Conventions

  • find_object_retry is the wait primitive. It calls find_object up to total_tries times (default 3) with a flat time_sleep pause (default 2.0 seconds) and returns None when every try fails. Prefer it over wait_for_object, which is kept for existing macros, and over continuous_mode.
  • Name your images. One mas.images({...}) call near the top of src/app.py gives every Image Library ID a readable name and returns ImageRef objects that any vision call accepts. Raw integers still work, but the SDK prints a hint on exit listing the ones you forgot to declare.
  • None is a state, not an error. Every find_* call returns None or an empty list on a miss and never raises for one. Branch on it with if.
  • Log with mas.log. It writes a leveled line to the run console; print also shows there. Use level="warning" and level="error" so the log is scannable.
  • Exit on purpose. Return a non-zero code from main when the macro cannot do its job, and let unexpected exceptions propagate. Both mark the run as failed. The patterns are on the Errors page.
  • Keep the default pauses in mind. click waits 1000 ms after the tap and swipe lasts 1000 ms by default; lower them in tight loops.

Next steps

Related pages

Was this page helpful?

Questions? Ask in Discord