Metadata-Version: 2.4
Name: pymobile-mcp
Version: 0.3.1
Summary: Pure Python MCP server for mobile automation — Android via uiautomator2, iOS via pymobiledevice3
Author: ByteTrue
License-Expression: GPL-3.0-only
Keywords: mcp,mobile,automation,android,ios,uiautomator2,pymobiledevice3
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mcp==1.28.1
Requires-Dist: uiautomator2>=3.2.0
Requires-Dist: adbutils>=2.8.10
Requires-Dist: pymobiledevice3>=9.0.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: pyyaml>=6.0.0
Requires-Dist: click>=8.0.0
Requires-Dist: structlog>=23.0.0
Requires-Dist: pillow>=10.0.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
Requires-Dist: black>=23.0.0; extra == "dev"
Requires-Dist: isort>=5.12.0; extra == "dev"
Requires-Dist: mypy>=1.0.0; extra == "dev"
Dynamic: license-file

# pymobile-mcp

Pure Python [MCP](https://modelcontextprotocol.io/) server for mobile automation.

- **Android**: `uiautomator2` + `adbutils`
- **iOS**: pure `pymobiledevice3` userspace tunnel + WebDriverAgent (no go-ios, no root)

Public contract: fixed upstream `mobile-mcp@c5d7d27` — **23 tools by default**, **26 with `MOBILEFLEET_ENABLE=1`**.
`mobile_get_page_source` is not public; fleet runtime uses the documented approved exception when no Python fleet provider is installed.

[![CI](https://github.com/ByteTrue/pymobile-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/ByteTrue/pymobile-mcp/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/ByteTrue/pymobile-mcp?display_name=tag)](https://github.com/ByteTrue/pymobile-mcp/releases/latest)
[![PyPI](https://img.shields.io/pypi/v/pymobile-mcp.svg)](https://pypi.org/project/pymobile-mcp/)
[![Python](https://img.shields.io/badge/python-%3E%3D3.10-blue)](https://www.python.org/downloads/)
[![License: GPL-3.0](https://img.shields.io/badge/license-GPL--3.0-blue.svg)](LICENSE)

Current release: **0.3.1** · [Changelog](CHANGELOG.md) · [Live regression checklist](docs/regression-checklist.md)

## Features

- One stdio MCP server for **Android + iOS**
- Upstream-compatible natural-language/image results and exact Actionable vs `isError=true` failures
- Live smoke scripts that exit `blocked` (code 2) when devices are missing — never fake pass
- Destructive actions (install/uninstall) gated by explicit env flags

## Requirements

| Platform | Need |
|---|---|
| Host | Python **≥ 3.10**, `pip` |
| Android | `adb` in `PATH`, authorized device or emulator |
| iOS | macOS recommended, paired iPhone/iPad, **Developer Mode**, mounted DDI, installed WDA runner (default `com.byte.WebDriverAgentRunner.xctrunner`) |

> [!NOTE]
> iOS real-device screen recording returns the approved upstream-compatible Actionable exception on current pure-userspace RSD (iOS 26.5.2). iOS Simulator uses `simctl recordVideo` + WDA and is not covered by that exception.

## Install

### From a git checkout (development)

```bash
git clone https://github.com/ByteTrue/pymobile-mcp.git
cd pymobile-mcp
python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e .
pymobile-mcp --help
```

### Dev install (tests)

```bash
pip install -e ".[dev]"
python -m pytest -q
```

### From PyPI

```bash
pip install pymobile-mcp==0.3.1
```

### From GitHub (no local clone of tooling)

```bash
pip install "git+https://github.com/ByteTrue/pymobile-mcp.git@v0.3.1"
```

> [!TIP]
> Prefer a venv. System Python + `pip install` often fights with Homebrew/PEP 668.

## Quick start

1. Connect an Android emulator/device (`adb devices`) and/or a paired iPhone.
2. For iOS, ensure your WDA runner is installed (Xcode once); pymobile-mcp launches it over userspace tunnel.
3. Run the server:

```bash
pymobile-mcp run
# equivalent:
python -m pymobile_mcp.cli run
```

4. Point an MCP client at the process (stdio).

### MCP client config

**Installed entrypoint** (`pymobile-mcp` on `PATH`):

```json
{
  "mcpServers": {
    "pymobile-mcp": {
      "command": "pymobile-mcp",
      "args": ["run"]
    }
  }
}
```

**Checkout / venv** (absolute paths are more reliable for GUI clients):

```json
{
  "mcpServers": {
    "pymobile-mcp": {
      "command": "/ABS/PATH/pymobile-mcp/.venv/bin/python",
      "args": ["-m", "pymobile_mcp.cli", "run"],
      "cwd": "/ABS/PATH/pymobile-mcp",
      "env": {
        "NO_PROXY": "*",
        "PYTHONPATH": "src"
      }
    }
  }
}
```

> [!IMPORTANT]
> For iOS USB, unset or bypass HTTP proxies (`NO_PROXY=*` or clear `http_proxy`/`https_proxy`). Proxy env often breaks usbmux / userspace tunnel.

## First tools to try

After the client connects:

1. `mobile_list_available_devices` — confirm Android/iOS ids
2. `mobile_get_screen_size` / `mobile_take_screenshot`
3. `mobile_list_elements_on_screen` — UI tree (no raw page source tool)
4. `mobile_list_apps` → `mobile_launch_app` with a known package/bundle id

## Capability matrix (23 default tools; 26 with fleet env)

| Status | Meaning |
|---|---|
| **supported** | Implemented and covered by automated contract tests; this label is not a claim of current-device live attestation |
| **approved exception** | Explicit, reviewed black-box difference with stable upstream-compatible text |

Current live evidence passes on Android physical, Android emulator, iOS Simulator, and iOS real device. Android recording and iOS Simulator recording both passed live; iOS real-device recording remains the approved exception.

| Tool | Android | iOS |
|---|---|---|
| mobile_list_available_devices | supported | supported |
| mobile_list_apps | supported | supported |
| mobile_launch_app | supported | supported |
| mobile_terminate_app | supported | supported |
| mobile_install_app | supported (destructive) | supported (destructive, gated) |
| mobile_uninstall_app | supported (destructive) | supported (destructive, gated) |
| mobile_get_screen_size | supported | supported |
| mobile_click_on_screen_at_coordinates | supported | supported |
| mobile_double_tap_on_screen | supported | supported |
| mobile_long_press_on_screen_at_coordinates | supported | supported |
| mobile_list_elements_on_screen | supported | supported (WDA source internal) |
| mobile_press_button | supported | supported (`HOME`/`VOLUME_*`; `BACK` unsupported) |
| mobile_open_url | supported (http/https default) | supported (device must be unlocked) |
| mobile_swipe_on_screen | supported | supported |
| mobile_type_keys | supported | supported |
| mobile_save_screenshot | supported | supported |
| mobile_take_screenshot | supported | supported |
| mobile_set_orientation | supported | supported |
| mobile_get_orientation | supported | supported |
| mobile_start_screen_recording | supported; live passed | simulator supported/live passed; real device **approved exception** |
| mobile_stop_screen_recording | supported; live passed | simulator supported/live passed; real device **approved exception** |
| mobile_list_crashes | supported (dropbox) | supported (crash reports) |
| mobile_get_crash | supported (dropbox) | supported (crash reports) |

## Environment variables

| Env | Purpose |
|---|---|
| `MOBILEMCP_ALLOW_UNSAFE_URLS=1` | allow non-http(s) schemes for `mobile_open_url` |
| `MOBILEFLEET_ENABLE=1` | expose the upstream fleet tool set (26 tools); runtime returns approved exception without a Python fleet provider |
| `PYMOBILE_MCP_ANDROID_DROPBOX_ALL=1` | include non-crash dropbox tags (strictmode/boot/…) |
| `PYMOBILE_MCP_ANDROID_DEVICE` | pin Android serial for smokes |
| `PYMOBILE_MCP_ANDROID_ACTIONS=1` | enable tap/type interactions in Android UI smoke |
| `PYMOBILE_MCP_ANDROID_TAP=x,y` | override Android tap point |
| `PYMOBILE_MCP_ANDROID_APK` + `PYMOBILE_MCP_ANDROID_PACKAGE` + `PYMOBILE_MCP_ANDROID_DESTRUCTIVE=1` | allow install/uninstall smoke |
| `PYMOBILE_MCP_IOS_DEVICE` | pin iOS UDID |
| `PYMOBILE_MCP_IOS_ACTIONS=1` | enable interaction steps in iOS core smoke |
| `PYMOBILE_MCP_WDA_XCTRUNNER` | WDA runner bundle id (default `com.byte.WebDriverAgentRunner.xctrunner`) |
| `PYMOBILE_MCP_WDA_PORT` | WDA port used by the in-process userspace RSD service client (default `8100`; not a remote WDA URL) |
| `PYMOBILE_MCP_IOS_IPA` + `PYMOBILE_MCP_IOS_DESTRUCTIVE=1` | allow iOS install/uninstall smoke |
| `NO_PROXY=*` | recommended for iOS USB / usbmux |

## Live smoke

Scripts print JSON with `status: passed|failed|blocked`.  
**Exit `2` + `blocked` = missing device/env, not a product pass.**

```bash
export NO_PROXY='*'
PATH=.venv/bin:$PATH

# Android
python tests/android_live_smoke.py
PYMOBILE_MCP_ANDROID_ACTIONS=1 python tests/android_live_smoke.py
python tests/android_app_system_live_smoke.py
python tests/android_recording_crash_live_smoke.py

# iOS (unlock phone for open_url)
python tests/ios_pmd3_wda_live_smoke.py
PYMOBILE_MCP_IOS_ACTIONS=1 python tests/ios_pmd3_wda_live_smoke.py
python tests/ios_system_helpers_live_smoke.py
python tests/ios_app_lifecycle_live_smoke.py
python tests/crash_tools_live_smoke.py
python tests/ios_app_recording_crash_live_smoke.py   # real recording uses approved exception
```

Full dual-device gate: [docs/regression-checklist.md](docs/regression-checklist.md).

## Architecture (short)

```
MCP client  --stdio-->  pymobile-mcp server
                           │
                           ├─ tools/registry + specs   (23 default / 26 fleet contract)
                           ├─ tools/* handlers
                           └─ drivers/
                                ├─ android.py       (adbutils / uiautomator2)
                                ├─ ios.py           (UserspaceRsdTunnel + WDA)
                                └─ ios_simulator.py (simctl + WDA)
```

## Development

```bash
pip install -e ".[dev]"
python -m pytest -q
```

CI runs unit tests on Python 3.11 and 3.12 ([workflow](.github/workflows/ci.yml)). Device live smokes stay local.

## Known limits

- Android crashes come from `dumpsys dropbox --print`, filtered to crash/ANR/tombstone-like tags by default
- iOS real-device recording remains an approved exception; Simulator recording requires a booted CoreSimulator + WDA
- Recording state is process-local (`ActiveRecording`); no cross-process resume
- Custom URL schemes need `MOBILEMCP_ALLOW_UNSAFE_URLS=1`
- Screenshot/recording host paths must resolve under cwd or system temp
- No go-ios/mobilecli runtime fallback by design

## Troubleshooting

| Symptom | What to check |
|---|---|
| iOS device list empty | Cable/trust, `pymobiledevice3 usbmux list`, proxies (`NO_PROXY=*`) |
| iOS driver tunnel errors | Developer Mode, `pymobiledevice3 mounter auto-mount`, only one userspace tunnel process |
| `open_url` fails on iPhone | Unlock the device (passcode lock blocks Safari/WDA URL open) |
| Android no devices | `adb devices` authorized; kill stale `adb` if offline |
| Install/uninstall smoke skipped | Set package/apk/ipa **and** `*_DESTRUCTIVE=1` |
| CI green but live red | Expected — CI is unit-only; use [regression checklist](docs/regression-checklist.md) |
