# jadx-mcp MCP (Model Context Protocol) server as a jadx-gui plugin. Lets an AI client (Claude Code, Claude Desktop, or any MCP client) analyze the app currently loaded in jadx-gui: list/search classes, fetch decompiled Java or smali, per-method source, manifest components (main activity, application package), resources and `strings.xml`, cross-references (class/method/field level), and renames (class/method/field/package/variable) for deobfuscation loops. Native implementation on the official [MCP Java SDK](https://java.sdk.modelcontextprotocol.io/latest/) - no Python shim, no external process. Server runs inside jadx-gui over streamable HTTP; host and port are configurable in the plugin's settings dialog (`127.0.0.1:8090` by default). ## Requirements - jadx-gui **1.5.6** (plugin API target). - **jadx-gui running on Java 17+** (the MCP SDK and the plugin jar are Java 17 bytecode). The `jadx-gui-x.y.z-with-jre-win` bundle ships a recent JRE and works out of the box. If you launch `jadx-gui.bat` with a system JDK, check `java -version` first: on an older JVM jadx cannot load the plugin (`UnsupportedClassVersionError` in the jadx log). - To build: JDK 17+ and Gradle (or use the Gradle wrapper). ### Launching the right JVM (Windows) - `jadx-gui.exe` (with-JRE bundle) uses its bundled JRE - always Java 17+, use this. - `jadx-gui.bat` uses the system `JAVA_HOME`. If yours is older than 17, point it at a recent JDK first: ```powershell $env:JAVA_HOME = "" jadx-gui.bat ``` The plugin is Java 17 bytecode by design: the official MCP Java SDK (2.x) is records-based and requires Java 17+, and mixing bytecode levels caused classloader issues in jadx's plugin loader. Keep jadx-gui on a JVM >= 17. ## Build Prerequisites: JDK 17+ (toolchain pinned to 21, any newer JDK works) and Gradle 9.x on the PATH. jadx-core and slf4j are `compileOnly` - no jadx install needed to build. PowerShell: ```powershell cd jadx-mcp # if your default JAVA_HOME is not a JDK 17+, point Gradle at one: $env:JAVA_HOME = "" gradle jar # fat jar -> build/libs/jadx-mcp-0.1.0.jar gradle smoke # headless e2e check: loads a real jar into jadx, starts the server on an # ephemeral port, runs raw JSON-RPC calls (initialize, tools/list for all # 27 tools, status, get_all_classes, get_class_source, get_method_by_name, # xrefs, renames, non-APK error paths) and stops it; prints "SMOKE OK" ``` What the jar contains: - Own classes (`jadxmcp/*`), compiled with `--release 17`. - MCP Java SDK 2.0.1 + Jackson 3 + Reactor + embedded Jetty 12 (ee11, servlet 6.1), unpacked into one fat jar (no shadow plugin; signatures and `module-info.class` stripped). Jetty instead of Tomcat because the jadx with-JRE bundle ships a stripped jlink runtime without `java.management`, which Tomcat requires. - `META-INF/services/jadx.api.plugins.JadxPlugin` — the ServiceLoader entry jadx picks up. - Transitive `org.slf4j:slf4j-api` is excluded so jadx's own slf4j binding wins. Build layout: ``` src/main/java/jadxmcp/ JadxMcpPlugin (entry), McpServerManager (Jetty + MCP lifecycle), JadxTools (all MCP tools), Settings (host+port persistence) src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin src/test/java/jadxmcp/ SmokeTest (run by gradle smoke) ``` Gradle details worth knowing if you touch the build: - `gradle clean jar smoke` for a from-scratch verify. - Toolchain comes from `java.toolchain` in `build.gradle.kts` — Gradle auto-downloads JDK 21 if `JAVA_HOME` doesn't provide it; setting `JAVA_HOME` just makes it instant. - To bump versions: `val jadxVersion` (plugin API target) and the `mcp` / `jetty-ee11-servlet` coordinates in `build.gradle.kts`. `requiredJadxVersion` in `JadxMcpPlugin` must match the jadx line you target. - `gradle smokeLimited` runs the same smoke test restricted to the jadx with-JRE module set (no `java.management`, no `jdk.unsupported`) — use it after any dependency change to make sure the server still loads in the stripped runtime. ## Install ``` jadx plugins --install-jar jadx-mcp-0.1.0.jar ``` or copy the jar to `~/.jadx/plugins/` and restart jadx-gui. If an older version is installed, delete it first (same plugin id, duplicate jars in the plugins dir). ## Use 1. jadx-gui → open an APK/DEX/JAR. 2. Menu **Plugins → jadx-mcp: Settings...** opens the single settings dialog: - **Host** (default `127.0.0.1`) and **Port** (default `8090`) — saved to `~/.jadx/jadx-mcp.properties` on Start. - **Start** starts (or restarts with new host/port) the server; **Stop** stops it; the status line shows the running URL. No need to close the dialog: status updates live. - **Warning:** binding a non-loopback host (e.g. `0.0.0.0` or a LAN IP) exposes the server to the network — there is no authentication. 3. Register with your MCP client (URL from the status line). **Claude Code** (one-shot, no config editing): ``` claude mcp add --transport http jadx-mcp http://localhost:8090/mcp ``` With a non-default host/port, use the URL from the status dialog. Scope flag as needed: `--scope project` writes `.mcp.json` in the repo, default is user-local. **Claude Desktop** - edit `claude_desktop_config.json` (Settings → Developer → Edit Config) and add under `mcpServers`: ```json { "mcpServers": { "jadx-mcp": { "type": "http", "url": "http://localhost:8090/mcp" } } } ``` Restart Claude Desktop after editing. Both clients speak streamable HTTP, which is what the plugin serves. 4. Stop with the **Stop** button in the same dialog (server also stops on jadx exit). ### Smoke test (no client) PowerShell: ```powershell $h = @{ Accept = "application/json, text/event-stream" } $b = '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}' $r = Invoke-WebRequest -Method Post http://localhost:8090/mcp -ContentType "application/json" -Headers $h -Body $b $r.Headers["mcp-session-id"] # session id -> server works ``` ## Tools 27 tools, all validated against a JSON schema by the SDK. Paginated tools take optional `offset`/`limit` (default 50, max 500). | Tool | Notes | |---|---| | `status` | app loaded, class/package/resource counts, manifest package | | **Classes** | | | `get_all_classes` | paginated, optional package prefix filter | | `search_classes` | class name contains, case-insensitive | | `get_class_source` | decompiled Java source (original or alias name) | | `get_smali_of_class` | DEX disassembly | | `search_classes_by_keyword` | full-text over decompiled code (`class:line:source` matches), paginated; expensive on large apps | | `fetch_current_class` | class the analyst is viewing in jadx-gui (node under caret), with source | | **Methods / fields** | | | `get_method_by_name` | single method source; `signature` fragment disambiguates overloads | | `search_method_by_name` | method name contains across all classes, paginated | | `get_methods_of_class` | methods with flags, signatures, return types | | `get_fields_of_class` | fields with flags and types | | **Manifest (APK)** | | | `get_android_manifest` | decoded AndroidManifest.xml | | `get_manifest_component` | one component type (activity, activity-alias, service, receiver, provider), optional name filter | | `get_main_activity_class` | MAIN/LAUNCHER activity, with source | | `get_main_application_classes_names` | class names in the manifest application package, paginated | | `get_main_application_classes_code` | their full sources, paginated (default 10 per page — token heavy) | | **Resources** | | | `get_strings` | decoded `res/values/strings.xml` | | `get_all_resource_file_names` | paginated, optional path filter | | `get_resource_file` | decoded content of one resource | | **Renames (mutate the jadx project)** | | | `rename_class` | short new name, GUI refresh included | | `rename_method` | `signature` fragment disambiguates overloads | | `rename_field` | | | `rename_package` | full new package name, cascades to all classes in it | | `rename_variable` | local var in a method; session-local (not persisted to saved jadx metadata) | | **Xrefs** | | | `xrefs_to_class` | all references to the class, paginated | | `xrefs_to_method` | references + override-related methods, paginated | | `xrefs_to_field` | methods accessing the field, paginated | Domain errors (class not found, no app loaded, binary resource, ambiguous overload) come back as `isError` tool results so the LLM can self-correct. ## Security - Binds `127.0.0.1` by default; Origin/Host allowlist is built from the configured host. - Origin/Host header validation (`http://localhost:*` / `localhost:*`) to block DNS-rebinding and cross-origin requests from web pages. - No auth: keep it on localhost. Do not port-forward. ## Known limits - **No `get_selected_text`** — the jadx plugin API (1.5.6 `JadxGuiContext`) exposes the node under the caret, not the raw text selection. `fetch_current_class` is the closest equivalent. - **No `debug_get_*` tools** (stack frames, threads, variables) — the plugin API has no debugger access. Use the jadx-gui debugger UI directly. - `rename_variable` is session-local: the new name shows in reloaded code but is not written to saved jadx metadata (`.jadx` files), unlike the class/method/field/package renames which go through the standard deobfuscation alias system. - Comments and headless (jadx-cli) operation are not implemented.