"""Pydantic models for Android ADB MCP Server.""" from typing import Any from pydantic import BaseModel, Field # ── Data Models (not tool results) ────────────────────────────────── class DeviceInfo(BaseModel): """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"] }, ) model: str | None = Field(None, description="Device model name") product: str | None = Field(None, description="Product name") class CommandResult(BaseModel): """Result of an ADB command execution (internal).""" success: bool = Field(description="Whether the command succeeded") stdout: str = Field(default="", description="Standard output from command") stderr: str = Field(default="", description="Standard error from command") returncode: int = Field(description="Command exit code") # ── Base Result ───────────────────────────────────────────────────── class ADBResult(BaseModel): """Base result for all ADB tool operations.""" success: bool = Field(description="Whether the operation succeeded") error: str | None = Field(None, description="Error message if operation failed") class ActionResult(ADBResult): """Result of a simple action (tap, press, toggle, etc.).""" action: str = Field(description="Action that was performed") # ── Screenshot / Screen ───────────────────────────────────────────── class ScreenshotResult(ADBResult): """Screenshot capture operation result.""" local_path: str | None = Field( None, description="Absolute path to the saved screenshot file" ) class ScreenSizeResult(ADBResult): """Screen resolution information.""" width: int | None = Field(None, description="Screen width in pixels") height: int | None = Field(None, description="Screen height in pixels") raw: str | None = Field(None, description="Raw wm size output") class ScreenDensityResult(ADBResult): """Screen density information.""" dpi: int | None = Field(None, description="Screen density in DPI") raw: str | None = Field(None, description="Raw wm density output") class RecordingResult(ADBResult): """Screen recording result.""" local_path: str | None = Field(None, description="Path to saved recording file") duration_seconds: int | None = Field(None, description="Actual recording duration") class ScreenSetResult(ActionResult): """Screen size override result.""" width: int | None = Field(None, description="New width in pixels") height: int | None = Field(None, description="New height in pixels") # ── Input ──────────────────────────────────────────────────────────── class InputResult(ActionResult): """Result of an input simulation action.""" coordinates: dict[str, int] | None = Field( None, description="Tap/press coordinates {x, y}" ) key_code: str | None = Field(None, description="Key code sent") text: str | None = Field(None, description="Text typed or on clipboard") duration_ms: int | None = Field(None, description="Duration in ms") class SwipeResult(ActionResult): """Result of a swipe gesture.""" start: dict[str, int] = Field(description="Start coordinates {x, y}") end: dict[str, int] = Field(description="End coordinates {x, y}") duration_ms: int = Field(description="Swipe duration in ms") class ClipboardSetResult(ActionResult): """Result of a clipboard set operation.""" text: str = Field(description="Text placed on clipboard (preview)") pasted: bool | None = Field(None, description="Whether paste was performed") paste_error: str | None = Field(None, description="Paste error if any") class ShellResult(ADBResult): """Result of a shell command execution.""" command: str = Field(description="Command that was executed") stdout: str = Field(default="", description="Standard output") stderr: str = Field(default="", description="Standard error") returncode: int = Field(default=0, description="Exit code") # ── Apps ───────────────────────────────────────────────────────────── class AppActionResult(ActionResult): """Result of an app management action.""" package: str | None = Field(None, description="Target package name") url: str | None = Field(None, description="URL opened") output: str | None = Field(None, description="Command output") apk: str | None = Field(None, description="APK path") kept_data: bool | None = Field(None, description="Whether data was preserved") cancelled: bool | None = Field(None, description="Whether operation was cancelled") message: str | None = Field(None, description="Status message") class AppCurrentResult(ADBResult): """Currently focused app information.""" package: str | None = Field(None, description="Foreground package name") activity: str | None = Field(None, description="Current activity class") raw: str | None = Field( None, description="Raw dumpsys output (if no package found)" ) class PackageListResult(ADBResult): """List of installed packages.""" packages: list[str] = Field(default_factory=list, description="Package names") count: int = Field(default=0, description="Number of packages") class IntentResult(ActionResult): """Result of an intent-based operation.""" component: str | None = Field(None, description="Activity component") intent_action: str | None = Field(None, description="Intent action") data_uri: str | None = Field(None, description="Data URI") broadcast_action: str | None = Field(None, description="Broadcast action sent") package: str | None = Field(None, description="Target package") output: str | None = Field(None, description="Command output") # ── Devices ────────────────────────────────────────────────────────── class DeviceSelectResult(ADBResult): """Device selection/status result.""" device: dict[str, Any] | str | None = Field( None, description="Selected device info" ) message: str | None = Field(None, description="Status message") available: list[str] | dict[str, Any] | None = Field( None, description="Available devices" ) cached_info: Any | None = Field(None, description="Cached device info (if any)") class DeviceInfoResult(ADBResult): """Comprehensive device information.""" battery: dict[str, Any] | None = Field( None, description="Battery state (level, status, plugged)" ) ip_address: str | None = Field(None, description="Device IP address") wifi_ssid: str | None = Field(None, description="Connected WiFi SSID") model: str | None = Field(None, description="Device model") manufacturer: str | None = Field(None, description="Device manufacturer") device_name: str | None = Field(None, description="Device codename") android_version: str | None = Field(None, description="Android version") sdk_version: str | None = Field(None, description="SDK API level") storage: dict[str, int] | None = Field( None, description="Storage info (total_kb, used_kb, available_kb)" ) class RebootResult(ActionResult): """Device reboot result.""" mode: str = Field(description="Reboot mode (normal, recovery, bootloader)") cancelled: bool | None = Field(None, description="Whether cancelled") message: str | None = Field(None, description="Status message") class LogcatResult(ADBResult): """Logcat capture result.""" action: str | None = Field(None, description="Action (logcat_clear)") lines_requested: int | None = Field(None, description="Lines requested") filter: str | None = Field(None, description="Filter spec applied") output: str | None = Field(None, description="Log output") # ── Connectivity ───────────────────────────────────────────────────── class ConnectResult(ADBResult): """ADB network connection result.""" address: str = Field(description="Device address (host:port)") output: str | None = Field(None, description="ADB command output") already_connected: bool | None = Field( None, description="Whether already connected" ) class TcpipResult(ADBResult): """TCP/IP mode switch result.""" port: int | None = Field(None, description="ADB TCP port") device_ip: str | None = Field(None, description="Device IP address") connect_address: str | None = Field( None, description="Address for adb_connect (ip:port)" ) message: str | None = Field(None, description="Status message") class DevicePropertiesResult(ADBResult): """Batch device properties result.""" identity: dict[str, str] | None = Field( None, description="Identity props (model, manufacturer, serial)" ) software: dict[str, str] | None = Field( None, description="Software props (android version, sdk, build)" ) hardware: dict[str, str] | None = Field( None, description="Hardware props (chipset, ABI)" ) system: dict[str, str] | None = Field( None, description="System props (timezone, locale)" ) # ── UI ─────────────────────────────────────────────────────────────── class UIDumpResult(ADBResult): """UI hierarchy dump result.""" xml: str | None = Field(None, description="Raw XML hierarchy") clickable_elements: list[dict[str, Any]] = Field( default_factory=list, description="Interactive elements" ) element_count: int = Field(default=0, description="Number of interactive elements") class UIFindResult(ADBResult): """UI element search result.""" matches: list[dict[str, Any]] = Field( default_factory=list, description="Matching elements" ) count: int = Field(default=0, description="Number of matches") class WaitResult(ADBResult): """Wait/poll operation result.""" found: bool | None = Field(None, description="Whether text was found") gone: bool | None = Field(None, description="Whether text disappeared") element: dict[str, Any] | None = Field(None, description="Found element info") wait_time: float | None = Field(None, description="Time waited in seconds") attempts: int = Field(default=0, description="Poll attempts made") class TapTextResult(ActionResult): """Tap-by-text result.""" text: str | None = Field(None, description="Text searched for") coordinates: dict[str, int] | None = Field(None, description="Tapped coordinates") element: dict[str, Any] | None = Field(None, description="Element that was tapped") # ── Files ──────────────────────────────────────────────────────────── class FileTransferResult(ActionResult): """File push/pull result.""" local_path: str | None = Field(None, description="Host file path") device_path: str | None = Field(None, description="Device file path") output: str | None = Field(None, description="ADB output") class FileListResult(ADBResult): """Directory listing result.""" path: str | None = Field(None, description="Listed directory path") files: list[dict[str, Any]] = Field( default_factory=list, description="File entries" ) count: int = Field(default=0, description="Number of files") class FileDeleteResult(ActionResult): """File deletion result.""" path: str | None = Field(None, description="Deleted file path") cancelled: bool | None = Field(None, description="Whether cancelled") message: str | None = Field(None, description="Status message") class FileExistsResult(ADBResult): """File existence check result.""" path: str | None = Field(None, description="Checked path") exists: bool = Field(description="Whether the file exists") # ── Settings ───────────────────────────────────────────────────────── class SettingGetResult(ADBResult): """Settings read result.""" namespace: str | None = Field(None, description="Settings namespace") key: str | None = Field(None, description="Setting key") value: str | None = Field(None, description="Setting value") exists: bool | None = Field(None, description="Whether key exists") class SettingPutResult(ADBResult): """Settings write result.""" namespace: str | None = Field(None, description="Settings namespace") key: str | None = Field(None, description="Setting key") value: str | None = Field(None, description="Value written") readback: str | None = Field(None, description="Read-back verification") verified: bool | None = Field(None, description="Whether verified") cancelled: bool | None = Field(None, description="Whether cancelled") message: str | None = Field(None, description="Status message") class ToggleResult(ActionResult): """Radio/toggle result (wifi, bluetooth, airplane).""" verified: bool | None = Field(None, description="Whether state verified") wifi_on: str | None = Field(None, description="WiFi state after toggle") airplane_mode: bool | None = Field(None, description="Airplane mode state") cancelled: bool | None = Field(None, description="Whether cancelled") message: str | None = Field(None, description="Status message") class BrightnessResult(ADBResult): """Screen brightness result.""" brightness: int | None = Field(None, description="Brightness level 0-255") auto_brightness: bool | None = Field(None, description="Auto-brightness state") class TimeoutResult(ADBResult): """Screen timeout result.""" timeout_seconds: int | None = Field(None, description="Timeout in seconds") timeout_ms: int | None = Field(None, description="Timeout in milliseconds") class NotificationListResult(ADBResult): """Notification list result.""" notifications: list[dict[str, str | None]] = Field( default_factory=list, description="Notification entries" ) count: int = Field(default=0, description="Number of notifications") class ClipboardGetResult(ADBResult): """Clipboard read result.""" text: str | None = Field(None, description="Clipboard text content") method: str | None = Field(None, description="Method used to read") class MediaControlResult(ActionResult): """Media control result.""" keycode: str | None = Field(None, description="Android keycode sent") # ── Config ─────────────────────────────────────────────────────────── class ConfigStatusResult(BaseModel): """Server configuration status.""" developer_mode: bool = Field(description="Developer mode enabled") auto_select_single_device: bool = Field(description="Auto-select when one device") default_screenshot_dir: str | None = Field( None, description="Screenshot output directory" ) current_device: str | None = Field(None, description="Currently selected device") class ConfigResult(ADBResult): """Configuration change result.""" developer_mode: bool | None = Field(None, description="Developer mode state") screenshot_dir: str | None = Field(None, description="Screenshot directory") message: str | None = Field(None, description="Status message")