minitap-mcp 0.5.3__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/PKG-INFO +59 -5
  2. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/PYPI_README.md +55 -3
  3. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/core/agents/compare_screenshots/agent.py +12 -2
  4. minitap_mcp-0.7.0/minitap/mcp/core/cloud_apk.py +109 -0
  5. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/core/config.py +9 -4
  6. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/core/decorators.py +19 -1
  7. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/core/device.py +11 -4
  8. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/core/sdk_agent.py +8 -0
  9. minitap_mcp-0.7.0/minitap/mcp/core/storage.py +274 -0
  10. minitap_mcp-0.7.0/minitap/mcp/main.py +328 -0
  11. minitap_mcp-0.7.0/minitap/mcp/server/cloud_mobile.py +492 -0
  12. minitap_mcp-0.7.0/minitap/mcp/server/middleware.py +21 -0
  13. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/server/poller.py +6 -6
  14. minitap_mcp-0.7.0/minitap/mcp/server/remote_proxy.py +96 -0
  15. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/tools/execute_mobile_command.py +47 -2
  16. minitap_mcp-0.7.0/minitap/mcp/tools/read_swift_logs.py +297 -0
  17. minitap_mcp-0.7.0/minitap/mcp/tools/take_screenshot.py +53 -0
  18. minitap_mcp-0.7.0/minitap/mcp/tools/upload_screenshot.py +80 -0
  19. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/pyproject.toml +4 -2
  20. minitap_mcp-0.5.3/minitap/mcp/main.py +0 -150
  21. minitap_mcp-0.5.3/minitap/mcp/server/middleware.py +0 -23
  22. minitap_mcp-0.5.3/minitap/mcp/tools/analyze_screen.py +0 -58
  23. minitap_mcp-0.5.3/minitap/mcp/tools/compare_screenshot_with_figma.py +0 -132
  24. minitap_mcp-0.5.3/minitap/mcp/tools/save_figma_assets.py +0 -276
  25. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/__init__.py +0 -0
  26. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/core/agents/compare_screenshots/eval/prompts/prompt_1.md +0 -0
  27. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/core/agents/compare_screenshots/eval/scenario_1_add_cartoon_img_and_move_button/actual.png +0 -0
  28. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/core/agents/compare_screenshots/eval/scenario_1_add_cartoon_img_and_move_button/figma.png +0 -0
  29. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/core/agents/compare_screenshots/eval/scenario_1_add_cartoon_img_and_move_button/human_feedback.txt +0 -0
  30. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/core/agents/compare_screenshots/eval/scenario_1_add_cartoon_img_and_move_button/prompt_1/model_params.json +0 -0
  31. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/core/agents/compare_screenshots/eval/scenario_1_add_cartoon_img_and_move_button/prompt_1/output.md +0 -0
  32. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/core/agents/compare_screenshots/prompt.md +0 -0
  33. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/core/llm.py +0 -0
  34. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/core/logging_config.py +0 -0
  35. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/core/models.py +0 -0
  36. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/core/utils/figma.py +0 -0
  37. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/core/utils/images.py +0 -0
  38. {minitap_mcp-0.5.3 → minitap_mcp-0.7.0}/minitap/mcp/tools/screen_analyzer.md +0 -0
@@ -1,17 +1,19 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: minitap-mcp
3
- Version: 0.5.3
3
+ Version: 0.7.0
4
4
  Summary: Model Context Protocol server for controlling Android & iOS devices with natural language
5
5
  Author: Pierre-Louis Favreau, Jean-Pierre Lo, Clément Guiguet
6
6
  Requires-Dist: fastmcp>=2.12.4
7
7
  Requires-Dist: python-dotenv>=1.1.1
8
8
  Requires-Dist: pydantic>=2.12.0
9
9
  Requires-Dist: pydantic-settings>=2.10.1
10
- Requires-Dist: minitap-mobile-use>=2.9.3
10
+ Requires-Dist: minitap-mobile-use>=3.4.0
11
11
  Requires-Dist: jinja2>=3.1.6
12
12
  Requires-Dist: langchain-core>=0.3.75
13
13
  Requires-Dist: pillow>=11.1.0
14
14
  Requires-Dist: structlog>=24.4.0
15
+ Requires-Dist: aiohttp>=3.9.0
16
+ Requires-Dist: httpx>=0.28.1
15
17
  Requires-Dist: ruff==0.5.3 ; extra == 'dev'
16
18
  Requires-Dist: pytest==8.4.1 ; extra == 'dev'
17
19
  Requires-Dist: pytest-cov==5.0.0 ; extra == 'dev'
@@ -40,11 +42,19 @@ Before running the MCP server, ensure you have the required mobile automation to
40
42
  - **For Android devices:**
41
43
 
42
44
  - [ADB (Android Debug Bridge)](https://developer.android.com/tools/adb) - For device communication
43
- - [Maestro](https://maestro.mobile.dev/) - For mobile automation
44
45
 
45
46
  - **For iOS devices (macOS only):**
46
47
  - Xcode Command Line Tools with `xcrun`
47
- - [Maestro](https://maestro.mobile.dev/) - For mobile automation
48
+ - **[fb-idb](https://fbidb.io/docs/installation/)**: Facebook's iOS Development Bridge for device automation.
49
+
50
+ ```bash
51
+ # Install via Homebrew (macOS)
52
+ brew tap facebook/fb
53
+ brew install idb-companion
54
+ ```
55
+
56
+ > [!NOTE]
57
+ > `idb_companion` is required to communicate with iOS simulators. Make sure it's in your PATH after installation.
48
58
 
49
59
  For detailed setup instructions, see the [mobile-use repository](https://github.com/minitap-ai/mobile-use).
50
60
 
@@ -244,6 +254,50 @@ The tool will:
244
254
  3. Compare both screenshots using vision AI
245
255
  4. Return a detailed analysis highlighting differences
246
256
 
257
+ ## Cloud Mobile Mode
258
+
259
+ Run the MCP server with cloud-hosted mobile devices instead of requiring a local device. This enables:
260
+
261
+ - **Zero local setup**: No ADB or physical device required
262
+ - **Remote development**: Control cloud mobiles from anywhere
263
+ - **Scalable automation**: Access multiple cloud devices
264
+
265
+ ### Setting Up Cloud Mobile Mode
266
+
267
+ 1. **Create a Cloud Mobile** on [Minitap Platform](https://platform.minitap.ai/cloud-mobiles):
268
+ - Click **Create New Device**
269
+ - Choose platform (currently Android v11 / API level 30)
270
+ - Set a **Reference Name** (e.g., `my-dev-device`)
271
+
272
+ 2. **Configure the environment variable**:
273
+
274
+ ```bash
275
+ # Using reference name (recommended)
276
+ export CLOUD_MOBILE_NAME="my-dev-device"
277
+ ```
278
+
279
+ 3. **Start the server** (no local device needed):
280
+
281
+ ```bash
282
+ minitap-mcp --server --api-key your_minitap_api_key
283
+ ```
284
+
285
+ The server will:
286
+ - Connect to your cloud mobile on startup
287
+ - Maintain a keep-alive connection while running
288
+ - Automatically disconnect when the server stops
289
+
290
+ > ⚠️ **Important**: Cloud mobiles are billed while connected. The MCP server automatically stops the connection when you close your IDE or stop the server. Make sure to properly shut down the server to avoid unexpected charges.
291
+
292
+ ### Cloud vs Local Mode
293
+
294
+ | Feature | Local Mode | Cloud Mode |
295
+ |---------|------------|------------|
296
+ | Device requirement | Physical/emulator | None |
297
+ | Setup complexity | ADB setup required | Low (env var only) |
298
+ | `CLOUD_MOBILE_NAME` | Not set | Set to device name/UUID |
299
+ | Billing | None | Per-minute usage |
300
+
247
301
  ## Advanced Configuration
248
302
 
249
303
  ### Custom ADB Server
@@ -259,7 +313,7 @@ export ADB_SERVER_SOCKET="tcp:192.168.1.100:5037"
259
313
  Customize the vision model used for screen analysis:
260
314
 
261
315
  ```bash
262
- export VISION_MODEL="qwen/qwen-2.5-vl-7b-instruct"
316
+ export VISION_MODEL="google/gemini-3-flash-preview"
263
317
  ```
264
318
 
265
319
  ## Device Setup
@@ -17,11 +17,19 @@ Before running the MCP server, ensure you have the required mobile automation to
17
17
  - **For Android devices:**
18
18
 
19
19
  - [ADB (Android Debug Bridge)](https://developer.android.com/tools/adb) - For device communication
20
- - [Maestro](https://maestro.mobile.dev/) - For mobile automation
21
20
 
22
21
  - **For iOS devices (macOS only):**
23
22
  - Xcode Command Line Tools with `xcrun`
24
- - [Maestro](https://maestro.mobile.dev/) - For mobile automation
23
+ - **[fb-idb](https://fbidb.io/docs/installation/)**: Facebook's iOS Development Bridge for device automation.
24
+
25
+ ```bash
26
+ # Install via Homebrew (macOS)
27
+ brew tap facebook/fb
28
+ brew install idb-companion
29
+ ```
30
+
31
+ > [!NOTE]
32
+ > `idb_companion` is required to communicate with iOS simulators. Make sure it's in your PATH after installation.
25
33
 
26
34
  For detailed setup instructions, see the [mobile-use repository](https://github.com/minitap-ai/mobile-use).
27
35
 
@@ -221,6 +229,50 @@ The tool will:
221
229
  3. Compare both screenshots using vision AI
222
230
  4. Return a detailed analysis highlighting differences
223
231
 
232
+ ## Cloud Mobile Mode
233
+
234
+ Run the MCP server with cloud-hosted mobile devices instead of requiring a local device. This enables:
235
+
236
+ - **Zero local setup**: No ADB or physical device required
237
+ - **Remote development**: Control cloud mobiles from anywhere
238
+ - **Scalable automation**: Access multiple cloud devices
239
+
240
+ ### Setting Up Cloud Mobile Mode
241
+
242
+ 1. **Create a Cloud Mobile** on [Minitap Platform](https://platform.minitap.ai/cloud-mobiles):
243
+ - Click **Create New Device**
244
+ - Choose platform (currently Android v11 / API level 30)
245
+ - Set a **Reference Name** (e.g., `my-dev-device`)
246
+
247
+ 2. **Configure the environment variable**:
248
+
249
+ ```bash
250
+ # Using reference name (recommended)
251
+ export CLOUD_MOBILE_NAME="my-dev-device"
252
+ ```
253
+
254
+ 3. **Start the server** (no local device needed):
255
+
256
+ ```bash
257
+ minitap-mcp --server --api-key your_minitap_api_key
258
+ ```
259
+
260
+ The server will:
261
+ - Connect to your cloud mobile on startup
262
+ - Maintain a keep-alive connection while running
263
+ - Automatically disconnect when the server stops
264
+
265
+ > ⚠️ **Important**: Cloud mobiles are billed while connected. The MCP server automatically stops the connection when you close your IDE or stop the server. Make sure to properly shut down the server to avoid unexpected charges.
266
+
267
+ ### Cloud vs Local Mode
268
+
269
+ | Feature | Local Mode | Cloud Mode |
270
+ |---------|------------|------------|
271
+ | Device requirement | Physical/emulator | None |
272
+ | Setup complexity | ADB setup required | Low (env var only) |
273
+ | `CLOUD_MOBILE_NAME` | Not set | Set to device name/UUID |
274
+ | Billing | None | Per-minute usage |
275
+
224
276
  ## Advanced Configuration
225
277
 
226
278
  ### Custom ADB Server
@@ -236,7 +288,7 @@ export ADB_SERVER_SOCKET="tcp:192.168.1.100:5037"
236
288
  Customize the vision model used for screen analysis:
237
289
 
238
290
  ```bash
239
- export VISION_MODEL="qwen/qwen-2.5-vl-7b-instruct"
291
+ export VISION_MODEL="google/gemini-3-flash-preview"
240
292
  ```
241
293
 
242
294
  ## Device Setup
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import base64
2
3
  from pathlib import Path
3
4
  from uuid import uuid4
4
5
 
@@ -9,6 +10,7 @@ from pydantic import BaseModel
9
10
  from minitap.mcp.core.device import capture_screenshot, find_mobile_device
10
11
  from minitap.mcp.core.llm import get_minitap_llm
11
12
  from minitap.mcp.core.utils.images import get_screenshot_message_for_llm
13
+ from minitap.mcp.server.cloud_mobile import get_cloud_mobile_id, get_cloud_screenshot
12
14
 
13
15
 
14
16
  class CompareScreenshotsOutput(BaseModel):
@@ -23,6 +25,8 @@ async def compare_screenshots(
23
25
  """
24
26
  Compare screenshots and return the comparison text along with both screenshots.
25
27
 
28
+ Supports both local devices (Android/iOS) and cloud devices.
29
+
26
30
  Returns:
27
31
  CompareScreenshotsOutput
28
32
  """
@@ -30,8 +34,14 @@ async def compare_screenshots(
30
34
  Path(__file__).parent.joinpath("prompt.md").read_text(encoding="utf-8")
31
35
  ).render()
32
36
 
33
- device = find_mobile_device()
34
- current_screenshot = capture_screenshot(device)
37
+ cloud_mobile_id = get_cloud_mobile_id()
38
+
39
+ if cloud_mobile_id:
40
+ screenshot_bytes = await get_cloud_screenshot(cloud_mobile_id)
41
+ current_screenshot = base64.b64encode(screenshot_bytes).decode("utf-8")
42
+ else:
43
+ device = find_mobile_device()
44
+ current_screenshot = capture_screenshot(device)
35
45
 
36
46
  messages: list[BaseMessage] = [
37
47
  SystemMessage(content=system_message),
@@ -0,0 +1,109 @@
1
+ """Cloud APK deployment utilities for uploading and installing APKs on cloud mobiles."""
2
+
3
+ import uuid
4
+ from pathlib import Path
5
+
6
+ import httpx
7
+
8
+ from minitap.mcp.core.config import settings
9
+
10
+
11
+ async def upload_apk_to_cloud_mobile(apk_path: str) -> str:
12
+ """
13
+ Upload an APK file via Platform storage API to user storage bucket.
14
+
15
+ Args:
16
+ apk_path: Path to the APK file
17
+
18
+ Returns:
19
+ Filename to use with install-apk endpoint
20
+
21
+ Raises:
22
+ FileNotFoundError: If APK file doesn't exist
23
+ httpx.HTTPError: If upload fails
24
+ ValueError: If MINITAP_API_KEY or MINITAP_API_BASE_URL is not configured
25
+ """
26
+ if not settings.MINITAP_API_KEY:
27
+ raise ValueError("MINITAP_API_KEY is not configured")
28
+ if not settings.MINITAP_API_BASE_URL:
29
+ raise ValueError("MINITAP_API_BASE_URL is not configured")
30
+
31
+ apk_file = Path(apk_path)
32
+ if not apk_file.exists():
33
+ raise FileNotFoundError(f"APK file not found: {apk_path}")
34
+
35
+ filename = f"app_{uuid.uuid4().hex[:6]}.apk"
36
+ api_key = settings.MINITAP_API_KEY.get_secret_value()
37
+ api_base_url = settings.MINITAP_API_BASE_URL.rstrip("/")
38
+
39
+ async with httpx.AsyncClient(timeout=300.0) as client:
40
+ # Step 1: Get signed upload URL from storage API
41
+ response = await client.get(
42
+ f"{api_base_url}/storage/signed-upload",
43
+ headers={"Authorization": f"Bearer {api_key}"},
44
+ params={"filenames": filename},
45
+ )
46
+ response.raise_for_status()
47
+ upload_data = response.json()
48
+
49
+ # Extract the signed URL for our file
50
+ signed_urls = upload_data.get("signed_urls", {})
51
+ if filename not in signed_urls:
52
+ raise ValueError(f"No signed URL returned for {filename}")
53
+
54
+ signed_url = signed_urls[filename]
55
+
56
+ # Step 2: Upload APK to signed URL
57
+ with open(apk_file, "rb") as f:
58
+ upload_response = await client.put(
59
+ signed_url,
60
+ content=f.read(),
61
+ headers={"Content-Type": "application/vnd.android.package-archive"},
62
+ )
63
+ upload_response.raise_for_status()
64
+
65
+ # Step 3: Return filename for install-apk call
66
+ return filename
67
+
68
+
69
+ async def install_apk_on_cloud_mobile(filename: str) -> None:
70
+ """
71
+ Install an APK on a cloud mobile device via mobile-manager API.
72
+
73
+ Args:
74
+ filename: Filename returned from upload_apk_to_cloud_mobile
75
+
76
+ Raises:
77
+ httpx.HTTPError: If installation fails
78
+ ValueError: If required config settings are not configured
79
+ """
80
+ if not settings.MINITAP_API_KEY:
81
+ raise ValueError("MINITAP_API_KEY is not configured")
82
+ if not settings.MINITAP_DAAS_API:
83
+ raise ValueError("MINITAP_DAAS_API is not configured")
84
+ if not settings.CLOUD_MOBILE_NAME:
85
+ raise ValueError("CLOUD_MOBILE_NAME is not configured")
86
+
87
+ api_key = settings.MINITAP_API_KEY.get_secret_value()
88
+ base_url = settings.MINITAP_DAAS_API
89
+ cloud_mobile_name = settings.CLOUD_MOBILE_NAME
90
+
91
+ async with httpx.AsyncClient(timeout=120.0) as client:
92
+ cloud_mobile_response = await client.get(
93
+ f"{base_url}/virtual-mobiles/{cloud_mobile_name}",
94
+ headers={"Authorization": f"Bearer {api_key}"},
95
+ )
96
+ cloud_mobile_response.raise_for_status()
97
+ response_data = cloud_mobile_response.json()
98
+ cloud_mobile_uuid = response_data.get("id")
99
+ if not cloud_mobile_uuid:
100
+ raise ValueError(f"Cloud mobile '{cloud_mobile_name}' response missing 'id' field")
101
+ response = await client.post(
102
+ f"{base_url}/virtual-mobiles/{cloud_mobile_uuid}/install-apk",
103
+ headers={
104
+ "Authorization": f"Bearer {api_key}",
105
+ "Content-Type": "application/json",
106
+ },
107
+ json={"filename": filename},
108
+ )
109
+ response.raise_for_status()
@@ -16,16 +16,21 @@ class MCPSettings(BaseSettings):
16
16
  # Minitap API configuration
17
17
  MINITAP_API_KEY: SecretStr | None = Field(default=None)
18
18
  MINITAP_API_BASE_URL: str = Field(default="https://platform.minitap.ai/api/v1")
19
+ MINITAP_DAAS_API: str = Field(default="https://platform.minitap.ai/api/daas")
20
+ MINITAP_API_MCP_BASE_URL: str | None = Field(default="https://platform.minitap.ai/mcp")
19
21
  OPEN_ROUTER_API_KEY: SecretStr | None = Field(default=None)
20
22
 
21
- VISION_MODEL: str = Field(default="qwen/qwen-2.5-vl-7b-instruct")
22
-
23
- # Figma MCP server configuration
24
- FIGMA_MCP_SERVER_URL: str = Field(default="http://127.0.0.1:3845/mcp")
23
+ VISION_MODEL: str = Field(default="google/gemini-3-flash-preview")
25
24
 
26
25
  # MCP server configuration (optional, for remote access)
27
26
  MCP_SERVER_HOST: str = Field(default="0.0.0.0")
28
27
  MCP_SERVER_PORT: int = Field(default=8000)
29
28
 
29
+ # Cloud Mobile configuration
30
+ # When set, the MCP server runs in cloud mode connecting to a Minitap cloud mobile
31
+ # instead of requiring a local device. Value can be a device name.
32
+ # Create cloud mobiles at https://platform.minitap.ai/cloud-mobiles
33
+ CLOUD_MOBILE_NAME: str | None = Field(default=None)
34
+
30
35
 
31
36
  settings = MCPSettings() # type: ignore
@@ -6,7 +6,7 @@ from collections.abc import Callable
6
6
  from functools import wraps
7
7
  from typing import Any, TypeVar
8
8
 
9
- from minitap.mcp.core.device import DeviceNotFoundError
9
+ from minitap.mcp.core.device import DeviceNotFoundError, DeviceNotReadyError
10
10
  from minitap.mcp.core.logging_config import get_logger
11
11
 
12
12
  F = TypeVar("F", bound=Callable[..., Any])
@@ -42,6 +42,15 @@ def handle_tool_errors[T: Callable[..., Any]](func: T) -> T:
42
42
  error_type=type(e).__name__,
43
43
  )
44
44
  return f"Error: {str(e)}"
45
+ except DeviceNotReadyError as e:
46
+ logger.error(
47
+ "device_not_ready_error",
48
+ tool_name=func.__name__,
49
+ error=str(e),
50
+ error_type=type(e).__name__,
51
+ device_state=e.state,
52
+ )
53
+ return f"Error: {str(e)}"
45
54
  except Exception as e:
46
55
  logger.error(
47
56
  "tool_error",
@@ -72,6 +81,15 @@ def handle_tool_errors[T: Callable[..., Any]](func: T) -> T:
72
81
  error_type=type(e).__name__,
73
82
  )
74
83
  return f"Error: {str(e)}"
84
+ except DeviceNotReadyError as e:
85
+ logger.error(
86
+ "device_not_ready_error",
87
+ tool_name=func.__name__,
88
+ error=str(e),
89
+ error_type=type(e).__name__,
90
+ device_state=e.state,
91
+ )
92
+ return f"Error: {str(e)}"
75
93
  except Exception as e:
76
94
  logger.error(
77
95
  "tool_error",
@@ -11,7 +11,6 @@ from typing import Literal
11
11
  from adbutils import AdbClient, AdbDevice
12
12
  from pydantic import BaseModel, ConfigDict
13
13
 
14
-
15
14
  DevicePlatform = Literal["android", "ios"]
16
15
 
17
16
 
@@ -40,6 +39,14 @@ class DeviceNotFoundError(Exception):
40
39
  pass
41
40
 
42
41
 
42
+ class DeviceNotReadyError(Exception):
43
+ """Raised when a device exists but is not ready (e.g., still starting)."""
44
+
45
+ def __init__(self, message: str, state: str | None = None):
46
+ super().__init__(message)
47
+ self.state = state
48
+
49
+
43
50
  def get_adb_client() -> AdbClient:
44
51
  """Get an ADB client instance."""
45
52
  custom_adb_socket = os.getenv("ADB_SERVER_SOCKET")
@@ -80,9 +87,9 @@ def list_available_devices() -> list[DeviceInfo]:
80
87
  # ADB not available or error listing devices
81
88
  pass
82
89
 
83
- # List iOS devices
90
+ # List iOS devices (only booted simulators to match SDK behavior)
84
91
  try:
85
- cmd = ["xcrun", "simctl", "list", "devices", "-j"]
92
+ cmd = ["xcrun", "simctl", "list", "devices", "booted", "-j"]
86
93
  result = subprocess.run(cmd, capture_output=True, text=True, check=True)
87
94
  data = json.loads(result.stdout)
88
95
 
@@ -95,7 +102,7 @@ def list_available_devices() -> list[DeviceInfo]:
95
102
  name = device.get("name")
96
103
  state = device.get("state")
97
104
 
98
- if udid:
105
+ if udid and state == "Booted":
99
106
  devices.append(
100
107
  DeviceInfo(
101
108
  device_id=udid,
@@ -3,6 +3,8 @@ import os
3
3
  from minitap.mobile_use.sdk import Agent
4
4
  from minitap.mobile_use.sdk.builders import Builders
5
5
 
6
+ from minitap.mcp.core.config import settings
7
+
6
8
  # Lazy-initialized singleton agent
7
9
  _agent: Agent | None = None
8
10
 
@@ -23,5 +25,11 @@ def get_mobile_use_agent() -> Agent:
23
25
  raise ValueError(f"Invalid ADB server socket: {custom_adb_socket}")
24
26
  _, host, port = parts
25
27
  config = config.with_adb_server(host=host, port=int(port))
28
+
29
+ # Add cloud mobile configuration if set
30
+ if settings.CLOUD_MOBILE_NAME:
31
+ config = config.for_cloud_mobile(cloud_mobile_id_or_ref=settings.CLOUD_MOBILE_NAME)
32
+
26
33
  _agent = Agent(config=config.build())
34
+
27
35
  return _agent