Skip to main content
Glama
banditPradyun

Hunt-Droid Mobile MCP

README.md
# Hunt-Droid Mobile MCP

**"Playwright for mobile" — as a Model Context Protocol server.**

An MCP server that lets any MCP client (Claude Desktop, Claude Code, Cursor, or an
agent framework like [mamta](https://github.com/)) **drive a real Android app**, capture
its API traffic, and test its mobile-native attack surface — the mobile analogue of the
Playwright MCP used for the web.

It exposes **32 `mobile_*` tools** over HTTP: open a device session (UI driving via the
MobileRun accessibility layer + mitmproxy capture + Frida SSL-unpinning), drive the app
by semantic locators, read the API traffic it produces, and replay/modify requests —
exactly the surface an API-security agent (BOLA/BFLA/injection/mass-assignment/…) needs,
sourced from a phone instead of a browser.

> Extracted, self-contained, from the [Hunt-Droid](../) dynamic mobile-testing project.
> The internal Python package is named `mamtamobile` (Hunt-Droid's shared device core).

---

## Architecture

```
  MCP client (Claude / mamta / Cursor)
        │  MCP over HTTP  (streamable-http or SSE)
        ▼
  Hunt-Droid Mobile MCP server   ── runs on the host where the phone is attached ──
        │
        ├── adb + MobileRun Portal (a11y)  →  get_ui / tap / type / scroll / find
        ├── am / content / dumpsys / logcat →  deeplinks, IPC, providers (attack surface)
        ├── mitmproxy (device global proxy) →  API traffic capture
        └── Frida                            →  SSL-unpinning (spawns the app)
        ▼
  Android device (USB or network-adb)
```

- **HTTP transport** because the physical device is on *this* host; the agent connects
  over the network. Default: `streamable-http` at `http://<host>:8765/mcp`.
- **One device per server.** A phone is a single resource; scale by running one server
  per device.
- **Accessibility-tree driving** (token-efficient, no vision model needed).

---

## Install

Requires **Python 3.10+**, plus the runtime tooling on the host: **adb**, a connected
Android device, **mitmproxy**, and **Frida** (frida-server on the device). The MobileRun
**Portal** app must be installed on the device (accessibility driver).

```bash
git clone <this-repo> hunt-droid-mobile-mcp
cd hunt-droid-mobile-mcp
pip install -e .          # installs deps + the `mamtamobile-mcp` command
```

Traffic capture and SSL-unpinning are optional at runtime — without mitmproxy/Frida the
server still drives the UI, it just won't capture HTTPS.

---

## Run

```bash
adb devices                      # confirm a device is attached first
mamtamobile-mcp                  # or: python -m mamtamobile.mcp
# → mobile MCP server starting: transport=streamable-http host=0.0.0.0 port=8765
```

Configuration (all optional, via environment):

| Var | Default | Meaning |
|---|---|---|
| `MM_MCP_HOST` | `0.0.0.0` | bind address (`127.0.0.1` = localhost-only, safer) |
| `MM_MCP_PORT` | `8765` | port |
| `MM_MCP_TRANSPORT` | `streamable-http` | `streamable-http` or `sse` |
| `MM_MCP_ALLOW_SHELL` | `0` | `1` exposes `mobile_adb_shell` (arbitrary adb — powerful) |
| `MM_DEVICE_SERIAL` | (auto) | pin a device serial (else the connected one / `mobile_start_session`'s `device` arg) |
| `MM_PROXY_PORT` | `18080` | mitmproxy listen port |
| `MM_FRIDA_ENABLED` | `1` | SSL-unpinning on/off |
| `MM_REPORT_DIR` | `reports` | where captured traffic/artifacts are stored |

Quick check it's up:
```bash
curl -s -X POST http://127.0.0.1:8765/mcp \
  -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"c","version":"1"}}}'
# → HTTP 200 + an SSE "event: message" reply
```

---

## Tools

All tools are prefixed `mobile_` so clients can namespace them (like Playwright's
`browser_`). A typical flow: `list_devices → start_session → find/tap → list_traffic →
send_request`.

### Session
| Tool | Purpose |
|---|---|
| `mobile_list_devices()` | List adb-connected devices (serials). Works with no session. |
| `mobile_start_session(package, device?)` | Open device, spawn app Frida-unpinned, start mitm capture. **Call first.** |
| `mobile_stop_session()` / `mobile_session_status()` | Tear down / inspect the session. |

### Drive the UI
| Tool | Purpose |
|---|---|
| `mobile_get_ui()` | Numbered accessibility snapshot. |
| `mobile_find(query, by?)` | Locate elements by text / resource-id / content-desc / class. |
| `mobile_tap_on(query, by?)` / `mobile_type_into(text, query?, by?)` | Tap / type by **what the element is**, not a fragile index. |
| `mobile_wait_for(text, timeout?, gone?)` | Auto-poll until text appears/disappears. |
| `mobile_scroll_to(text, dir?, max_swipes?)` | Scroll until an element is on screen. |
| `mobile_tap(i)` / `mobile_input_text(t,i?)` / `mobile_scroll(dir)` / `mobile_press_back()` / `mobile_open_app(pkg?)` | Index-based driving. |
| `mobile_screenshot()` | PNG image content. |
| `mobile_reset_app(package?)` | `pm clear` — deterministic logout / clean slate. |

### Traffic (what an API-security agent consumes)
| Tool | Purpose |
|---|---|
| `mobile_list_traffic(limit?, method?, host?)` | Distinct captured API requests — the attack surface. |
| `mobile_get_traffic(id)` | Full request+response for one entry. |
| `mobile_send_request(method, url, headers?, body?)` | Resend a (modified) request — the IDOR/BOLA/injection primitive. |
| `mobile_action_traffic(settle?)` | The requests your **last UI action** triggered (action→endpoint correlation). |

### Mobile-native attack surface
| Tool | Purpose |
|---|---|
| `mobile_attack_surface()` | Enumerate deeplink schemes, exported components, provider authorities (`dumpsys`). |
| `mobile_open_deeplink(uri)` | Fire a deeplink / custom-scheme VIEW intent. |
| `mobile_start_activity(component?, action?, data?, extras?)` | Launch an Activity with a crafted intent. |
| `mobile_send_broadcast(...)` / `mobile_start_service(component, extras?)` | Exported receiver / service IPC PoCs. |
| `mobile_query_provider(uri, projection?, where?)` | Content-provider access-control / SQLi / traversal. |
| `mobile_logcat(filter?, lines?)` | Runtime evidence — leaked tokens, PoC confirmation. |
| `mobile_current_activity()` / `mobile_list_apps(include_system?)` | Orientation / target enumeration. |
| `mobile_forensics()` | Scan on-device storage (shared_prefs, dbs, files) for secrets. |
| `mobile_adb_shell(command)` | Arbitrary shell PoC — **disabled unless `MM_MCP_ALLOW_SHELL=1`**. |

---

## Connect a client

### Claude Code
```bash
claude mcp add --transport http hunt-droid-mobile http://127.0.0.1:8765/mcp
# then /mcp shows the mobile_* tools
```

### Claude Desktop
Custom connectors need HTTPS, but a local server is plain HTTP — bridge it with the
Python `mcp-proxy` (no Node needed):
```bash
pip install --user mcp-proxy
# run the server in SSE mode:
MM_MCP_TRANSPORT=sse mamtamobile-mcp
```
`claude_desktop_config.json`:
```json
{
  "mcpServers": {
    "hunt-droid-mobile": {
      "command": "mcp-proxy",
      "args": ["http://127.0.0.1:8765/sse"]
    }
  }
}
```
Restart Claude Desktop. (Or use `npx mcp-remote http://127.0.0.1:8765/mcp` if you have
Node ≥ 20.)

### mamta / any MCP client
mamta connects with a Go streamable-http MCP client and registers the `mobile_*` tools
into its agent (mirroring its Playwright MCP integration). Point it at
`http://<host>:8765/mcp`.

Then just ask: *"start a session on `com.example.app`, log in, list the API traffic, and
try swapping the order id in `/orders/{id}`."*

---

## Security

- **No authentication.** Fine on `localhost` / a trusted LAN. Before exposing it, bind
  `MM_MCP_HOST=127.0.0.1` or put it behind an authenticated proxy — otherwise anyone on
  the network can drive your device.
- **`mobile_adb_shell` is off by default.** Only enable `MM_MCP_ALLOW_SHELL=1` on a
  device you're authorized to test.
- **Only test apps/devices you own or are authorized to assess.** This tool drives real
  intents, replays real requests, and reads on-device storage.
- **`reports/` and `.env` are git-ignored** (captured traffic contains tokens/PII).

---

## Development

```bash
pip install -e ".[dev]"
python -m pytest tests/            # device-free: tools tested against an injected fake session
python -m mamtamobile.mcp          # run the server
```

The tests exercise tool registration + dispatch (session lifecycle, driving, semantic
locators, traffic, attack-surface parsing, device listing) with a fake harness/portal —
no device, mitmproxy, or LLM required.

## Layout
```
mamtamobile/
  mcp/          # the MCP server (server.py) + entry point
  config.py     # env-based config
  explorer/     # mobile_tools.py — MobileRun device-tool harness
  device/       # portal.py — adb wrapper + Portal client (intents, providers, logcat)
  proxy/        # mitmproxy capture (setup + addon)
  instrument/   # Frida SSL-unpinning (pinning.py + ssl-bypass.js)
  forensics/    # on-device storage scan
  findings/     # per-session traffic + findings store
mobilerun/      # vendored MobileRun tool/UI source (device driving)
tests/
```