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
On this page
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 installfor a project, andimport masworks in the Code Editor from the first line. Leavemasout ofrequirements.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:
python -u -m src.app --max_items 20 --headlessThe 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:
| Variable | Value | Purpose |
|---|---|---|
MAS_RPC_PORT | a local port | Where the app listens for JSON-RPC |
MAS_RPC_HOST | localhost | The RPC host |
MAS_DEVICE_ID | the device id | The device this run is bound to |
MAS_SESSION_TOKEN | a token | Pairs the script’s connection with the run |
PYTHONPATH | the project root | Makes src importable |
PYTHONUNBUFFERED | 1 | Unbuffered 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.
| File | Who writes it | What it is |
|---|---|---|
src/app.py | You | The entry point. Runs as the module src.app |
src/script_args.py | The UI Builder | Generated from the argument form. Parses the command line at import time; do not hand-edit it |
ui.xml | The UI Builder | The argument form the app renders before a run |
*.uibproj | The UI Builder | The argument form design |
*.uibrt | The UI Builder | The runtime dashboard design; the first match in the folder is used |
requirements.txt | You | Extra 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:
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 boolThe 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:
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
| Page | What it covers | Functions |
|---|---|---|
| Interaction | Touch and keys | click, swipe, input_text, key_press, zoom_in, zoom_out |
| Vision | Screenshots, template matching, OCR, image names | take_screenshot, find_object, find_objects, find_any_object, find_object_retry, find_any_object_retry, read_text, wait_for_object, images |
| Elements | The accessibility tree | find_element, find_elements, find_element_retry, read_element, scroll_to_element, read_page, dump_hierarchy, host_capabilities |
| Apps and device | Apps, device facts, logging, webhooks | open_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 |
| Storage | State between runs | save, retrieve, retrieve_all, clear, get_current_device_port |
| Runtime UI | Live dashboards | mas.ui.* |
| Errors | Exceptions and handling | MASError, RPCError, ElementsUnavailable and the coded subclasses |
| API reference | Every signature on one page | All 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.
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_retryis the wait primitive. It callsfind_objectup tototal_triestimes (default 3) with a flattime_sleeppause (default 2.0 seconds) and returnsNonewhen every try fails. Prefer it overwait_for_object, which is kept for existing macros, and overcontinuous_mode.- Name your images. One
mas.images({...})call near the top ofsrc/app.pygives every Image Library ID a readable name and returnsImageRefobjects that any vision call accepts. Raw integers still work, but the SDK prints a hint on exit listing the ones you forgot to declare. Noneis a state, not an error. Everyfind_*call returnsNoneor an empty list on a miss and never raises for one. Branch on it withif.- Log with
mas.log. It writes a leveled line to the run console;printalso shows there. Uselevel="warning"andlevel="error"so the log is scannable. - Exit on purpose. Return a non-zero code from
mainwhen 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.
clickwaits 1000 ms after the tap andswipelasts 1000 ms by default; lower them in tight loops.
Next steps
Related pages
Thanks. If something is wrong, tell us in Discord.
Questions? Ask in Discord