Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 35 additions & 7 deletions server.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,37 @@
f"Config file {CONFIG_FILE} not found, using auto-selection for device")

# Initialize MCP and device manager
# AdbDeviceManager will handle auto-selection if device_name is None
# AdbDeviceManager will handle auto-selection if device_name is None.
# Device acquisition is deferred — the MCP server starts even when no Android
# device is currently attached, and each tool re-attempts device acquisition
# on first call. This prevents a transient adb-disconnect (e.g., cable unplug,
# device sleep, USB hub power cycle) from crashing the server at startup.
mcp = FastMCP("android")
deviceManager = AdbDeviceManager(device_name)
deviceManager: AdbDeviceManager | None = None

try:
deviceManager = AdbDeviceManager(device_name, exit_on_error=False)
except RuntimeError as e:
print(
f"Android device unavailable at startup: {e} "
f"MCP server will start in deferred-init mode; tools will re-attempt "
f"device acquisition on first call.",
file=sys.stderr,
)


def _get_device_manager() -> AdbDeviceManager:
"""Return the live device manager, re-attempting acquisition if needed."""
global deviceManager
if deviceManager is None:
try:
deviceManager = AdbDeviceManager(device_name, exit_on_error=False)
except RuntimeError as e:
raise RuntimeError(
f"Android device unavailable: {e} "
f"Connect a device via `adb` and retry the tool call."
)
return deviceManager


@mcp.tool()
Expand All @@ -55,7 +83,7 @@ def get_packages() -> str:
Returns:
str: A list of all installed packages on the device as a string
"""
result = deviceManager.get_packages()
result = _get_device_manager().get_packages()
return result


Expand All @@ -67,7 +95,7 @@ def execute_adb_shell_command(command: str) -> str:
Returns:
str: The output of the ADB command
"""
result = deviceManager.execute_adb_shell_command(command)
result = _get_device_manager().execute_adb_shell_command(command)
return result


Expand All @@ -81,7 +109,7 @@ def get_uilayout() -> str:
Returns:
str: A formatted list of clickable elements with their properties
"""
result = deviceManager.get_uilayout()
result = _get_device_manager().get_uilayout()
return result


Expand All @@ -91,7 +119,7 @@ def get_screenshot() -> Image:
Returns:
Image: the screenshot
"""
deviceManager.take_screenshot()
_get_device_manager().take_screenshot()
return Image(path="compressed_screenshot.png")


Expand All @@ -104,7 +132,7 @@ def get_package_action_intents(package_name: str) -> list[str]:
Returns:
list[str]: A list of all non-data actions from the Activity Resolver Table for the package
"""
result = deviceManager.get_package_action_intents(package_name)
result = _get_device_manager().get_package_action_intents(package_name)
return result


Expand Down