Defer device acquisition so MCP server starts without an attached device#18
Open
mariovpereira wants to merge 1 commit into
Open
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
AdbDeviceManager.__init__callssys.exit(1)at module load time when no Android device is connected viaadb. Whenserver.pyis 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 listjust shows✘ Failed to connectwith no actionable error reaching the user, because the process died before any tool call could surface the underlyingadbstate.Fix
The existing
AdbDeviceManageralready supportsexit_on_error=False, which raisesRuntimeErrorinstead of callingsys.exit. This PR uses that path inserver.py:exit_on_error=False. If it fails, log a one-line warning and continue — the MCP stdio server starts normally.Android device unavailable: <reason>. Connect a device via adb and retry.) instead of the server being dead.Net behavior:
Backward compatibility
config.ymlschema are untouched.exit_on_error=Falseparameter already present inAdbDeviceManager— no new flags or config keys.Smoke evidence
Tested on macOS with no Android device attached:
python server.pyexits immediately with code 1;claude mcp listreports✘ Failed to connect.python server.pystays alive under a 4s timeout (only killed by the timeout, not by self-exit);claude mcp listreports✓ Connected. Callingmobile_take_screenshotwith 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.exittranslates 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
server.py, +35/-7). No new dependencies.