Quick start: MCP
Start a session on a device, launch Settings, tap a row, take a screenshot, and quit — from an MCP client. Starts with app_session with {"action": "start", "platform": ..., "app": ...}; ends with app_session with {"action": "end"}, or the client closing the server's stdin.
Before this page: install mobium and prepare a device.
1. What you need
An agent that speaks MCP — Claude Code, or any MCP client — to drive devices with Mobium's tools. The program on this page plays the client's part by hand, to show exactly what goes over the wire; it needs Python 3.9 or later and nothing beyond its standard library.
2. Connect an agent
mobium mcp is an MCP server on stdio, so there is nothing to install beyond mobium itself. Register it with your agent — in Claude Code:
claude mcp add mobium -- mobium mcp
Other clients take the same command in their mcpServers configuration; the MCP guide has the JSON. From then on, ask the agent for what you want done on the device, and it calls the tools below itself.
3. The code
Save this as quickstart.py — it is examples/mcp/quickstart.py.
The calls an agent makes, made by a script instead: start the server, open with the handshake, then one tools/call per step. Every tool is an app_ name with JSON arguments, and every answer has text to read and, for most, structuredContent to use as data.
"""Mobium quick start over MCP: start a session, drive Settings, quit.
MOBIUM_PLATFORM=android python3 quickstart.py # or ios
An agent does all of this for you once `mobium mcp` is registered with it.
This is the same conversation by hand: the requests an MCP client sends, one
JSON-RPC message per line on the server's stdin, and the answers it reads
back. Standard library only.
"""
import json
import os
import subprocess
# Settings is on every emulator, simulator and phone, with nothing to install.
PLATFORMS = {
"android": {"app": "com.android.settings", "row": "Network & internet", "next": "text=Airplane mode"},
"ios": {"app": "com.apple.Preferences", "row": "General", "next": "label=About,role=button"},
}
platform = os.environ.get("MOBIUM_PLATFORM", "android")
p = PLATFORMS[platform]
# With one device running, no --device is needed; MOBIUM_DEVICE picks one of several.
cmd = ["mobium", "mcp"]
if os.environ.get("MOBIUM_DEVICE"):
cmd += ["--device", os.environ["MOBIUM_DEVICE"]]
server = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
next_id = 0
def request(method, params):
global next_id
next_id += 1
server.stdin.write(json.dumps({"jsonrpc": "2.0", "id": next_id, "method": method, "params": params}) + "\n")
server.stdin.flush()
while True:
reply = json.loads(server.stdout.readline())
if reply.get("id") == next_id:
return reply["result"]
def call(tool, arguments):
"""One tools/call. A tool that fails answers isError, with a code."""
print(f"→ {tool} {json.dumps(arguments, ensure_ascii=False)}")
result = request("tools/call", {"name": tool, "arguments": arguments})
if result.get("isError"):
code = result.get("structuredContent", {}).get("code")
raise SystemExit(f"{tool} failed [{code}]: {result['content'][0]['text']}")
return result
# The handshake every MCP client opens with.
request("initialize", {"protocolVersion": "2025-06-18", "capabilities": {},
"clientInfo": {"name": "quickstart", "version": "1"}})
server.stdin.write(json.dumps({"jsonrpc": "2.0", "method": "notifications/initialized"}) + "\n")
tools = request("tools/list", {})["tools"]
print(f"{len(tools)} tools, among them app_session, app_map, app_tap")
try:
# 1. Start the session: the driver is started on the device and Settings
# is launched. Every call after it uses this session.
started = call("app_session", {"action": "start", "platform": platform, "app": p["app"]})
print(" " + started["content"][0]["text"])
# 2. Map the screen: every element you can act on, each with a @ref. The
# text is for reading; structuredContent is the same list, as data.
elements = call("app_map", {})["structuredContent"]["elements"]
for e in elements[:5]:
print(f" {e['ref']} {e['label']} ({e['role']})")
# 3. Tap a row by its ref, then wait for the screen it opens. A row's label
# can carry its summary too ("Network & internet Mobile, Wi-Fi, ..."),
# so match its start.
row = next(e for e in elements if e["label"].startswith(p["row"]))
call("app_tap", {"target": row["ref"]})
waited = call("app_wait_for", {"target": p["next"]})
print(" " + waited["content"][0]["text"])
# 4. Take a screenshot. The image comes back in the answer; path also
# writes it to disk.
call("app_screenshot", {"path": f"quickstart-{platform}.png"})
print(f" saved quickstart-{platform}.png")
finally:
# 5. End the session. Closing stdin would end it too, as an agent's
# client does when it exits.
ended = call("app_session", {"action": "end"})
print(" " + ended["content"][0]["text"])
server.stdin.close()
server.wait()
Settings is on every Android and iOS device with nothing to install. The row and the screen after it are the only things that differ by platform.
4. Run it
Android
MOBIUM_PLATFORM=android python3 quickstart.py
What it printed on an Android 15 emulator:
waiting for the UiAutomator2 server to start...
71 tools, among them app_session, app_map, app_tap
→ app_session {"action": "start", "platform": "android", "app": "com.android.settings"}
session started on emulator-5554 (android, uiautomator2); com.android.settings was launched fresh and is in the foreground
→ app_map {}
@e1 settings_homepage_container (list)
@e2 Profile picture, double tap to open Google Account (button)
@e3 Search settings (button)
@e4 main_content_scrollable_container (list)
@e5 Network & internet Mobile, Wi‑Fi, hotspot (button)
→ app_tap {"target": "@e5"}
→ app_wait_for {"target": "text=Airplane mode"}
text=Airplane mode is visible after 1.301s — @e5 Airplane mode (switch, unchecked)
→ app_screenshot {"path": "quickstart-android.png"}
saved quickstart-android.png
→ app_session {"action": "end"}
session ended on emulator-5554; anything it changed for the session is put back; com.android.settings, which the session launched, was stopped
| After start | After the tap |
|---|---|
![]() | ![]() |
iOS
MOBIUM_PLATFORM=ios python3 quickstart.py
What it printed on an iOS 26.5 simulator:
waiting for WebDriverAgent to start...
71 tools, among them app_session, app_map, app_tap
→ app_session {"action": "start", "platform": "ios", "app": "com.apple.Preferences"}
session started on 457C7DC2-C706-45D9-8D68-1D26953E28B1 (ios, wda); com.apple.Preferences was launched fresh and is in the foreground
→ app_map {}
@e1 Apple Account, Sign in to access your iCloud data, the App Store, Apple services, and more. (button)
@e2 General (button)
@e3 Accessibility (button)
@e4 Action Button (button)
@e5 Apple Intelligence & Siri (button)
→ app_tap {"target": "@e2"}
→ app_wait_for {"target": "label=About,role=button"}
label=About,role=button is visible after 410ms — @e5 About (button)
→ app_screenshot {"path": "quickstart-ios.png"}
saved quickstart-ios.png
→ app_session {"action": "end"}
session ended on 457C7DC2-C706-45D9-8D68-1D26953E28B1; anything it changed for the session is put back; com.apple.Preferences, which the session launched, was stopped
| After start | After the tap |
|---|---|
![]() | ![]() |
The first start on a device is slow: it installs the UiAutomator2 server on Android, and on a real iPhone builds WebDriverAgent. Later starts take seconds.
Notes
- The server holds one session per device for as long as it runs. An agent's client ends it by closing the server's stdin when the agent exits, which also puts back anything the session changed.
- A tool that fails answers with
isErrorand a code instructuredContent, never a JSON-RPC error: the agent reads the explanation and decides what to do. The MCP guide shows an agent at work, the handshake, every kind of answer, and sharing a device with the command line. - With more than one device of the session's platform running — two Android devices, or two among the booted simulators and attached iPhones —
startrefuses to guess and lists them. One Android device and one iOS device are not ambiguous: the platform picks. Name one withMOBIUM_DEVICE=<serial or UDID>, which the example passes on as the device.
Next: the rest of the tool surface, and setting up phones and simulators. Changing the client itself? DEVELOPMENT.md is the contributor's guide.



