Skip to content

Defer device acquisition so MCP server starts without an attached device#18

Open
mariovpereira wants to merge 1 commit into
minhalvp:masterfrom
mariovpereira:defer-device-check-startup-resilience
Open

Defer device acquisition so MCP server starts without an attached device#18
mariovpereira wants to merge 1 commit into
minhalvp:masterfrom
mariovpereira:defer-device-check-startup-resilience

Conversation

@mariovpereira

Copy link
Copy Markdown

Problem

AdbDeviceManager.__init__ calls sys.exit(1) at module load time when no Android device is connected via adb. When server.py is launched as an MCP stdio server (Claude Code, Cursor, etc.), this kills the process before the MCP handshake completes. The host then marks the server as "failed to connect" and does not retry, even after a device is plugged in seconds later.

The symptom from the host side is silent — claude mcp list just shows ✘ Failed to connect with no actionable error reaching the user, because the process died before any tool call could surface the underlying adb state.

Fix

The existing AdbDeviceManager already supports exit_on_error=False, which raises RuntimeError instead of calling sys.exit. This PR uses that path in server.py:

  1. At startup, try device init with exit_on_error=False. If it fails, log a one-line warning and continue — the MCP stdio server starts normally.
  2. Each tool wrapper re-attempts device acquisition on first call. If a device is now attached, the tool runs. If not, the tool returns a clear error to the caller (Android device unavailable: <reason>. Connect a device via adb and retry.) instead of the server being dead.

Net behavior:

State Before After
Device present at startup tools work tools work (unchanged)
No device at startup, plug in later, then call tool server already dead tool succeeds (server self-heals)
No device at startup, no plug-in, call tool server already dead, no error reaches caller tool returns clear error to caller

Backward compatibility

  • No public API change. Tool signatures, return types, and config.yml schema are untouched.
  • Leverages the existing exit_on_error=False parameter already present in AdbDeviceManager — no new flags or config keys.
  • Behavior with a device attached at startup is byte-identical to the current behavior.

Smoke evidence

Tested on macOS with no Android device attached:

  • Before patch: python server.py exits immediately with code 1; claude mcp list reports ✘ Failed to connect.
  • After patch: python server.py stays alive under a 4s timeout (only killed by the timeout, not by self-exit); claude mcp list reports ✓ Connected. Calling mobile_take_screenshot with no device returns a clear error message rather than failing silently.

Why this matters for MCP hosts

MCP servers run as long-lived stdio processes. A startup-time sys.exit translates to a permanent "failed" state in the host UI with no path to recovery short of re-launching the host. Deferring device acquisition to first tool call lets the server tolerate the (very common) workflow of starting the host first and plugging in a device second, and produces actionable errors on the call path rather than silence at startup.

Notes

  • Single-file change (server.py, +35/-7). No new dependencies.
  • I'm happy to adjust the warning-log message format, error string wording, or split the changes differently if it would help review. This is offered as a contribution back from a fleet that hit and patched this locally — no expectation of merge timing.

…evice

Previously, AdbDeviceManager.__init__ would call sys.exit(1) at module
load time if no Android device was connected via adb, which caused
Claude / Cursor / any MCP host to mark the server as "failed to connect"
and never retry — even when the device became available seconds later.

The class already supports exit_on_error=False which raises RuntimeError
instead of exiting. This change uses it: the server tries device init
at startup, logs a one-line warning if no device is present, then
proceeds to start the MCP stdio transport. Tool calls re-attempt device
acquisition on first use, so the server self-heals as soon as a device
appears via `adb`.

Behavior matrix:
  device present at startup    → tools work immediately (unchanged)
  no device at startup, plug in later, then call tool
                               → tool succeeds (NEW — was: server dead)
  no device at startup, no plug-in, call tool
                               → tool returns clear error
                                 "Android device unavailable: ...
                                  Connect a device via adb and retry."
                                 (NEW — was: server dead, no error to caller)

Smoke verified locally: server stays alive in MCP stdio loop with no
device attached (previously exited with code 1 immediately).

Resolves: MCP host showing android-mcp as failed when no device attached.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant