WormaCeptor

MCP Server

Let AI coding agents read network traffic, crashes, logs, performance, and storage from a running debug build over the Model Context Protocol.

Available from WormaCeptor 3.4.0

The MCP server ships with WormaCeptor 3.4.0. Earlier versions don't include it.

What It Is

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.

Setup for AI agents

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.

  1. Add the device server to the app module, next to the existing WormaCeptor dependencies:
    debugImplementation("com.azikar24.wormaceptor:wormaceptor-mcp-server:VERSION")
  2. 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:
    curl -fLO https://github.com/azikar24/WormaCeptor/releases/download/vVERSION/wormaceptor-mcp-bridge.jar
    curl -fLO https://github.com/azikar24/WormaCeptor/releases/download/vVERSION/wormaceptor-mcp-bridge.jar.sha256
    shasum -a 256 -c wormaceptor-mcp-bridge.jar.sha256
    claude mcp add --transport stdio wormaceptor -- java -jar /absolute/path/to/wormaceptor-mcp-bridge.jar
  3. 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>.
  4. With more than one device attached, append --device <serial> (from adb devices) to the command.
  5. Build and run the debug app, keep it in the foreground, then check with claude mcp get wormaceptor and call get_device_info.

Setup

1. Add the dependency

Add the device server next to your existing WormaCeptor dependencies:

build.gradle.kts
dependencies {
    debugImplementation("com.azikar24.wormaceptor:wormaceptor-mcp-server:3.4.0")
}

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.

2. Build the bridge

From a checkout of the WormaCeptor repository:

./gradlew :mcp:bridge:jar

This produces mcp/bridge/build/libs/bridge.jar.

3. Check the connection

# 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.

4. Register it with your agent

Claude Code:

claude mcp add --transport stdio wormaceptor -- java -jar /absolute/path/to/bridge.jar

Or share it with your team by committing a .mcp.json at the project root (Cursor reads the same shape from .cursor/mcp.json):

.mcp.json
{
  "mcpServers": {
    "wormaceptor": {
      "command": "java",
      "args": ["-jar", "/absolute/path/to/bridge.jar"]
    }
  }
}

With more than one device attached, add "--device", "<serial>" to args.

Configure the server (optional)

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:

Application.kt
WormaCeptorApi.configureMcpServer(
    McpConfig(
        port = 9000,                  // default: 8999
        enableAuth = true,            // default: false
        authToken = "my-debug-token", // required when auth enabled
        maxBodySize = 2_097_152L,     // default: 1 MB
    )
)
WormaCeptorApi.init(context = this)

When you change the port, pass the same value to the bridge with --port.

McpConfig(enabled = false) stops a running server and keeps it from auto-starting. To control it manually:

WormaCeptorApi.configureMcpServer(McpConfig(enabled = false))
WormaCeptorApi.init(context = this)

WormaCeptorApi.startMcpServer()
WormaCeptorApi.isMcpServerRunning() // true
WormaCeptorApi.stopMcpServer()

McpConfig Options

McpConfig fields (package com.azikar24.wormaceptor.domain.entities), passed to WormaCeptorApi.configureMcpServer:

FieldTypeDefaultDescription
portInt8999HTTP server port
enableAuthBooleanfalseEnable bearer token authentication
authTokenString?nullRequired token when auth is enabled
maxBodySizeLong1,048,576 (1 MB)Max characters of a body returned by the body endpoints
enabledBooleantrueAuto-start the server; false stops a running one

Tools (44 total)

Network (8 tools)

ToolParametersDescription
list_transactionsquery?, limit?, offset?List captured HTTP transactions, paginated (default 20). query is a case-insensitive substring match on URL, method, or status code
get_transactionidGet full details of a specific transaction
get_request_bodyidGet the request body of a transaction
get_response_bodyidGet the response body of a transaction
list_websocket_connections—List active WebSocket connections
list_websocket_messagesconnection_id?, limit?List WebSocket messages, optionally filtered by connection
get_rate_limit—Get current rate limiting configuration
set_rate_limitenabled?, preset?, download_kbps?, upload_kbps?, latency_ms?, packet_loss?Configure network throttling. Presets: WIFI, GOOD_3G, REGULAR_3G, SLOW_3G, GOOD_2G, SLOW_2G, EDGE, OFFLINE. enabled: false turns it off; packet_loss is a percentage (0-100)

Diagnostics (6 tools)

ToolParametersDescription
list_crasheslimit?, offset?List captured crash reports, paginated (default 20)
get_crashidGet full crash details with stack trace
tail_logslevel?, 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

Performance (5 tools)

ToolParametersDescription
get_cpu_statsinclude_history?Get CPU usage with optional history. When the monitor is off, returns a one-off sample and no history
get_memory_statsinclude_history?Get memory usage with optional history. When the monitor is off, returns a one-off sample and no history
get_fps_statsinclude_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_monitoringtarget, enabledStart or stop the cpu, memory or fps monitor. Stopping keeps a monitor running while the performance overlay shows it

Storage (8 tools)

ToolParametersDescription
list_preferences—List all SharedPreferences files and entries
list_databases—List all SQLite databases
query_databasedatabase, queryExecute a SELECT query (read-only, no DROP/DELETE/INSERT/UPDATE/ALTER)
list_filespath?Browse app file system directory
read_filepathRead a text file (JSON/XML pretty-printed). Binary files, images, and PDFs return a one-line summary
browse_secure_storagetype?List secure storage keys and metadata (ENCRYPTED_SHARED_PREFS, KEYSTORE, DATASTORE). Values are never returned
list_dependenciescategory?List detected app dependencies, optionally by category (NETWORKING, UI_FRAMEWORK, ...)
list_loaded_librariestype?, system?List loaded libraries. type: NATIVE_SO, DEX, JAR, AAR_RESOURCE; system: true/false

Actions (7 tools)

ToolParametersDescription
clear_transactions—Clear all captured network transactions
clear_crashes—Clear all crash reports
clear_logs—Empty the in-app log buffer; tail_logs then hides entries logged before the call (by host clock; logcat itself isn't cleared)
simulate_locationlatitude, 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_notificationtitle, body, channel_id?, priority?Send a simulated push notification
bring_app_to_frontpackage?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

Timeline and events (3 tools)

ToolParametersDescription
get_timelinesince_ms?, until_ms?, limit?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_transactionurl_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_crashtimeout_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

Mock rules (5 tools)

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).

ToolParametersDescription
list_mock_rules—List mock rules (pattern, match type, method, status, content type, body preview, delay, enabled) and whether mocking is on
create_mock_ruleurl_pattern, status, match_type?, method?, body?, content_type?, delay_ms?, enabled?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_transactionid, 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
set_mock_rule_enabledid, enabledTurn a rule on or off without deleting it
delete_mock_ruleidDelete a rule permanently

Reproduce (2 tools)

ToolParametersDescription
export_curlidBuild 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_transactionid, 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

Security

The MCP server is meant for debug builds only. Never ship it in a release build.

Authentication

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:

WormaCeptorApi.configureMcpServer(
    McpConfig(enableAuth = true, authToken = "your-secret-token"),
)

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.

Header Redaction

Sensitive HTTP headers are automatically redacted in API responses. The following headers have their values replaced with [REDACTED]:

  • Authorization
  • Cookie
  • Set-Cookie
  • Proxy-Authorization
  • X-Api-Key
  • X-Auth-Token

Input Validation

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

Network Exposure

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.

Bridge CLI Options

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.

Troubleshooting

Bridge can't find device

  • Run adb devices and confirm your device shows as device (not offline or unauthorized)
  • If multiple devices are connected, use --device <serial> or -s <serial>

Connection refused / timeout

  • Make sure the app is running in the foreground
  • Check logcat: adb logcat -s WormaCeptorMCP WormaCeptorServer
  • Verify port forwarding: adb forward tcp:8999 tcp:8999 && curl http://localhost:8999/api/health

Server not starting

  • Check that wormaceptor-mcp-server is included as debugImplementation
  • Look for WormaCeptorServer in logcat: a port clash or enableAuth without a token is logged there
  • Tools answering "engine not available" mean WormaCeptorApi.init() hasn't run yet

simulate_location says mock locations aren't enabled

  • Select the app under Developer options > Select mock location app, or run adb shell appops set <package> android:mock_location allow

Auth failures

  • Ensure McpConfig(enableAuth = true, authToken = ...) is set on the device side
  • Pass the same token with --token on the bridge side
  • The health endpoint (/api/health) always bypasses auth

Bridge reconnection exhausted

  • A failing tool call retries the port forward 4 times, then returns the connection error
  • The bridge keeps running; the next tool call tries again, so reconnecting the device or relaunching the app is enough

Further Reading

The MCP reference on GitHub covers the device server's REST endpoints, the /api/stream WebSocket, and the module layout.

On this page