Every tool now returns a structured BaseModel instead of dict[str, Any], giving callers attribute access, IDE autocomplete, and schema validation. Adds ~30 model classes to models.py and updates all test assertions.
429 lines
16 KiB
Python
429 lines
16 KiB
Python
"""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")
|