WormaCeptor exposes a Model Context Protocol (MCP) server that lets AI agents such as Claude Code and Cursor inspect a running Android app in real time. Ask "why did the app crash?" or "show me the last 5 failed requests" and the agent reads live data from the device instead of pasted logs.
It has two parts:
Device server: an HTTP server embedded in your debug build, bound to 127.0.0.1:8999 on the device
Bridge CLI: a JVM program on your computer that speaks MCP (JSON-RPC 2.0 over stdin/stdout) and forwards each tool call to the device server through adb port forwarding
AI agent / IDE <-- MCP over stdio --> Bridge CLI (JVM) <-- HTTP via adb forward --> Device server (debug app, port 8999)
The device server is a debugImplementation dependency, so none of it reaches release builds.
These steps are written so a coding agent can do the whole setup itself. Use the WormaCeptor version the project already depends on (the version of wormaceptor-client) everywhere VERSION appears; the server, bridge and launcher are released together.
Add the device server to the app module, next to the existing WormaCeptor dependencies:
Register the bridge with the MCP client. With Node.js 18+ (the launcher downloads the matching bridge jar from the GitHub release, checks its SHA-256 and caches it):
claude mcp add --transport stdio wormaceptor -- npx -y wormaceptor-mcp@VERSION
Without Node, download the jar and its checksum, verify, and register it directly:
The bridge needs Java 17+ and adb. If java isn't on PATH, use Android Studio's bundled JDK (macOS: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java); the npx launcher finds it automatically. The bridge looks for adb in $ANDROID_HOME/platform-tools, $ANDROID_SDK_ROOT/platform-tools, on PATH, then in the default SDK dir (macOS ~/Library/Android/sdk, Linux ~/Android/Sdk, Windows %LOCALAPPDATA%\Android\Sdk); anywhere else, pass --adb <path>.
With more than one device attached, append --device <serial> (from adb devices) to the command.
Build and run the debug app, keep it in the foreground, then check with claude mcp get wormaceptor and call get_device_info.
The server auto-starts via a ContentProvider, no code changes needed. It answers as soon as the process starts; tools return data once WormaCeptorApi.init() has run.
# Device or emulator must show as "device"adb devices# Run the bridge once by hand (it sets up port forwarding itself)java -jar mcp/bridge/build/libs/bridge.jar
The bridge discovers the device, forwards port 8999, verifies the server is reachable, and waits for MCP requests on stdin.
The server works out of the box with defaults. To change them, call configureMcpServer in Application.onCreate. If the server already auto-started, it restarts with the new settings:
List captured crash reports, paginated (default 20)
get_crash
id
Get full crash details with stack trace
tail_logs
level?, tag?, limit?
Retrieve the most recent log entries (default 30). level matches exactly (VERBOSE...ASSERT); tag is a case-insensitive substring. The bridge reads the app process's logcat over adb (logcat -d -v threadtime --pid=<pid>): the whole buffer, no in-app capture and no Android 13+ log access prompt. If adb can't read it, it falls back to /api/logs, which only has entries while WormaCeptor's in-app capture runs (Logs screen)
list_leaks
—
List detected memory leaks (Activity leak detection)
list_violations
—
List StrictMode and thread policy violations
get_device_info
—
Get device model, Android version, app info, and system properties
Get CPU usage with optional history. When the monitor is off, returns a one-off sample and no history
get_memory_stats
include_history?
Get memory usage with optional history. When the monitor is off, returns a one-off sample and no history
get_fps_stats
include_history?
Get frame rate stats with optional history. Returns an error while FPS monitoring is off (start it with set_monitoring, the FPS tool or the performance overlay)
get_performance_snapshot
—
Get a combined snapshot of CPU, memory, and FPS
set_monitoring
target, enabled
Start or stop the cpu, memory or fps monitor. Stopping keeps a monitor running while the performance overlay shows it
Empty the in-app log buffer; tail_logs then hides entries logged before the call (by host clock; logcat itself isn't cleared)
simulate_location
latitude, longitude, altitude?, name?
Set a mock GPS location (lat: -90..90, lng: -180..180). The app must be the mock location app: Developer options, or adb shell appops set <package> android:mock_location allow
stop_location_simulation
—
Stop mock location
send_push_notification
title, body, channel_id?, priority?
Send a simulated push notification
bring_app_to_front
package?
Launch the app or bring it back to the foreground via adb (monkey with the launcher intent). Bridge-only; defaults to the package the bridge last reached
Transactions, crashes, logs, StrictMode violations, leaks and WebSocket messages merged in time order, newest last. Window defaults to the last 5 minutes (device clock, epoch ms), limit to 50 (max 500, newest kept). Logs are included only while log capture is running
wait_for_transaction
url_contains?, method?, status?, timeout_s?
Block until a NEW request matching the filters finishes (started after the call), then return it. url_contains is a case-insensitive URL substring, status an exact code. timeout_s defaults to 30, max 120
wait_for_crash
timeout_s?
Block until a NEW crash is recorded, then return it with the stack trace. A fatal crash kills the process, so the call can end with "connection dropped": relaunch the app and use list_crashes
Mocks apply to OkHttp and Ktor clients that use the WormaCeptor interceptor or plugin. Rules are the same ones the in-app Mock Rules screen edits, and only apply while mocking is on globally (list_mock_rules shows it).
Tool
Parameters
Description
list_mock_rules
—
List mock rules (pattern, match type, method, status, content type, body preview, delay, enabled) and whether mocking is on
Create a rule that returns a canned response. match_type: PREFIX (default) or EXACT compare against the full URL including scheme and query; REGEX searches anywhere in it. method omitted matches any; content_type defaults to application/json; status 100-599
mock_from_transaction
id, status?, body?
Copy a captured transaction into a rule, like the in-app "Add to Mock": exact URL and method, captured status, content type and text body. status / body override them; binary or oversized bodies are left out
Build a shell-quoted cURL command (method, URL, headers, text body) for a captured request. Sensitive headers stay [REDACTED] and are listed; binary bodies are left out
replay_transaction
id, url?, headers?, body?
Send a captured request again from the device and capture it as a new transaction; returns its id and status. headers is an object whose entries replace captured headers of the same name. Goes straight to the server: mock rules, throttling and the app's own interceptors don't apply
Auth is off by default. With it off, anything that can reach port 8999 on the device can read every captured request and response body, crash, log line, SharedPreferences value, database row and app file, and can run the action tools. The server binds to 127.0.0.1 and rejects browser requests, so in practice that means:
other apps on the same device (any app can connect to 127.0.0.1:8999)
anyone with adb access to the device, and any local process on your computer while adb forward is active
That is acceptable on a personal emulator or test device. Turn auth on for shared devices, device farms, or apps that handle real user data:
Then pass --token your-secret-token to the bridge CLI. If enableAuth is true but authToken is blank, the server refuses to start rather than run unprotected.
Without a token the server answers 401, with a wrong token 403. The health endpoint (/api/health) always bypasses authentication so the bridge can probe it.
The bridge validates tool inputs before forwarding to the device, and the server checks again:
Path traversal: file paths and database names containing .. are rejected by the bridge; the server only resolves paths inside the app's own storage directories
SQL injection — only SELECT queries are allowed; DROP, DELETE, INSERT, UPDATE, ALTER are blocked
Parameter ranges — latitude (-90..90), longitude (-180..180) are bounds-checked
Required fields — IDs, titles, and bodies are checked for presence and non-emptiness
The device server binds to 127.0.0.1:8999 on the Android device. It is not exposed on the device's network interfaces; from your computer it is reachable only through ADB port forwarding.
Usage: java -jar bridge.jar [options]Options: --port <port> Device server port (default: 8999) --device, -s <id> Target device serial (required if multiple devices) --adb <path> Path to adb (default: searches ANDROID_HOME, ANDROID_SDK_ROOT, PATH, then the default Android SDK dir) --token <token> Bearer token for authentication --package <id> App under debug (default: the only installed app with the MCP server) --verbose, -v Enable verbose request/response logging --version Print version and exit --help, -h Print help
At startup the bridge waits about 10 seconds for the server, then starts anyway. A tool call that fails with a connection error re-creates the port forward, retries 4 times with backoff (1s, 2s, 4s), then repeats the call once. --verbose logs every MCP request and response to stderr.