leakcanary-mcp-fix 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CLAUDE.md +111 -0
  2. package/README.md +239 -0
  3. package/RELEASE.md +48 -0
  4. package/bin/index.js +127 -0
  5. package/build.gradle.kts +39 -0
  6. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  7. package/gradle/wrapper/gradle-wrapper.properties +7 -0
  8. package/gradle.properties +2 -0
  9. package/gradlew +251 -0
  10. package/gradlew.bat +94 -0
  11. package/install.sh +48 -0
  12. package/package.json +29 -0
  13. package/scripts/leakcanary-mcp +91 -0
  14. package/settings.gradle.kts +1 -0
  15. package/src/main/kotlin/com/leakcanary/mcp/Main.kt +119 -0
  16. package/src/main/kotlin/com/leakcanary/mcp/adb/AdbExecutor.kt +200 -0
  17. package/src/main/kotlin/com/leakcanary/mcp/adb/AppStorageReader.kt +509 -0
  18. package/src/main/kotlin/com/leakcanary/mcp/analyzer/FixSuggester.kt +162 -0
  19. package/src/main/kotlin/com/leakcanary/mcp/analyzer/LeakAnalyzer.kt +149 -0
  20. package/src/main/kotlin/com/leakcanary/mcp/history/LeakHistoryStore.kt +139 -0
  21. package/src/main/kotlin/com/leakcanary/mcp/model/HeapSummary.kt +27 -0
  22. package/src/main/kotlin/com/leakcanary/mcp/model/LeakHistoryEntry.kt +20 -0
  23. package/src/main/kotlin/com/leakcanary/mcp/model/LeakNode.kt +25 -0
  24. package/src/main/kotlin/com/leakcanary/mcp/model/LeakReport.kt +16 -0
  25. package/src/main/kotlin/com/leakcanary/mcp/model/LeakSummary.kt +17 -0
  26. package/src/main/kotlin/com/leakcanary/mcp/model/LeakTrace.kt +19 -0
  27. package/src/main/kotlin/com/leakcanary/mcp/parser/HeapSummaryParser.kt +140 -0
  28. package/src/main/kotlin/com/leakcanary/mcp/parser/LeakTraceParser.kt +253 -0
  29. package/src/main/kotlin/com/leakcanary/mcp/tools/AnalyzeLeakTool.kt +128 -0
  30. package/src/main/kotlin/com/leakcanary/mcp/tools/ClearLeaksTool.kt +49 -0
  31. package/src/main/kotlin/com/leakcanary/mcp/tools/DeduplicateLeaks.kt +36 -0
  32. package/src/main/kotlin/com/leakcanary/mcp/tools/DetectLeaksTool.kt +139 -0
  33. package/src/main/kotlin/com/leakcanary/mcp/tools/DeviceMemoryTool.kt +222 -0
  34. package/src/main/kotlin/com/leakcanary/mcp/tools/ExportReportTool.kt +234 -0
  35. package/src/main/kotlin/com/leakcanary/mcp/tools/HeapSummaryTool.kt +59 -0
  36. package/src/main/kotlin/com/leakcanary/mcp/tools/LeakDiffTool.kt +173 -0
  37. package/src/main/kotlin/com/leakcanary/mcp/tools/LeakHistoryTool.kt +60 -0
  38. package/src/main/kotlin/com/leakcanary/mcp/tools/LeakSource.kt +161 -0
  39. package/src/main/kotlin/com/leakcanary/mcp/tools/ListDevicesTool.kt +84 -0
  40. package/src/main/kotlin/com/leakcanary/mcp/tools/ListLeaksTool.kt +117 -0
  41. package/src/main/kotlin/com/leakcanary/mcp/tools/PackageFilter.kt +105 -0
  42. package/src/main/kotlin/com/leakcanary/mcp/tools/SearchLeakTool.kt +174 -0
  43. package/src/main/kotlin/com/leakcanary/mcp/tools/SuggestFixesTool.kt +110 -0
package/CLAUDE.md ADDED
@@ -0,0 +1,111 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ LeakCanary MCP Server is a Kotlin/JVM MCP (Model Context Protocol) server that automates Android memory leak detection. It reads LeakCanary output from `adb logcat` (primary) or the app's stored LeakCanary database (fallback), parses leak traces, classifies root causes, assigns priority, suggests fixes, and tracks leak history. Communication with AI clients (Cursor, VS Code, Android Studio) is via STDIO JSON-RPC.
8
+
9
+ ## Build & Run Commands
10
+
11
+ ```bash
12
+ # Build the fat JAR (includes all dependencies)
13
+ ./gradlew shadowJar
14
+ # Output: build/libs/leakcanary-mcp-server-all.jar
15
+
16
+ # Run the server directly
17
+ java -jar build/libs/leakcanary-mcp-server-all.jar
18
+
19
+ # Build only (compile check)
20
+ ./gradlew build
21
+
22
+ # Clean build
23
+ ./gradlew clean shadowJar
24
+ ```
25
+
26
+ There are no tests in this project currently.
27
+
28
+ ## Architecture
29
+
30
+ **Entry point:** `Main.kt` creates a `Server`, registers 12 tools, and starts a `StdioServerTransport`.
31
+
32
+ **Source root:** `src/main/kotlin/com/leakcanary/mcp/`
33
+
34
+ **Data flow:** ADB (logcat or app storage) -> Parser -> Analyzer -> Tool response (JSON)
35
+
36
+ ### Key Layers
37
+
38
+ - **`adb/AdbExecutor`** — Singleton that shells out to `adb` via `ProcessBuilder`. Auto-resolves adb path from `ANDROID_HOME`, `ANDROID_SDK_ROOT`, common SDK locations, or PATH. Provides `captureLeakLogs()`, `captureFullLogs()`, `listDevices()`, `clearLogcat()`, and `runShellCommand()`.
39
+
40
+ - **`adb/AppStorageReader`** — Reads LeakCanary's persisted leak data directly from the app's internal SQLite database (`databases/leaks.db` or `databases/leakcanary`). Pulls the DB to the host via `adb shell run-as <package> cat databases/<db>`. Primary method: deserializes the `HeapAnalysisSuccess` Java objects stored as BLOBs in the `heap_analysis.object` column using shark library — this provides full reference chains, retained sizes, leak status per node, GC roots, and heap metadata. Falls back to SQL-based queries if BLOB deserialization fails. Also provides `discoverLeakCanaryApps()` to scan all third-party packages for LeakCanary databases. Returns `StoredLeakResult` with traces and optional `HeapSummary`. Requires debuggable app.
41
+
42
+ - **`parser/LeakTraceParser`** — State-machine parser for LeakCanary's ASCII box-drawing format (unicode chars: `┬───`, `├─`, `│ ↓`, `╰→`). Extracts `LeakTrace` objects with signature, GC root, reference chain nodes, retained size, and leak status.
43
+
44
+ - **`parser/HeapSummaryParser`** — Parses the METADATA section from logcat for heap stats (class count, instance count, bitmap info, device info).
45
+
46
+ - **`analyzer/LeakAnalyzer`** — Classifies leaks by pattern (SINGLETON_LEAK, LISTENER_LEAK, CONTEXT_LEAK, LIBRARY_LEAK, VIEWMODEL_LEAK, HANDLER_LEAK) using heuristics on the reference chain text. Assigns priority: P0 (>20MB), P1 (>5MB), P2 (rest).
47
+
48
+ - **`analyzer/FixSuggester`** — Generates classification-specific fix suggestions (e.g., singleton -> use applicationContext, listener -> unregister in onDestroy).
49
+
50
+ - **`history/LeakHistoryStore`** — Persists leak records to `~/.leakcanary-mcp/history.json`. Tracks first/last seen timestamps and occurrence count per signature.
51
+
52
+ - **`tools/*`** — Each file registers one MCP tool on the `Server` via extension function `Server.register*Tool()`. 12 tools total: `detect_leaks`, `list_leaks`, `search_leak`, `get_heap_summary`, `get_leak_history`, `analyze_leak`, `suggest_fixes`, `clear_leaks`, `list_devices`, `leak_diff`, `get_device_memory`, `export_report`.
53
+
54
+ - **`tools/LeakSource.kt`** — Three-tier fetch strategy used by most tools. Tries logcat first; if empty and `package_name` provided, reads from app storage; if empty and no `package_name`, auto-discovers LeakCanary apps on the device. Returns a `LeakFetchResult` with the data source indicator and any discovered apps.
55
+
56
+ - **`tools/PackageFilter.kt`** — Multi-app support utilities. Extracts app package names from leak traces, detects when multiple apps have leaks, filters traces by package, and builds a user-facing prompt listing detected apps. `filterByPackageIfNeeded()` skips filtering when data comes from app storage (since class names are simple, not fully qualified).
57
+
58
+ - **`tools/DeduplicateLeaks.kt`** — Groups duplicate leak traces by signature, keeps the richest trace as representative, and tracks occurrence count within a single scan.
59
+
60
+ - **`model/*`** — `@Serializable` data classes: `LeakTrace`, `LeakNode`, `LeakStatus`, `HeapSummary`, `LeakReport`, `LeakSummary`, `LeakHistoryEntry`.
61
+
62
+ ### Adding a New MCP Tool
63
+
64
+ 1. Create `tools/NewTool.kt` with a `Server.registerNewTool()` extension function
65
+ 2. Use `addTool(name, description, inputSchema) { request -> ... }` to define the tool
66
+ 3. Register it in `Main.kt` by calling `server.registerNewTool()`
67
+ 4. Use `fetchLeakTraces()` from `LeakSource.kt` for the two-tier data strategy
68
+ 5. Use `deduplicateTraces()` if the tool lists multiple leaks
69
+
70
+ ### Database-First Data Strategy
71
+
72
+ Tools use `fetchLeakTraces()` from `LeakSource.kt` which:
73
+
74
+ 1. **App database (primary):** `AppStorageReader.readStoredLeaks()` — pulls the app's LeakCanary SQLite DB to the host and deserializes `HeapAnalysisSuccess` BLOBs via shark library. Provides full reference chains, retained sizes, leak status per node, GC roots, heap metadata. Returns `StoredLeakResult` with traces and `HeapSummary`.
75
+ 2. **Logcat (fallback):** `AdbExecutor.captureLeakLogs()` — used when DB is not accessible (app not debuggable, package unknown)
76
+ 3. **Auto-discovery:** `AppStorageReader.discoverLeakCanaryApps()` — scans all third-party packages when no `package_name` is provided. If one app is found, reads its leaks directly. If multiple are found, returns `discoveredApps` so the tool can prompt the user to choose.
77
+
78
+ The BLOB deserialization falls back to SQL-based queries (limited data, no reference chains) if deserialization fails, and then to logcat. Responses include a `[Data source: ...]` note when data comes from app storage.
79
+
80
+ ### Multi-App Support
81
+
82
+ When a device has multiple LeakCanary-enabled apps, `adb logcat -s LeakCanary` returns leaks from all apps mixed together. The server handles this via `tools/PackageFilter.kt`:
83
+
84
+ - **Detection:** `detectAppPackages()` extracts unique app packages from leaking class names.
85
+ - **Prompting:** If multiple packages found and no `package_name` provided, `buildMultiAppPrompt()` lists detected apps.
86
+ - **Filtering:** `filterByPackage()` keeps only traces matching the given package.
87
+ - **Scope:** 8 of 12 tools support this: `detect_leaks`, `list_leaks`, `search_leak`, `analyze_leak`, `suggest_fixes`, `leak_diff`, `export_report`, and indirectly `get_device_memory` (requires `package_name`).
88
+
89
+ ### Important Constraints
90
+
91
+ - **No stdout for debugging.** The server uses STDIO transport (stdin/stdout for JSON-RPC). Any `println()` or `System.out` usage will corrupt the MCP protocol. Use `System.err` for debug logging if needed.
92
+ - **All model classes must be `@Serializable`** (kotlinx-serialization). The `@Serializable` annotation and `kotlinx.serialization.json.Json` are used throughout for encoding tool responses.
93
+ - **ADB must be available** at runtime. The server resolves the `adb` binary path lazily on first use and caches it.
94
+ - **App storage fallback requires debuggable apps.** `run-as` only works on debug builds. Release builds cannot be read this way.
95
+ - **App storage class names are simple, not fully qualified.** When reading from the LeakCanary database, `class_simple_name` is used (e.g., `HomeActivity` not `com.myapp.ui.HomeActivity`). Package filtering must be skipped for app storage data — this is handled by `filterByPackageIfNeeded()` in `PackageFilter.kt`.
96
+
97
+ ## Tech Stack
98
+
99
+ - Kotlin 2.2 / JVM 17
100
+ - Gradle Kotlin DSL with Shadow plugin (fat JAR)
101
+ - MCP Kotlin SDK 0.8.3 (`io.modelcontextprotocol:kotlin-sdk`)
102
+ - shark 2.14 (`com.squareup.leakcanary:shark`) for BLOB deserialization
103
+ - kotlinx-serialization-json for all JSON encoding
104
+ - Ktor server (CIO) as transitive dependency of MCP SDK
105
+ - STDIO transport (stdin/stdout JSON-RPC)
106
+
107
+ ## Prerequisites
108
+
109
+ - Java 17+
110
+ - `adb` installed (Android SDK Platform-Tools)
111
+ - Connected Android device/emulator with LeakCanary in the debug build
package/README.md ADDED
@@ -0,0 +1,239 @@
1
+ # LeakCanary MCP Server
2
+
3
+ A standalone **Kotlin/JVM MCP server** that automates Android memory leak detection using [LeakCanary](https://square.github.io/leakcanary/) and `adb logcat`. It parses leak traces, classifies root causes, assigns priority levels, suggests fixes, and tracks leak history across sessions -- all exposed as MCP tools for use in **Cursor**, **VS Code**, and **Android Studio**.
4
+
5
+ ---
6
+
7
+ ## Prerequisites
8
+
9
+ - **Java 17+** installed and available on PATH
10
+ - **adb** installed (Android SDK Platform-Tools) -- the server auto-detects `adb` from `ANDROID_HOME`, `ANDROID_SDK_ROOT`, or common SDK install locations
11
+ - An Android device or emulator connected via USB/WiFi with **LeakCanary** integrated in the debug build
12
+
13
+ ---
14
+
15
+ **Repository Link**: [https://github.com/ravisharma46/Leak-Canary-Mcp-Server.git](https://github.com/ravisharma46/Leak-Canary-Mcp-Server.git)
16
+
17
+ ## Quick Install (Recommended)
18
+
19
+ The fastest and most universally compatible way to use the MCP server across **Windows, macOS, and Linux**. It requires zero path configuration.
20
+
21
+ Add this directly to your **`~/.cursor/mcp.json`**:
22
+
23
+ ```json
24
+ {
25
+ "mcpServers": {
26
+ "leakcanary": {
27
+ "command": "npx",
28
+ "args": ["-y", "leakcanary-mcp-fix"]
29
+ }
30
+ }
31
+ }
32
+ ```
33
+
34
+ Restart Cursor and the 12 tools will appear in the MCP tools panel.
35
+
36
+ ---
37
+
38
+ ## Setup in VS Code
39
+
40
+ Make sure you have [GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot) extension installed with **Agent mode** enabled (VS Code 1.99+).
41
+
42
+ **Option A — Workspace level** (`.vscode/mcp.json` in your project root):
43
+
44
+ ```json
45
+ {
46
+ "servers": {
47
+ "leakcanary": {
48
+ "type": "stdio",
49
+ "command": "npx",
50
+ "args": ["-y", "leakcanary-mcp-fix"]
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ **Option B — User level** (VS Code `settings.json`):
57
+
58
+ ```json
59
+ {
60
+ "mcp": {
61
+ "servers": {
62
+ "leakcanary": {
63
+ "type": "stdio",
64
+ "command": "npx",
65
+ "args": ["-y", "leakcanary-mcp-fix"]
66
+ }
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ Open **Copilot Chat → Agent mode** and the LeakCanary tools will be available.
73
+
74
+ ---
75
+
76
+ ## Setup in Android Studio
77
+
78
+ Android Studio supports MCP servers through the **Gemini** plugin (Android Studio Meerkat 2024.3+ or later).
79
+
80
+ 1. Open **Settings → Tools → AI Assistant → MCP Servers**
81
+ 2. Click **+ Add** and configure:
82
+
83
+ ```json
84
+ {
85
+ "mcpServers": {
86
+ "leakcanary": {
87
+ "command": "npx",
88
+ "args": ["-y", "leakcanary-mcp-fix"]
89
+ }
90
+ }
91
+ }
92
+ ```
93
+
94
+ Alternatively, add a `mcp.json` file in the `.idea` folder of your project:
95
+
96
+ ```json
97
+ {
98
+ "mcpServers": {
99
+ "leakcanary": {
100
+ "command": "npx",
101
+ "args": ["-y", "leakcanary-mcp-fix"]
102
+ }
103
+ }
104
+ }
105
+ ```
106
+
107
+ Restart Android Studio and the tools will appear under **Gemini → MCP Tools**.
108
+
109
+ > **Note:** All setups use `npx` to dynamically execute the wrapper. The wrapper auto-downloads and updates the server JAR for you on each run.
110
+
111
+ ---
112
+
113
+ ## Database-First Data Strategy
114
+
115
+ The server reads leak data **directly from the app's LeakCanary SQLite database** as the primary source, not from `adb logcat`. This means leak data is always available -- even after reboots, logcat buffer clears, or the next day.
116
+
117
+ ### How it works
118
+
119
+ 1. **Primary: App Database (BLOB deserialization)** -- Pulls the app's `leaks.db` to the host and deserializes the `HeapAnalysisSuccess` Java objects stored as BLOBs using the [shark](https://github.com/square/leakcanary/tree/main/shark) library. This provides the **full analysis** -- complete reference chains, retained sizes, leak status per node, GC roots, heap metadata, and device info. Identical quality to what you see in the LeakCanary app.
120
+ 2. **Fallback: Logcat** -- If the database isn't accessible (app not debuggable, not installed, etc.), falls back to reading `adb logcat -s LeakCanary`.
121
+ 3. **Auto-Discovery** -- When **no `package_name` is provided**, the server automatically scans all installed apps on the device to find ones with a LeakCanary database. If multiple apps are found, it prompts you to choose. If only one app is found, it reads leaks directly.
122
+
123
+ This means you can just say **"list all leaks"** without knowing any package names -- the server will find your LeakCanary-enabled apps and show you what's available.
124
+
125
+ ---
126
+
127
+ ## MCP Tools (12 total)
128
+
129
+ ### Leak Detection & Analysis
130
+
131
+ | Tool | Description |
132
+ |------|-------------|
133
+ | `detect_leaks` | Capture LeakCanary logs, parse all leak traces, classify/prioritize, and return a full structured report. Falls back to app storage when logcat is empty. |
134
+ | `list_leaks` | List all detected leaks in a deduplicated compact summary with occurrence counts. Falls back to app storage when logcat is empty. |
135
+ | `search_leak` | Search for leaks by class name or keyword (e.g. "SearchHistoryManager"). Falls back to app storage when logcat is empty. |
136
+ | `analyze_leak` | Deep-dive a specific leak by its signature hash -- full reference chain, root cause, severity. Falls back to app storage when logcat is empty. |
137
+ | `suggest_fixes` | Generate actionable fix steps for a leak based on its pattern (singleton, listener, context, etc.). Falls back to app storage when logcat is empty. |
138
+
139
+ ### Memory & Device Info
140
+
141
+ | Tool | Description |
142
+ |------|-------------|
143
+ | `get_heap_summary` | Extract heap-level statistics (class count, instance count, heap size, bitmap stats, device info) |
144
+ | `get_device_memory` | Get live memory stats for an app (PSS, Java Heap, Native Heap, Code, Stack, Graphics) via `dumpsys meminfo` |
145
+ | `list_devices` | List all connected Android devices and emulators with their connection state |
146
+
147
+ ### History, Diff & Reporting
148
+
149
+ | Tool | Description |
150
+ |------|-------------|
151
+ | `get_leak_history` | Query persisted leak records across sessions with optional filters (signature, date, limit) |
152
+ | `leak_diff` | Compare current leaks against previous scans -- shows NEW, RECURRING, and RESOLVED leaks |
153
+ | `export_report` | Generate a shareable Markdown leak report with priority summary, reference chains, and history |
154
+ | `clear_leaks` | Clear the logcat buffer so the next scan only shows newly detected leaks |
155
+
156
+ ---
157
+
158
+ ## Usage Examples
159
+
160
+ Once the MCP server is active, you can ask the AI agent in natural language:
161
+
162
+ - **"Detect memory leaks on my connected device"** -- runs `detect_leaks`, returns a full prioritized report
163
+ - **"List all memory leaks"** -- runs `list_leaks`, returns a deduplicated summary with occurrence counts
164
+ - **"Check memory leak in SearchHistoryManager"** -- runs `search_leak`, filters leaks matching the class name
165
+ - **"Show me the heap summary"** -- runs `get_heap_summary`, returns heap stats
166
+ - **"How much memory is my app using?"** -- runs `get_device_memory`, returns live PSS/heap stats
167
+ - **"Analyze the leak with signature 42ac34ed..."** -- runs `analyze_leak`, returns a deep-dive of that specific leak
168
+ - **"How do I fix the leak with signature 48c887..."** -- runs `suggest_fixes`, returns actionable fix steps
169
+ - **"Show me leak history from the last week"** -- runs `get_leak_history` with a date filter
170
+ - **"What leaks are new since last scan?"** -- runs `leak_diff`, shows new/recurring/resolved leaks
171
+ - **"Export a leak report"** -- runs `export_report`, generates a Markdown file for sharing
172
+ - **"Clear leaks and start fresh"** -- runs `clear_leaks`, resets the logcat buffer
173
+ - **"What devices are connected?"** -- runs `list_devices`, shows all adb devices
174
+
175
+ ### Auto-discovery & multi-app support
176
+
177
+ When you ask for leaks **without specifying an app**, the server automatically discovers all debuggable apps on the device that have a LeakCanary database:
178
+
179
+ ```
180
+ No LeakCanary output found in logcat, but I found apps with LeakCanary
181
+ databases on the device.
182
+
183
+ Please specify which app to analyze by providing the 'package_name' parameter:
184
+ 1. com.myapp.debug (15 leaks)
185
+ 2. com.otherapp.debug (2 leaks)
186
+ 3. com.myapp.man.preprod (2 leaks)
187
+
188
+ Example: use package_name="com.myapp.debug" to see its leaks.
189
+ ```
190
+
191
+ You can then specify the app:
192
+ - **"List leaks for com.myapp.debug"** -- reads stored leaks from that app's database
193
+ - **"Detect leaks for com.otherapp.debug"** -- analyzes leaks for the other app
194
+
195
+ If only **one** app has LeakCanary on the device, the server skips the prompt and shows leaks directly -- no extra parameters needed.
196
+
197
+ ### Persistent leak reading
198
+
199
+ The logcat buffer clears over time (reboot, buffer rotation, next day). The server handles this transparently:
200
+
201
+ - **With `package_name`:** Reads leaks directly from the app's LeakCanary SQLite database
202
+ - **Without `package_name`:** Auto-discovers apps with LeakCanary and prompts you to choose
203
+
204
+ The database is pulled to the host and queried locally with `sqlite3`, so it works even on devices that don't have `sqlite3` installed. Requires the app to be a debug build (for `run-as` access).
205
+
206
+ ### Multi-device support
207
+
208
+ If you have multiple devices connected, pass the `device_id` parameter:
209
+ - **"Detect leaks on device emulator-5554"** -- the tool will use `adb -s emulator-5554`
210
+ - **"What devices are connected?"** -- runs `list_devices` to see all available device IDs
211
+
212
+ ---
213
+
214
+ ## Tech Stack
215
+
216
+ | Component | Technology |
217
+ |-----------|-----------|
218
+ | Language | Kotlin 2.2 / JVM 17 |
219
+ | MCP SDK | [io.modelcontextprotocol:kotlin-sdk:0.8.3](https://github.com/modelcontextprotocol/kotlin-sdk) |
220
+ | Leak Analysis | [shark:2.14](https://github.com/square/leakcanary/tree/main/shark) (LeakCanary's heap analysis library) |
221
+ | Serialization | kotlinx-serialization-json |
222
+ | Transport | STDIO (stdin/stdout JSON-RPC) |
223
+ | Build | Gradle Kotlin DSL + Shadow plugin (fat JAR) |
224
+ | History storage | Local JSON file (`~/.leakcanary-mcp/history.json`) |
225
+ | Report export | Markdown files (`~/.leakcanary-mcp/reports/`) |
226
+
227
+ ---
228
+
229
+ ## License
230
+
231
+ This project is for internal use at Todo App.
232
+
233
+ ---
234
+
235
+ ## Author
236
+
237
+ Developed by **Ravi Kumar**
238
+
239
+ 🙏 If you like LeakCanary MCP Server you can show support by starring ⭐ this repository.
package/RELEASE.md ADDED
@@ -0,0 +1,48 @@
1
+ # Release Instructions
2
+
3
+ How to publish a new version of the LeakCanary MCP Server. Developers using the launcher script will auto-update on their next run.
4
+
5
+ ---
6
+
7
+ ## 1. Build the fat JAR
8
+
9
+ ```bash
10
+ ./gradlew shadowJar
11
+ ```
12
+
13
+ This produces:
14
+ ```
15
+ build/libs/leakcanary-mcp-server-all.jar
16
+ ```
17
+
18
+ ## 2. Create a GitHub release
19
+
20
+ Choose a semantic version tag (e.g. `v1.0.0`, `v1.1.0`, `v2.0.0`):
21
+
22
+ ```bash
23
+ gh release create vX.X.X \
24
+ --repo ravisharma46/Leak-Canary-Mcp-Server \
25
+ --title "vX.X.X" \
26
+ --notes "Release notes here" \
27
+ build/libs/leakcanary-mcp-server-all.jar
28
+ ```
29
+
30
+ This creates the release and uploads the JAR as a release asset in one step.
31
+
32
+ ## 3. Publish to NPM (Optional but Recommended)
33
+
34
+ For users to get the latest version via the `npx` wrapper, update the `package.json` version to match your new GitHub release, then publish to NPM:
35
+
36
+ ```bash
37
+ # Update version in package.json
38
+ npm version patch # or minor, or major
39
+
40
+ # Publish to NPM registry
41
+ npm publish --access public
42
+ ```
43
+
44
+ ---
45
+
46
+ ## How auto-update works
47
+
48
+ The Node.js wrapper (`bin/index.js` published to NPM) polls the GitHub releases API on each run. If the GitHub tag has changed since the last download, it pulls the new JAR automatically entirely independently from NPM versioning. (Meaning even if you don't publish to NPM, the wrapper evaluates the latest GitHub release anyway).
package/bin/index.js ADDED
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+ const { spawn, execSync } = require('child_process');
6
+ const https = require('https');
7
+
8
+ const REPO = "ravisharma46/Leak-Canary-Mcp-Server";
9
+ const ASSET_NAME = "leakcanary-mcp-server-all.jar";
10
+ const INSTALL_DIR = path.join(os.homedir(), '.leakcanary-mcp');
11
+ const JAR_PATH = path.join(INSTALL_DIR, 'server.jar');
12
+ const VERSION_FILE = path.join(INSTALL_DIR, 'version');
13
+
14
+ // Helpers for logging specifically to stderr, to keep stdout entirely clean for JSON-RPC MCP
15
+ function logError(msg) { console.error(`\x1b[31mERROR: ${msg}\x1b[0m`); }
16
+ function logWarn(msg) { console.error(`\x1b[33mWARN: ${msg}\x1b[0m`); }
17
+ function logInfo(msg) { console.error(`\x1b[32mINFO: ${msg}\x1b[0m`); }
18
+
19
+ // 1. Initial Checks
20
+ try {
21
+ execSync('java -version', { stdio: 'ignore' });
22
+ } catch (e) {
23
+ logError("Java not found. Please install Java 17+ to use this MCP server.");
24
+ process.exit(1);
25
+ }
26
+
27
+ try {
28
+ execSync('adb version', { stdio: 'ignore' });
29
+ } catch (e) {
30
+ logWarn("adb not found. Leak detection requires a connected Android device with adb.");
31
+ }
32
+
33
+ if (!fs.existsSync(INSTALL_DIR)) {
34
+ fs.mkdirSync(INSTALL_DIR, { recursive: true });
35
+ }
36
+
37
+ // 2. Fetch and Download Engine
38
+ function fetchJson(url) {
39
+ return new Promise((resolve, reject) => {
40
+ const req = https.get(url, { headers: { 'User-Agent': 'NodeJS LeakCanary-MCP Installer' } }, (res) => {
41
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
42
+ return resolve(fetchJson(res.headers.location));
43
+ }
44
+ let data = '';
45
+ res.on('data', chunk => data += chunk);
46
+ res.on('end', () => resolve(JSON.parse(data)));
47
+ });
48
+ req.on('error', reject);
49
+ });
50
+ }
51
+
52
+ function downloadToFile(url, dest) {
53
+ return new Promise((resolve, reject) => {
54
+ const req = https.get(url, { headers: { 'User-Agent': 'NodeJS LeakCanary-MCP Installer', 'Accept': 'application/octet-stream' } }, (res) => {
55
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
56
+ return resolve(downloadToFile(res.headers.location, dest)); // follow redirect
57
+ }
58
+ if (res.statusCode !== 200) {
59
+ return reject(new Error(`Download failed with status ${res.statusCode}`));
60
+ }
61
+ const file = fs.createWriteStream(dest);
62
+ res.pipe(file);
63
+ file.on('finish', () => { file.close(resolve); });
64
+ });
65
+ req.on('error', reject);
66
+ });
67
+ }
68
+
69
+ async function downloadBinary() {
70
+ try {
71
+ const release = await fetchJson(`https://api.github.com/repos/${REPO}/releases/latest`);
72
+ if (!release.tag_name) throw new Error("Could not fetch latest release format.");
73
+
74
+ const latestVersion = release.tag_name;
75
+
76
+ let currentVersion = "";
77
+ if (fs.existsSync(VERSION_FILE)) {
78
+ currentVersion = fs.readFileSync(VERSION_FILE, 'utf8').trim();
79
+ }
80
+
81
+ if (currentVersion !== latestVersion || !fs.existsSync(JAR_PATH)) {
82
+ logInfo(currentVersion ? `Updating from ${currentVersion} to ${latestVersion} ...` : `First run — downloading LeakCanary MCP Server ${latestVersion} ...`);
83
+
84
+ const asset = release.assets.find(a => a.name === ASSET_NAME);
85
+ if (!asset) throw new Error(`Could not find ${ASSET_NAME} in release ${latestVersion}`);
86
+
87
+ logInfo(`Downloading ${ASSET_NAME} ...`);
88
+
89
+ const tempFile = path.join(INSTALL_DIR, `temp-${ASSET_NAME}`);
90
+ await downloadToFile(asset.browser_download_url, tempFile);
91
+
92
+ // Atomically replace JAR
93
+ fs.renameSync(tempFile, JAR_PATH);
94
+ fs.writeFileSync(VERSION_FILE, latestVersion);
95
+ logInfo(`Downloaded ${latestVersion} successfully.`);
96
+ }
97
+ } catch (err) {
98
+ if (!fs.existsSync(JAR_PATH)) {
99
+ logError(`Failed to fetch and no cached JAR exists. ${err.message}`);
100
+ process.exit(1);
101
+ } else {
102
+ // It's perfectly fine if offline or ratelimited, just use the cached fallback
103
+ logWarn(`Update check failed. Using cached JAR. (${err.message})`);
104
+ }
105
+ }
106
+ }
107
+
108
+ async function startServer() {
109
+ await downloadBinary();
110
+ logInfo("LeakCanary MCP ready! Starting JSON-RPC transport...");
111
+
112
+ // Spawn the loaded jar, inheriting only the necessary streams
113
+ const child = spawn('java', ['-jar', JAR_PATH], {
114
+ stdio: 'inherit',
115
+ env: process.env
116
+ });
117
+
118
+ child.on('error', (err) => {
119
+ logError(`Failed to start Java process: ${err.message}`);
120
+ process.exit(1);
121
+ });
122
+ child.on('exit', (code) => {
123
+ process.exit(code || 0);
124
+ });
125
+ }
126
+
127
+ startServer();
@@ -0,0 +1,39 @@
1
+ plugins {
2
+ kotlin("jvm") version "2.2.0"
3
+ kotlin("plugin.serialization") version "2.2.0"
4
+ id("com.gradleup.shadow") version "9.0.0-beta4"
5
+ application
6
+ }
7
+
8
+ group = "com.leakcanary.mcp"
9
+ version = "1.0.0"
10
+
11
+ repositories {
12
+ mavenCentral()
13
+ }
14
+
15
+ dependencies {
16
+ implementation("io.modelcontextprotocol:kotlin-sdk:0.12.0")
17
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
18
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
19
+ implementation("io.ktor:ktor-server-core:3.0.3")
20
+ implementation("io.ktor:ktor-server-cio:3.0.3")
21
+ implementation("org.slf4j:slf4j-nop:2.0.16")
22
+ // LeakCanary's shark library for deserializing heap analysis BLOBs from the app's database
23
+ implementation("com.squareup.leakcanary:shark:2.14")
24
+ }
25
+
26
+ application {
27
+ mainClass.set("com.leakcanary.mcp.MainKt")
28
+ }
29
+
30
+ kotlin {
31
+ jvmToolchain(21)
32
+ }
33
+
34
+ tasks.shadowJar {
35
+ archiveBaseName.set("leakcanary-mcp-server")
36
+ archiveClassifier.set("all")
37
+ archiveVersion.set("")
38
+ mergeServiceFiles()
39
+ }
@@ -0,0 +1,7 @@
1
+ distributionBase=GRADLE_USER_HOME
2
+ distributionPath=wrapper/dists
3
+ distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
4
+ networkTimeout=10000
5
+ validateDistributionUrl=true
6
+ zipStoreBase=GRADLE_USER_HOME
7
+ zipStorePath=wrapper/dists
@@ -0,0 +1,2 @@
1
+ kotlin.code.style=official
2
+ org.gradle.jvmargs=-Xmx1024m