Skip to content
Open
Show file tree
Hide file tree
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
20 changes: 15 additions & 5 deletions adbdevicemanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,26 @@


class AdbDeviceManager:
def __init__(self, device_name: str | None = None, exit_on_error: bool = True) -> None:
def __init__(
self,
device_name: str | None = None,
exit_on_error: bool = True,
adb_host: str = "127.0.0.1",
adb_port: int = 5037,
) -> None:
"""
Initialize the ADB Device Manager

Args:
device_name: Optional name/serial of the device to manage.
If None, attempts to auto-select if only one device is available.
exit_on_error: Whether to exit the program if device initialization fails
adb_host: Host address of the ADB server (default: 127.0.0.1)
adb_port: Port of the ADB server (default: 5037)
"""
self.adb_host = adb_host
self.adb_port = adb_port

if not self.check_adb_installed():
error_msg = "adb is not installed or not in PATH. Please install adb and ensure it is in your PATH."
if exit_on_error:
Expand Down Expand Up @@ -60,7 +71,7 @@ def __init__(self, device_name: str | None = None, exit_on_error: bool = True) -

# At this point, selected_device_name should always be set due to the logic above
# Initialize the device
self.device = AdbClient().device(selected_device_name)
self.device = AdbClient(host=self.adb_host, port=self.adb_port).device(selected_device_name)

@staticmethod
def check_adb_installed() -> bool:
Expand All @@ -72,10 +83,9 @@ def check_adb_installed() -> bool:
except (subprocess.CalledProcessError, FileNotFoundError):
return False

@staticmethod
def get_available_devices() -> list[str]:
def get_available_devices(self) -> list[str]:
"""Get a list of available devices."""
return [device.serial for device in AdbClient().devices()]
return [device.serial for device in AdbClient(host=self.adb_host, port=self.adb_port).devices()]

def get_packages(self) -> str:
command = "pm list packages"
Expand Down
6 changes: 6 additions & 0 deletions config.yaml.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Android MCP Server Configuration
# All sections are optional

# ADB server configuration (optional)
# If not specified, defaults to localhost:5037
adb:
host: "127.0.0.1" # ADB server host (default: 127.0.0.1)
port: 5037 # ADB server port (default: 5037)

# Device configuration (optional)
# If not specified or name is empty, will auto-select device when only one is connected
device:
Expand Down
13 changes: 12 additions & 1 deletion server.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,20 @@
# Load config (make config file optional)
config = {}
device_name = None
adb_host = "127.0.0.1"
adb_port = 5037

if os.path.exists(CONFIG_FILE):
try:
with open(CONFIG_FILE) as f:
config = yaml.safe_load(f.read()) or {}

# Load ADB server configuration
adb_config = config.get("adb", {})
if adb_config:
adb_host = adb_config.get("host", adb_host)
adb_port = adb_config.get("port", adb_port)

device_config = config.get("device", {})
configured_device_name = device_config.get(
"name") if device_config else None
Expand All @@ -28,9 +37,11 @@
if configured_device_name and configured_device_name.strip():
device_name = configured_device_name.strip()
print(f"Loaded config from {CONFIG_FILE}")
print(f"ADB server: {adb_host}:{adb_port}")
print(f"Configured device: {device_name}")
else:
print(f"Loaded config from {CONFIG_FILE}")
print(f"ADB server: {adb_host}:{adb_port}")
print(
"No device specified in config, will auto-select if only one device connected")
except Exception as e:
Expand All @@ -45,7 +56,7 @@
# Initialize MCP and device manager
# AdbDeviceManager will handle auto-selection if device_name is None
mcp = FastMCP("android")
deviceManager = AdbDeviceManager(device_name)
deviceManager = AdbDeviceManager(device_name, adb_host=adb_host, adb_port=adb_port)


@mcp.tool()
Expand Down
44 changes: 41 additions & 3 deletions tests/test_adb_device_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,19 +138,27 @@ def test_check_adb_installed_failure(self, mock_run):

assert result is False

@patch('adbdevicemanager.AdbDeviceManager.check_adb_installed')
@patch('adbdevicemanager.AdbClient')
def test_get_available_devices(self, mock_adb_client):
def test_get_available_devices(self, mock_adb_client, mock_check_adb):
"""Test getting available devices"""
# Setup mock devices
# Setup mocks
mock_check_adb.return_value = True

mock_device1 = MagicMock()
mock_device1.serial = "device123"
mock_device2 = MagicMock()
mock_device2.serial = "device456"

mock_adb_client.return_value.devices.return_value = [
mock_device1, mock_device2]
mock_adb_client.return_value.device.return_value = MagicMock()

# Create manager with first device to test get_available_devices
with patch('builtins.print'):
manager = AdbDeviceManager(device_name="device123", exit_on_error=False)

devices = AdbDeviceManager.get_available_devices()
devices = manager.get_available_devices()

assert devices == ["device123", "device456"]

Expand All @@ -169,3 +177,33 @@ def test_exit_on_error_true(self, mock_adb_client, mock_get_devices, mock_check_
AdbDeviceManager(device_name=None, exit_on_error=True)

mock_exit.assert_called_once_with(1)

@patch('adbdevicemanager.AdbDeviceManager.check_adb_installed')
@patch('adbdevicemanager.AdbClient')
def test_custom_adb_host_port(self, mock_adb_client, mock_check_adb):
"""Test that custom ADB host and port are used"""
# Setup mocks
mock_check_adb.return_value = True
mock_device1 = MagicMock()
mock_device1.serial = "device123"
mock_adb_client.return_value.devices.return_value = [mock_device1]
mock_adb_client.return_value.device.return_value = MagicMock()

# Test with custom host and port
custom_host = "192.168.1.100"
custom_port = 5038

with patch('builtins.print'):
manager = AdbDeviceManager(
device_name=None,
exit_on_error=False,
adb_host=custom_host,
adb_port=custom_port
)

# Verify custom host and port are stored
assert manager.adb_host == custom_host
assert manager.adb_port == custom_port

# Verify AdbClient was called with custom host and port
mock_adb_client.assert_called_with(host=custom_host, port=custom_port)