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/
```
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues