# Memorydetective

> MCP server for iOS leak hunting and performance investigation. 28 MCP tools, 34-pattern retain-cycle classifier with Swift fixTemplate snippets, compareTracesByPattern for CI gating, SourceKit-LSP source bridging. Reads .memgraph and .trace files; macOS only.

- **Type:** MCP server
- **Install:** `agentstack add mcp-carloshpdoc-memorydetective`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [carloshpdoc](https://agentstack.voostack.com/s/carloshpdoc)
- **Installs:** 0
- **Category:** [Developer Tools](https://agentstack.voostack.com/c/developer-tools)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [carloshpdoc](https://github.com/carloshpdoc)
- **Source:** https://github.com/carloshpdoc/memorydetective
- **Website:** https://www.npmjs.com/package/memorydetective

## Install

```sh
agentstack add mcp-carloshpdoc-memorydetective
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# memorydetective

**English** · [Português brasileiro](README.pt-br.md)

> Diagnose iOS retain cycles and performance regressions from your chat window. No Xcode required.

[](https://www.npmjs.com/package/memorydetective)
[](https://github.com/carloshpdoc/memorydetective/actions/workflows/ci.yml)
[](./LICENSE)
[](https://github.com/carloshpdoc/memorydetective/stargazers)
[](#requirements)
[](#requirements)

## Highlights

- **CLI-driven leak hunting.** Read `.memgraph` files captured by Xcode (or by `memorydetective` itself on simulators), find ROOT CYCLEs, classify them against known SwiftUI/Combine patterns, and get a one-liner fix hint. All from a script or a chat.
- **MCP-native.** Plugs into Claude Code, Claude Desktop, Cursor, Cline, and any other MCP client. The agent drives the full investigate → classify → suggest-fix loop without you opening Instruments.
- **Honest about its limits.** No mocked outputs, no over-promises. Hangs analysis works clean from `xctrace`; sample-level Time Profile is parsed when `xctrace` symbolicates the trace and returns a structured workaround notice when it can't (the underlying `xctrace` SIGSEGV on heavy unsymbolicated traces is an Apple-side limitation we surface explicitly). Memory Graph capture works on Mac apps and iOS simulator; physical iOS devices still need Xcode.

> **What's new in v1.18** (2026-05-17): MetricKit + audit-close. **`analyzeMetricKitPayload` is the 42nd MCP tool**: ingests Apple MetricKit `.mxdiagnostic` JSON payloads from real-device TestFlight / App Store builds (post-mortem production diagnostics, no MCP competitor covers this lane today). Three actionable outputs: crash clusters by exception type / binary / top frame, hang hotspots with localized-duration parsing (`"5.4 sec"` / `"20秒"`), CPU + disk exceptions. Cross-tool chain hints (e.g. `objc_release`-style top frame surfaces a `findCycles` suggestion). Plus three audit-close items: open-enum `SupportStatusKind` (downstream consumers add kinds without a breaking type bump), invocation-scoped `schemaDiscovery` cache (`summarizeTrace` end-to-end shaved from ~28s to ~15s on real Apple traces via single up-front TOC fetch), and local-only integration tests against real Apple `.trace` bundles (closes the v1.14 P+O drift class for good). 701 → 757 tests. 41 → 42 MCP tools.
>
> **Also recent (v1.17)**: reliability pass. 14 bug fixes across three tiers. Headlines: strtobool env truthy parsing, `verifyFix` whitelist match modes (exact / substring / regex), `recordViaInstrumentsApp` catches traces saved outside `watchDir`, `inspectTrace` fault-tolerant fallback, configurable `countAlive` framework-noise filter, variable-size class min/max/median.
>
> **And v1.16**: macOS 26.x recording-unblock release. New `recordViaInstrumentsApp` MCP tool wraps the Instruments.app GUI flow: opens the app, surfaces step-by-step instructions, watches a directory for the saved `.trace`, and chains into `inspectTrace` on success. Until Apple fixes the `xcrun xctrace record` regression on macOS 26.x sims, this is the automated path.
>
> **And v1.15**: schema coverage + verify-fix UX. Three new MCP trace tools filled the remaining schema gap: `analyzeMemoryFootprint` (38th, VM resident / dirty / virtual + jetsam diagnosis), `analyzeEnergyImpact` (39th, battery drain investigation), `analyzeLeakTimeline` (40th, xctrace's leaks instrument as a time series). `summarizeTrace` now chains `analyzeNetworkActivity`. `replayScenario` captures simulator screenshots per step.
>
> **Earlier**: v1.14 trace-side reliability, `analyzeNetworkActivity`, unified `supportStatus[]`, FLEX-inspired `countAlive` size view, MLeaksFinder + DebugSwift-inspired `verifyFix` whitelist. v1.13 shipped `summarizeTrace` + `/summarize-trace` MCP prompt. v1.12 completed reference-tree propagation. v1.11 added inspectTrace, diffMemgraphs reference-tree. v1.9 shipped analyzeAbandonedMemory, detectLeaksInXCTest, cleanupTraces, mainThreadViolations. Full notes in [CHANGELOG](./CHANGELOG.md).

> **Heads up for macOS 26.x users:** Apple shipped a `task_for_pid` kernel regression on macOS 26.x that blocks `leaks --outputGraph`, `heap`, AND `xctrace --template Allocations` against iOS simulator processes regardless of `MallocStackLogging`. Even Xcode's "View Memory Graph Hierarchy" hits it unless `Malloc Stack Logging` is enabled in the scheme's Diagnostics tab. memorydetective surfaces this as a proactive `platformAdvisory` on the first capture-class tool call, plus a `workaroundNotice` with `issue: "macos-26-task-for-pid-broken"` if `leaks` is invoked. **The most reliable workaround today is to target an iOS 18 simulator runtime** (install via Xcode > Settings > Platforms > +iOS 18.x). Empirically validated in the [notelet investigation](https://github.com/carloshpdoc/memorydetective/blob/main/CHANGELOG.md#unreleased) 2026-05-12 where three independent CLI memory-introspection paths all failed before iOS 18 was identified as the working escape hatch. Set `MEMORYDETECTIVE_SUPPRESS_PLATFORM_ADVISORY=1` to silence the notice once you have settled on a workaround.

> **Also on macOS 26.x: `xctrace record` is broken for simulator targets.** Independent from the `task_for_pid` regression above, `xcrun xctrace record --time-limit Ns` against iOS simulator processes wedges past the time limit, eventually exits when killed, and the resulting `.trace` bundle is missing template metadata. `xctrace export --toc` then fails with `Document Missing Template Error`. Re-validated against Xcode 26.5 (build 17F42, xctrace 16.0) 2026-05-15: regression survives the update. This hits the entire `xctrace`-based ecosystem the same way (`memorydetective.recordTimeProfile`, XcodeTraceMCP, and naked `xcrun xctrace record` calls all fail identically). **Workarounds:** (1) **use `recordViaInstrumentsApp`** (v1.16, hardened in v1.17), which opens Instruments.app for you, prompts you to record + save the `.trace`, then chains into `inspectTrace` automatically once the bundle appears. v1.17 also catches saves outside the watch directory via an Instruments.app AppleScript document query, returning `savedOutsideWatchDir: true` plus the actual path; (2) record from an older macOS host with Xcode 26.0 if you have one; (3) record against a physical device (the regression appears to be simulator-specific). v1.17 added a viability probe on `recordTimeProfile` (`bundleStatus: "wedged"` when the on-disk bundle is the 52K stub) and on `inspectTrace` (returns `ok: true` with diagnosis text instead of throwing). USAGE.md > Troubleshooting has a step-by-step.

## Quickstart

```bash
npm install -g memorydetective
```

Add to your MCP client config (Claude Code shown):

```jsonc
// ~/.claude/settings.json
{
  "mcpServers": {
    "memorydetective": { "command": "memorydetective" }
  }
}
```

Or use it directly from the command line:

```bash
memorydetective analyze   ~/Desktop/myapp.memgraph
memorydetective classify  ~/Desktop/myapp.memgraph
```

→ See [Examples](#examples) for chat-driven flows · [API](#api) for the full tool reference · [Configure](#configure) for Claude Desktop / Cursor / Cline · [USAGE.md](./USAGE.md) for the full walkthrough including how fixes flow from diagnosis to your codebase.

---

## Examples

> Realistic chat snippets. The agent calls the MCP tools listed in [API](#api).

### Diagnose a leak from a memgraph

> **You:** I just exported a memory graph at `~/Desktop/example.memgraph`. Something's leaking after I close a detail screen. What is it?
>
> **Claude:** *(calls `analyzeMemgraph` → `classifyCycle`)*
> Found 4 ROOT CYCLE blocks across 60,436 leaks. The dominant one matches **swiftui.tag-index-projection** with high confidence. A closure attached via `.tag()` on a photo carousel `ForEach` is capturing `self` strongly through `_viewModel.wrappedValue`, which keeps `DetailViewModel` and the navigation coordinator alive after dismissal.
>
> **Suggested fix:** replace `[weak self]` capture in the tap handler with a static helper, or weak-capture the view-model via `[weak coord = self.coordinator]`. Want me to find the exact file?

### Verify a fix actually worked

> **You:** I shipped the fix. Compare `~/Desktop/before.memgraph` to `~/Desktop/after.memgraph` and tell me whether `DetailViewModel` count dropped.
>
> **Claude:** *(calls `diffMemgraphs` → `countAlive`)*
> `DetailViewModel`: **12 → 0** (delta `-12`). The `swiftui.tag-index-projection` cycle is gone. Total leak count fell from 60,436 to 55,576 (−4,860). Three other ROOT CYCLEs persist. They're SwiftUI internals, not your code.

### Self-contained verify-fix loop (v1.8)

When `leaks --outputGraph` fails on macOS 26.x (`Failed to get DYLD info for task`), the cause is usually that the app was not launched with `MallocStackLogging=1`. The new orchestration tools handle this end to end without you wiring up xcodebuild + simctl + axe yourself.

> **You:** Reproduce the leak in `MyApp` for me. Build, launch with the right env vars, repeat the photo carousel flow 5 times to amplify it, capture before, then I'll ship the fix and you capture after.
>
> **Claude:** *(calls `bootAndLaunchForLeakInvestigation`)*
> Built + booted iPhone 15 (UDID `ABCD-...`). Launched `MyApp` (PID `49581`) with `MallocStackLogging=1`. Bundle id `com.example.MyApp`.
>
> *(calls `replayScenario` with 5 taps on the carousel + back-button cycle, repeat 5)*
> Executed 25 actions in 12.3s. No failures.
>
> *(calls `captureScenarioState({label:"before"})`)*
> Wrote `before.memgraph` (63,201 leaks), `before.png`, `before.ui.json`. `MallocStackLogging` active, no workaroundNotice.
>
> *(you ship the fix and rebuild)*
>
> *(calls `bootAndLaunchForLeakInvestigation` + replay + `captureScenarioState({label:"after"})`)*
>
> *(calls `diffMemgraphs` → `verifyFix`)*
> `DetailViewModel`: **15 → 0**. `swiftui.tag-index-projection` PASS. Bytes freed: 28.4 MB.

### Find the slow path on a real device

> **You:** Profile DemoApp on my iPhone for 90 seconds and tell me where the hangs are.
>
> **Claude:** *(calls `listTraceDevices` → `recordTimeProfile` → `analyzeHangs`)*
> Recorded `~/Desktop/run.trace` (90s, attached to DemoApp on iPhone 17 Pro Max). Found **23 user-visible hangs** (>500 ms each), longest 1.16 s, average 769 ms. Severe load. All on the main thread. Sample-level hotspots aren't symbolicated yet; open the trace in Instruments for that step (`analyzeTimeProfile` returns a structured workaround notice when `xctrace` can't symbolicate. See CHANGELOG known limits).

### Analyze MetricKit payloads from real-user crashes (v1.18)

> **You:** I have a folder of `.mxdiagnostic` files from the last 30 days of TestFlight at `~/Downloads/metrickit-may/`. What's catching real users?
>
> **Claude:** *(calls `analyzeMetricKitPayload({ payloadDir: "~/Downloads/metrickit-may/" })`)*
> 47 payloads, 2026-04-15 → 2026-05-15. Top: **18 crashes EXC_BAD_ACCESS** clustered on the same top frame, all in builds 456 and 457 (clean on 458 + earlier). Pattern matches retain-cycle release; chain hint surfaces `findCycles` next.
>
> Second: **5.4s hang** on `libsqlite3.dylib` (3 samples). Pattern `db-lock`. If you reproduce locally, chain into `analyzeHangs` with `includeStackClassification: true` for the `mainThreadViolations[]` classifier.
>
> No symbolicated frames in v1.18 (raw `binaryUUID + offset` shipped; dSYM lookup is a separate tool, future release). Localized hang strings handled: input was `"5.4 sec"`, normalized to 5400ms; one diagnostic carried `"20秒"` (Japanese), normalized to 20000ms.

See [USAGE.md § MetricKit production post-mortem](./USAGE.md#end-to-end-example-metrickit-production-post-mortem-v118) for the full end-to-end including how to wire `analyzeMetricKitPayload` into a verify-fix loop across multiple TestFlight builds.

### End-to-end: leak → file → fix suggestion

The full loop, from artifact to code edit. Combines memory-graph analysis with Swift source-bridging.

> **You:** Tem um leak. Memgraph em `~/Desktop/example-leaks.memgraph`. The cycle points at `DetailViewModel`. Find where it lives in the project `~/Development/myapp/` and suggest a fix.
>
> **Claude:** *(calls `analyzeMemgraph` → `classifyCycle` → `reachableFromCycle`)*
> Found **4 ROOT CYCLE blocks**. The dominant one matches `swiftui.tag-index-projection` with high confidence. `reachableFromCycle` confirms 4 `DetailViewModel` instances pinning ~1100 `NSURLSessionConfiguration`s each. The VM is the culprit, the network stack is collateral.
>
> *(calls `swiftSearchPattern` to locate `.tag(` + `onImageSliderTap` patterns in `~/Development/myapp/Sources/`)*
> 1 match in `MyApp/Views/MediaCarousel.swift:142`.
>
> *(calls `swiftGetSymbolDefinition({ symbolName: "DetailViewModel", projectRoot: "...", candidatePaths: ["MyApp/ViewModels/"] })`)*
> Declared at `MyApp/ViewModels/DetailViewModel.swift:18`. `class DetailViewModel: ObservableObject`.
>
> *(calls `swiftFindSymbolReferences` to scope the blast radius. 7 callsites)*
>
> **Suggested fix at `MediaCarousel.swift:142`:**
> ```swift
> // before
> .tag(index)
> .onImageSliderTap { index in
>     self.viewModel.handlePhotoTap(at: index)
> }
>
> // after. Static helper + weak captures
> .tag(index)
> .onImageSliderTap { [weak vm = _viewModel.wrappedValue,
>                      weak coord = self.coordinator] index in
>     Self.handlePhotoTap(index: index, viewModel: vm, coordinator: coord)
> }
> ```
> Want me to apply this and verify with a fresh `.memgraph`?

---

## What it saves you

The pitch in one sentence: **`memorydetective` turns a 50-500 MB binary memgraph (or a 200 KB `leaks(1)` text dump) into a 2-5 KB structured summary your AI agent can actually reason about.** That changes the economics of using an LLM for iOS perf investigation.

### Tokens (when paired with an AI agent like Claude / Cursor / Cline)

A real-world retain-cycle investigation, run twice. Once with `memorydetective`, once with the agent reading the raw `leaks(1)` output directly:

| Step | Without MCP (agent reads raw output) | With `memorydetective` |
|---|---|---|
| Load `leaks` text dump (~280 KB) | ~70,000 input tokens | n/a |
| `analyzeMemgraph` summary | n/a | ~750 input tokens |
| `classifyCycle` + fix hint | agent re-reasons over the dump per follow-up (3-4 extra turns) | 1 turn, structured `patternId` + `fixHint` |
| `findRetainers` / `reachableFromCycle` | agent re-scans the dump | ~500 tokens, scoped query |
| **Net per investigation** | ~85,000 tokens, ~6 turns | ~3,000 tokens, ~2 turns |

**Translates to roughly $0.40-$1.20 per investigation** depending on the model (Claude Opus / Sonnet / Haiku). Compounds linearly with file size and investigation depth.

### Developer time

The same investigation, measured by the developer:

| Step | Without MCP | With `memorydetective` |
|---|---|---|
| Capture memgraph + run `leaks` | 5 min | 5 min (same) |
| Read & interpret `leaks` text dump | 15-30 min (skim 200 KB of repetitive frames) | 30 sec (read 3 KB summary) |
| Identify the responsible pattern | 10-20 min (recognize the cycle shape from experience) | instant (classifier returns `patternId` + fix hint) |
| Locate the suspect type in source | 10-15 min (grep + manual navigation) | 30 sec (`swiftGetSymbolDefinition` returns `file:line`) |
| Find every callsite to gauge fix blast radius | 5-10 min (Xcode / grep) | 10 sec (`swiftFindSymbolReferences`) |
| **Net wall-clock** | **45-80 min** | **~10 min** |

Numbers are rounded from a single anonymized real investigation (a SwiftUI retain cycle over a tagged `ForEach` that pinned ~28 MB of network-stack state). Your mileage will vary with cycle complexity and codebase size.

### When the win is marginal

Be honest about where this **doesn't** help much:

- **Tiny memgraphs** (a single cycle, 
Claude Code

```jsonc
// ~/.claude/settings.json (global) or .mcp.json (per-project)
{
  "mcpServers": {
    "memorydetective": { "command": "memorydetective" }
  }
}
```

Claude Desktop

```jsonc
// ~/Librar

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [carloshpdoc](https://github.com/carloshpdoc)
- **Source:** [carloshpdoc/memorydetective](https://github.com/carloshpdoc/memorydetective)
- **License:** Apache-2.0
- **Homepage:** https://www.npmjs.com/package/memorydetective

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** yes
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-carloshpdoc-memorydetective
- Seller: https://agentstack.voostack.com/s/carloshpdoc
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
