Skip to content

Server exits at import when emulator-5554 isn't present; physical devices are unreachable #33

Description

@jcyrio

TL;DRMobile() is constructed at module import time with a hardcoded emulator-5554 default, so the process dies before the MCP handshake if that emulator isn't running. Because the Claude Desktop extension never passes --device, a physical handset can never be driven, and the failure surfaces to the user only as "Server disconnected".

Environment

Android-MCP 0.1.0 (installed as a Claude Desktop extension, ant.dir.gh.cursortouch.android-mcp)
Host Windows 11 (10.0.26200)
Python 3.13 (uv-managed venv)
Launcher uv --directory <ext> run android-mcp (from manifest.json)
Device physical handset, adb devices reports state device
Emulator none running

Steps to reproduce

  1. Attach and authorize a physical Android device. Confirm adb devices lists it as device.
  2. Do not start an emulator.
  3. Start the MCP server the way the extension does: uv --directory <ext> run android-mcp.

Actual result

The process raises during import and exits before serving anything:

uiautomator2.exceptions.ConnectError: device emulator-5554 not online

During handling of the above exception, another exception occurred:
  File "src\android_mcp\__main__.py", line 26, in <module>
    mobile=Mobile(device=args.device if args.device else 'emulator-5554')
  File "src\android_mcp\mobile\service.py", line 14, in __init__
    raise ConnectionError(f"Failed to connect to device {device}: {e}")
ConnectionError: Failed to connect to device emulator-5554: device emulator-5554 not online

The client sends initialize, gets a closed pipe, and reports:

[Android-MCP] [error] Server disconnected. For troubleshooting guidance, please visit our debugging documentation

Expected result

The server starts, advertises its tools, and drives the attached device — or, if no device is reachable, still starts and returns an actionable error from the first tool call.

Root cause

Two independent problems compound:

1. Connection happens at import, so failure is fatal. __main__.py line 26 runs at module scope:

mobile=Mobile(device=args.device if args.device else 'emulator-5554')
device=mobile.get_device()

A device is a runtime-variable resource — unplugged, asleep, unauthorized, revoked mid-session — but binding it at import turns any transient absence into a dead server. The MCP client can't distinguish "server is broken" from "phone is unplugged", so the user sees only a generic disconnect.

2. There is no way to specify a device through the extension. --device exists in the ArgumentParser, but manifest.json hardcodes the launch args with no user_config block:

"mcp_config": {
  "command": "uv",
  "args": ["--directory", "${__dirname}", "run", "android-mcp"]
}

So the default always wins, and an attached handset is never even enumerated. The README's "Ensure ADB is installed and your device is connected/authorized" implies physical devices are supported, but through the extension they cannot be.

Suggested fix

Three changes, roughly in order of value:

  1. Connect lazily. Construct Mobile on first tool use (or in the existing lifespan hook) rather than at import, and surface connection failures as tool errors. The lifespan context manager is already defined and unused — it's a natural home, though a per-call lazy accessor also allows recovery after a reconnect without restarting the server.

  2. Auto-detect when --device is absent. uiautomator2 already depends on adbutils, so picking the first online device costs almost nothing:

def resolve_device(preferred=None):
    if preferred:
        return preferred
    serials = [d.serial for d in adbutils.adb.list() if d.state == 'device']
    if not serials:
        raise ConnectionError('No online ADB devices. Attach and authorize a device, or pass --device.')
    return serials[0]

Erroring with an explicit message when nothing is attached beats silently falling back to emulator-5554, which is what makes the current failure so opaque.

  1. Expose the serial in manifest.json. Add a user_config entry for the device serial and thread it into the args, so users with several devices attached can choose without editing files inside the extension directory.

I patched (2) locally to confirm the diagnosis — the server came up immediately and bound the physical device, no other changes needed:

Android-MCP: auto-selected device 652B...0012
FastMCP 2.14.0 | Server name: Android-MCP | Transport: STDIO
INFO  Starting MCP server 'Android-MCP' with transport 'stdio'

I've since reverted it to stay on the stock install. Happy to open a PR for any of the three if that's useful.

Workaround for anyone else hitting this

Start an emulator before launching Claude Desktop — the first AVD gets serial emulator-5554 — or edit src/android_mcp/__main__.py in the installed extension directory as above, accepting that an extension update will overwrite it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions