Announcing Beacon: Self-improving memory for coding agents
← All MCP servers
MCP server

io.github.saranshbamania/mobile-device-mcp

mcp:saranshbamania/mobile-device-mcp

AI control of Android/iOS devices. Screenshots, UI tree, AI vision, Flutter, video. 49 tools.

Verdict
Suspicious

It takes control from the person who installs it, or hides content, without established intent.

Analyzed Oct 29, 2026, 16:05 UTC at 0c753ba

Dispute security verdict

If you believe this verdict is incorrect, submit a dispute and we'll review it.

4
findings
medium
highest severity
1
threat classes
62
files scanned
458/458
checks satisfied
0
package advisories

Why this verdict

  • suspicious: confirmed overreach finding mcp-server:.:FEATURES.md:148:PRIVESC
  • suspicious: confirmed overreach finding mcp-server:.:src/drivers/android/companion-client.ts:150:PRIVESC
  • suspicious: confirmed overreach finding mcp-server:.:src/drivers/android/companion-client.ts:200:PRIVESC
  • suspicious: confirmed overreach finding mcp-server:.:src/drivers/android/index.ts:150:PRIVESC

Security findings 4

Privilege escalation

mediumOverreachConfirmed on reviewmedium confidence

The auto-install flow silently installs the bundled companion APK with all runtime permissions granted (-g) and enables its AccessibilityService on the user's device without asking, which takes device authority without the user's consent.

Enables the AccessibilityService via `adb shell settings put`
vector:code
Reviewer's reasoning

The documented auto-install enables the companion's AccessibilityService via adb settings put without any user confirmation, which takes device authority the user did not grant. The companion is the artifact's own app, but enabling accessibility bypasses the user's consent step, so test 6 applies.

Privilege escalation

mediumOverreachConfirmed on reviewmedium confidence

The companion client enables its own Android accessibility service through ADB and installs the APK with permission grants, so the user is not asked to approve the accessibility grant in Settings; this is undeclared standing authority on the user's device.

This avoids the user having to navigate to Settings manually.
vector:code
Reviewer's reasoning

enableService writes the accessibility setting through adb so the user is not asked to approve it in Settings. This bypasses the consent prompt for a significant device permission, which is test 6 overreach.

Privilege escalation

mediumOverreachConfirmed on reviewmedium confidence

The companion install runs adb install with -g, which grants all runtime permissions to the companion app at install time without the user's review.

["install", "-r", "-g", apkPath]
vector:code
Reviewer's reasoning

The install command passes -g, which silently grants all runtime permissions to the companion app at install time with no user review. This takes device authority without consent, so test 6 applies.

Privilege escalation

mediumOverreachConfirmed on reviewmedium confidence

On first connect failure, the driver silently installs a companion APK, enables its accessibility service, and forces accessibility_enabled=1 on the device without asking the user, granting the app screen-reading control beyond the declared UI-inspection task.

Auto-installing companion app on
vector:code
Reviewer's reasoning

On first connect failure the driver silently installs the companion APK, enables its accessibility service, and forces accessibility_enabled=1 with no prompt to the user. This grants screen-reading control beyond the user's direct consent, so test 6 applies.

Notes 8

Capabilities, vulnerabilities and disclosed behavior worth knowing. Nothing here works against the person who installs it, so notes never change the verdict.

Code execution

mediumNoteConfirmed on reviewmedium confidence

The iOS driver's raw shell method runs a caller-supplied command inside the simulator via simctl spawn, split on spaces, with no shell quoting; it is a caller-invoked capability, not a hidden one.

const result = await this.simctl.execute(["spawn", deviceId, ...command.split(" ")]);
vector:instruction
Reviewer's reasoning

The shell method runs a caller-supplied command inside the simulator as its declared raw-shell capability, and it needs a caller to supply input; this is a declared core capability, not a hidden one.

Credential access

mediumNoteConfirmed on reviewmedium confidence

The server reads GEMINI_API_KEY and MCP_AI_API_KEY from the environment, which server.json does not declare, and uses them only as the key for the AI model service the server itself wraps, so this is undeclared operator configuration.

process.env.GEMINI_API_KEY
vector:code
Reviewer's reasoning

The server reads AI provider keys from the operator's environment and uses them only for the AI service it wraps, which is operator-controlled configuration and an API key for the wrapped service.

Credential access

mediumNoteConfirmed on reviewmedium confidence

The license check reads the MOBILE_MCP_LICENSE_KEY environment variable to set the tier; the read is undeclared but only gates the publisher's own pro tools and sends nothing off-host.

const key = process.env.MOBILE_MCP_LICENSE_KEY;
vector:code
Reviewer's reasoning

The license key environment variable is only read to set the tier for the publisher's own pro tools and nothing is sent off-host, so this is operator-controlled configuration.

Code execution

mediumNoteConfirmed on reviewlow confidence

Recorded tool parameters are embedded inside single-quoted Python source in the generated test script without escaping, so a parameter containing a quote breaks or alters the script the user later runs; a hygiene bug.

arguments=json.loads('${JSON.stringify(action.params)}'),
vector:code
Reviewer's reasoning

Unescaped parameters in the generated Python script are a hygiene bug in the user's own recorded test output, which the user chooses to run; no outside input is shown to drive it.

Supply chain

lowNoteConfirmed on reviewmedium confidence

The smithery launch config runs npx with -y on the unpinned publisher package mobile-device-mcp, so the latest published version is fetched and executed at each start; this is a note because it is the publisher's own package.

args: ["-y", "mobile-device-mcp"],
vector:instruction
Reviewer's reasoning

The launch config runs npx -y on the publisher's own unpinned package, which is the carve-out for the publisher's own package fetched when the user follows its instructions. npm is the official registry, so this is a note.

Privilege escalation

lowNoteConfirmed on reviewmedium confidence

The handlePopup tool, in its default auto mode, taps the first clickable button of any detected system permission dialog without user confirmation, so the agent grants permissions on the device the user controls.

// Fallback: if auto mode found nothing specific, tap the first clickable button
vector:code
Reviewer's reasoning

The handlePopup tool's auto mode is its declared capability (the doc comment says it can dismiss, accept, or auto-handle), and it runs only when the user's agent invokes it on the device. The blind fallback tap is a reliability flaw in doing the declared job, not a concealed or unconsented action, so it is a note.

Manipulation

lowNoteConfirmed on reviewmedium confidence

The free-tier upgrade message tells the user to pay for a license and add the key to .mcp.json; this is a publisher upsell shown to the user, who decides.

Add it to your .mcp.json:
vector:instruction
Reviewer's reasoning

The free-tier message is a publisher upsell shown to the user, who decides whether to pay and add the key; it does not steer the agent or act without the user.

Reconnaissance

infoNoteNot verified by reviewlow confidence

The Flutter driver probes localhost TCP ports in the DDS range 50000-60000 and reads HTTP status codes to locate a live Dart VM service, a discovery step beyond the declared single-app connection, which is a note-level behavior.

// DDS typically runs on ports 50000-60000
vector:code

Third-party flags 1

Behavior aimed at other sites, platforms or people, such as evading rate limits or offensive-security tooling. It does not affect the person who installs it, so it never changes the verdict.

Privilege escalation

lowNoteConfirmed on reviewmedium confidence

The license signing salt is hardcoded and a key generator ships in the artifact, so anyone can mint pro-tier keys that bypass the publisher's paywall; this affects only the publisher's licensing, not the installer.

const SALT = "m0b1l3-d3v1c3-mcp-2026-s4r4nsh";
vector:code
Reviewer's reasoning

The hardcoded salt and key generator let anyone mint pro keys, but the harm falls only on the publisher's paywall and does not act against the installer.