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.
- package/CLAUDE.md +111 -0
- package/README.md +239 -0
- package/RELEASE.md +48 -0
- package/bin/index.js +127 -0
- package/build.gradle.kts +39 -0
- package/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/gradle.properties +2 -0
- package/gradlew +251 -0
- package/gradlew.bat +94 -0
- package/install.sh +48 -0
- package/package.json +29 -0
- package/scripts/leakcanary-mcp +91 -0
- package/settings.gradle.kts +1 -0
- package/src/main/kotlin/com/leakcanary/mcp/Main.kt +119 -0
- package/src/main/kotlin/com/leakcanary/mcp/adb/AdbExecutor.kt +200 -0
- package/src/main/kotlin/com/leakcanary/mcp/adb/AppStorageReader.kt +509 -0
- package/src/main/kotlin/com/leakcanary/mcp/analyzer/FixSuggester.kt +162 -0
- package/src/main/kotlin/com/leakcanary/mcp/analyzer/LeakAnalyzer.kt +149 -0
- package/src/main/kotlin/com/leakcanary/mcp/history/LeakHistoryStore.kt +139 -0
- package/src/main/kotlin/com/leakcanary/mcp/model/HeapSummary.kt +27 -0
- package/src/main/kotlin/com/leakcanary/mcp/model/LeakHistoryEntry.kt +20 -0
- package/src/main/kotlin/com/leakcanary/mcp/model/LeakNode.kt +25 -0
- package/src/main/kotlin/com/leakcanary/mcp/model/LeakReport.kt +16 -0
- package/src/main/kotlin/com/leakcanary/mcp/model/LeakSummary.kt +17 -0
- package/src/main/kotlin/com/leakcanary/mcp/model/LeakTrace.kt +19 -0
- package/src/main/kotlin/com/leakcanary/mcp/parser/HeapSummaryParser.kt +140 -0
- package/src/main/kotlin/com/leakcanary/mcp/parser/LeakTraceParser.kt +253 -0
- package/src/main/kotlin/com/leakcanary/mcp/tools/AnalyzeLeakTool.kt +128 -0
- package/src/main/kotlin/com/leakcanary/mcp/tools/ClearLeaksTool.kt +49 -0
- package/src/main/kotlin/com/leakcanary/mcp/tools/DeduplicateLeaks.kt +36 -0
- package/src/main/kotlin/com/leakcanary/mcp/tools/DetectLeaksTool.kt +139 -0
- package/src/main/kotlin/com/leakcanary/mcp/tools/DeviceMemoryTool.kt +222 -0
- package/src/main/kotlin/com/leakcanary/mcp/tools/ExportReportTool.kt +234 -0
- package/src/main/kotlin/com/leakcanary/mcp/tools/HeapSummaryTool.kt +59 -0
- package/src/main/kotlin/com/leakcanary/mcp/tools/LeakDiffTool.kt +173 -0
- package/src/main/kotlin/com/leakcanary/mcp/tools/LeakHistoryTool.kt +60 -0
- package/src/main/kotlin/com/leakcanary/mcp/tools/LeakSource.kt +161 -0
- package/src/main/kotlin/com/leakcanary/mcp/tools/ListDevicesTool.kt +84 -0
- package/src/main/kotlin/com/leakcanary/mcp/tools/ListLeaksTool.kt +117 -0
- package/src/main/kotlin/com/leakcanary/mcp/tools/PackageFilter.kt +105 -0
- package/src/main/kotlin/com/leakcanary/mcp/tools/SearchLeakTool.kt +174 -0
- 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();
|
package/build.gradle.kts
ADDED
|
@@ -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
|
+
}
|
|
Binary file
|