diff --git a/src/server.py b/src/server.py index 34b75ef..c5995dc 100644 --- a/src/server.py +++ b/src/server.py @@ -18,16 +18,21 @@ from pydantic import BaseModel, Field class DeviceInfo(BaseModel): - """Android device information""" - device_id: str - status: str + """Android device information returned by ADB""" + device_id: str = Field(description="Unique device identifier/serial number") + status: str = Field( + description="Device connection status", + json_schema_extra={ + "examples": ["device", "offline", "unauthorized", "no permissions"] + } + ) class ScreenshotResult(BaseModel): - """Screenshot capture result""" - success: bool - local_path: Optional[str] = None - error: Optional[str] = None + """Screenshot capture operation result""" + success: bool = Field(description="Whether the screenshot was captured successfully") + local_path: Optional[str] = Field(None, description="Absolute path to the saved screenshot file") + error: Optional[str] = Field(None, description="Error message if operation failed") class ADBCommand(BaseModel): @@ -37,14 +42,26 @@ class ADBCommand(BaseModel): class InputAction(BaseModel): - """Input action parameters""" - action_type: str = Field(description="Type of input: tap, swipe, key") - x: Optional[int] = None - y: Optional[int] = None - x2: Optional[int] = None - y2: Optional[int] = None - key_code: Optional[str] = None - text: Optional[str] = None + """Input action parameters for simulating user interactions""" + action_type: str = Field( + description="Type of input action to perform", + json_schema_extra={ + "enum": ["tap", "swipe", "key", "text"], + "examples": ["tap", "swipe", "key", "text"] + } + ) + x: Optional[int] = Field(None, description="X coordinate for tap/swipe start (pixels)", ge=0) + y: Optional[int] = Field(None, description="Y coordinate for tap/swipe start (pixels)", ge=0) + x2: Optional[int] = Field(None, description="X coordinate for swipe end (pixels)", ge=0) + y2: Optional[int] = Field(None, description="Y coordinate for swipe end (pixels)", ge=0) + key_code: Optional[str] = Field( + None, + description="Android key code (e.g., KEYCODE_BACK, KEYCODE_HOME, KEYCODE_CAMERA)", + json_schema_extra={ + "examples": ["KEYCODE_BACK", "KEYCODE_HOME", "KEYCODE_CAMERA", "KEYCODE_VOLUME_DOWN"] + } + ) + text: Optional[str] = Field(None, description="Text to type (for text action type)") # Initialize FastMCP server @@ -86,7 +103,15 @@ async def run_adb_command(cmd: List[str], device_id: Optional[str] = None) -> Di @mcp.tool() async def adb_devices() -> List[DeviceInfo]: - """List all connected Android devices""" + """ + List all Android devices connected via USB or network. + + Returns device information including unique identifiers and connection status. + Use this to identify available devices before performing other operations. + + Returns: + List of connected devices with their IDs and status + """ result = await run_adb_command(["devices"]) if not result["success"]: @@ -108,8 +133,23 @@ async def adb_devices() -> List[DeviceInfo]: @mcp.tool() -async def adb_screenshot(device_id: Optional[str] = None, local_filename: str = "screenshot.png") -> ScreenshotResult: - """Take screenshot from Android device and save locally""" +async def adb_screenshot( + device_id: Optional[str] = Field(None, description="Target device ID (if multiple devices connected)"), + local_filename: str = Field("screenshot.png", description="Local filename to save screenshot to") +) -> ScreenshotResult: + """ + Capture a screenshot from Android device and save it locally. + + Takes a screenshot of the current screen content and saves it as a PNG file. + Automatically handles device communication and file transfer. + + Args: + device_id: Specific device to target (optional if only one device) + local_filename: Name for the saved screenshot file + + Returns: + Result object with success status and file path + """ # Take screenshot on device result = await run_adb_command(["shell", "screencap", "-p", "/sdcard/temp_screenshot.png"], device_id) @@ -131,8 +171,26 @@ async def adb_screenshot(device_id: Optional[str] = None, local_filename: str = @mcp.tool() -async def adb_input(action: InputAction, device_id: Optional[str] = None) -> Dict[str, Any]: - """Send input events to Android device""" +async def adb_input( + action: InputAction = Field(description="Input action to perform on the device"), + device_id: Optional[str] = Field(None, description="Target device ID (if multiple devices connected)") +) -> Dict[str, Any]: + """ + Send input events to Android device to simulate user interactions. + + Supports various input types: + - tap: Single touch at coordinates (requires x, y) + - swipe: Drag gesture from one point to another (requires x, y, x2, y2) + - key: Hardware key press (requires key_code like KEYCODE_BACK) + - text: Type text input (requires text string) + + Args: + action: Input action configuration with type and parameters + device_id: Specific device to target (optional if only one device) + + Returns: + Command execution result with success status + """ if action.action_type == "tap": if action.x is None or action.y is None: @@ -162,24 +220,69 @@ async def adb_input(action: InputAction, device_id: Optional[str] = None) -> Dic @mcp.tool() -async def adb_launch_app(package_name: str, device_id: Optional[str] = None) -> Dict[str, Any]: - """Launch Android app by package name""" +async def adb_launch_app( + package_name: str = Field(description="Android package name (e.g., com.android.chrome)"), + device_id: Optional[str] = Field(None, description="Target device ID (if multiple devices connected)") +) -> Dict[str, Any]: + """ + Launch an Android application by its package name. + + Starts the main activity of the specified app. Use adb_list_packages to find + available package names on the device. + + Args: + package_name: Full package identifier (e.g., com.android.chrome, com.whatsapp) + device_id: Specific device to target (optional if only one device) + + Returns: + Command execution result with success status and output + """ cmd = ["shell", "monkey", "-p", package_name, "-c", "android.intent.category.LAUNCHER", "1"] result = await run_adb_command(cmd, device_id) return result @mcp.tool() -async def adb_launch_url(url: str, device_id: Optional[str] = None) -> Dict[str, Any]: - """Open URL in default browser""" +async def adb_launch_url( + url: str = Field(description="URL to open (e.g., https://example.com)"), + device_id: Optional[str] = Field(None, description="Target device ID (if multiple devices connected)") +) -> Dict[str, Any]: + """ + Open a URL in the device's default browser application. + + Launches the default browser and navigates to the specified URL. + Supports HTTP, HTTPS, and other URL schemes supported by Android. + + Args: + url: Web address to navigate to + device_id: Specific device to target (optional if only one device) + + Returns: + Command execution result with success status + """ cmd = ["shell", "am", "start", "-a", "android.intent.action.VIEW", "-d", url] result = await run_adb_command(cmd, device_id) return result @mcp.tool() -async def adb_list_packages(device_id: Optional[str] = None, filter_text: Optional[str] = None) -> Dict[str, Any]: - """List installed packages on device""" +async def adb_list_packages( + device_id: Optional[str] = Field(None, description="Target device ID (if multiple devices connected)"), + filter_text: Optional[str] = Field(None, description="Filter packages containing this text (e.g., 'chrome', 'google')") +) -> Dict[str, Any]: + """ + List all installed applications on the Android device. + + Retrieves package names of all installed apps, optionally filtered by text. + Useful for finding package names to use with adb_launch_app. + + Args: + device_id: Specific device to target (optional if only one device) + filter_text: Only return packages containing this text + + Returns: + Dictionary with success status, package list, and count + """ cmd = ["shell", "pm", "list", "packages"] if filter_text: cmd.extend(["|", "grep", filter_text]) @@ -202,8 +305,23 @@ async def adb_list_packages(device_id: Optional[str] = None, filter_text: Option @mcp.tool() -async def adb_shell_command(command: str, device_id: Optional[str] = None) -> Dict[str, Any]: - """Execute shell command on Android device""" +async def adb_shell_command( + command: str = Field(description="Shell command to execute (e.g., 'ls /sdcard', 'getprop ro.build.version.release')"), + device_id: Optional[str] = Field(None, description="Target device ID (if multiple devices connected)") +) -> Dict[str, Any]: + """ + Execute arbitrary shell commands on the Android device. + + Runs commands in the Android shell environment. Use with caution as this + provides direct access to the device's command line interface. + + Args: + command: Shell command string to execute + device_id: Specific device to target (optional if only one device) + + Returns: + Command execution result with stdout, stderr, and return code + """ cmd = ["shell"] + command.split() result = await run_adb_command(cmd, device_id) return result