memorydetective
Diagnoses retain cycles and performance regressions in iOS applications using memory graph and time profile analysis.
Diagnoses retain cycles and performance regressions in macOS applications, including simulator and Mac apps.
Provides tools for searching Swift source code patterns, finding symbol definitions, and locating references within a project.
Analyzes .memgraph files and .trace bundles produced by Xcode to diagnose retain cycles and performance regressions in iOS and macOS apps.
memorydetective
Diagnose iOS retain cycles and performance regressions from your chat window. No Xcode required.

Highlights
CLI-driven leak hunting. Read
.memgraphfiles captured by Xcode (or bymemorydetectiveitself 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 whenxctracesymbolicates the trace and returns a structured workaround notice when it can't (the underlyingxctraceSIGSEGV 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.7 (2026-05-03): catalog grew from 33 to 34 cycle patterns (
swiftdata.modelcontext-actor-cyclefor the SwiftData@Actorpattern), every classification now carries afixTemplatefield with concrete Swift before/after snippets the agent can adapt directly, and a newcompareTracesByPatterntool does for.tracebundles whatverifyFixdoes for memgraphs. PASS/PARTIAL/FAIL verdicts on hangs / animation-hitches / app-launch regressions. 27 → 28 MCP tools.Also in v1.6 (same day): catalog 27 → 33, MCP Resources + Prompts surface, and the original
staticAnalysisHintfield. Full notes in CHANGELOG.
Quickstart
npm install -g memorydetectiveAdd to your MCP client config (Claude Code shown):
// ~/.claude/settings.json
{
"mcpServers": {
"memorydetective": { "command": "memorydetective" }
}
}Or use it directly from the command line:
memorydetective analyze ~/Desktop/myapp.memgraph
memorydetective classify ~/Desktop/myapp.memgraph→ See Examples for chat-driven flows · API for the full tool reference · Configure for Claude Desktop / Cursor / Cline · 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.
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 carouselForEachis capturingselfstrongly through_viewModel.wrappedValue, which keepsDetailViewModeland 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.memgraphto~/Desktop/after.memgraphand tell me whetherDetailViewModelcount dropped.Claude: (calls
diffMemgraphs→countAlive)DetailViewModel: 12 → 0 (delta-12). Theswiftui.tag-index-projectioncycle 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.
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 (analyzeTimeProfilereturns a structured workaround notice whenxctracecan't symbolicate. See CHANGELOG known limits).
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 atDetailViewModel. 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 matchesswiftui.tag-index-projectionwith high confidence.reachableFromCycleconfirms 4DetailViewModelinstances pinning ~1100NSURLSessionConfigurations each. The VM is the culprit, the network stack is collateral.(calls
swiftSearchPatternto locate.tag(+onImageSliderTappatterns in~/Development/myapp/Sources/) 1 match inMyApp/Views/MediaCarousel.swift:142.(calls
swiftGetSymbolDefinition({ symbolName: "DetailViewModel", projectRoot: "...", candidatePaths: ["MyApp/ViewModels/"] })) Declared atMyApp/ViewModels/DetailViewModel.swift:18.class DetailViewModel: ObservableObject.(calls
swiftFindSymbolReferencesto scope the blast radius. 7 callsites)Suggested fix at
MediaCarousel.swift:142:// 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 |
Load | ~70,000 input tokens | n/a |
| n/a | ~750 input tokens |
| agent re-reasons over the dump per follow-up (3–4 extra turns) | 1 turn, structured |
| 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 |
Capture memgraph + run | 5 min | 5 min (same) |
Read & interpret | 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 |
Locate the suspect type in source | 10–15 min (grep + manual navigation) | 30 sec ( |
Find every callsite to gauge fix blast radius | 5–10 min (Xcode / grep) | 10 sec ( |
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, < 50 KB raw): MCP overhead is roughly token-neutral vs. Raw read. The dev-time win still holds (no manual cycle parsing) but the token win shrinks.
One-shot symbol lookups without a leak attached: just use
grep, you don't need this.First-time investigations on a new codebase: the agent still needs orientation turns regardless of MCP. The compounding wins kick in on the second and later investigations once the agent has cached the project's shape.
The win compounds with (a) file size, (b) investigation depth (multi-turn), and (c) how many leaks you investigate per quarter. For a single dev fixing one leak per year, the value is mostly the dev-time saving. For a team running CI gates with verifyFix across every PR, the token + time savings stack across hundreds of runs.
Configure
The memorydetective binary speaks MCP over stdio. Point any MCP-compatible client at it.
// ~/.claude/settings.json (global) or .mcp.json (per-project)
{
"mcpServers": {
"memorydetective": { "command": "memorydetective" }
}
}// ~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"memorydetective": { "command": "memorydetective" }
}
}Restart Claude Desktop after editing.
// ~/.cursor/mcp.json
{
"mcpServers": {
"memorydetective": { "command": "memorydetective" }
}
}// VS Code settings.json
{
"cline.mcpServers": {
"memorydetective": { "command": "memorydetective" }
}
}Kiro supports MCP servers via its global config. The block mirrors Claude Desktop's:
{
"mcpServers": {
"memorydetective": { "command": "memorydetective" }
}
}Consult Kiro's MCP setup docs for the exact config file path on your system.
GitHub Copilot supports MCP servers in Agent mode (VS Code 1.94+). Add to .vscode/mcp.json in your repo:
{
"servers": {
"memorydetective": {
"type": "stdio",
"command": "memorydetective"
}
}
}Copilot's MCP integration moves fast. If this snippet is stale, see the VS Code MCP docs.
API
28 MCP tools + 34 Resources + 5 Prompts, grouped by purpose. Tool descriptions are tagged with a category prefix ([mg.memory], [mg.trace], [mg.code], [mg.log], [mg.render], [mg.ci], [mg.discover], [meta]) so related tools are visible at a glance.
Many tools include a suggestedNextCalls field in their response. A typed list of { tool, args, why } entries pre-populated from the current result, so the orchestrating LLM can chain calls without re-reasoning. Start with getInvestigationPlaybook(kind) for the canonical sequence. Or just type /investigate-leak (one of the Prompts) in any client that exposes MCP slash commands.
The cycle classifier ships 34 named antipatterns spanning SwiftUI (including the Swift 6 / @Observable / SwiftData / NavigationStack era), Combine, Swift Concurrency (incl. AsyncSequence-on-self and the new Observations API), UIKit (Timer/CADisplayLink/UIGestureRecognizer/KVO/URLSession/WebKit/DispatchSource), Core Animation, Core Data, Coordinator pattern, and the popular third-party libs RxSwift + Realm. Each pattern carries:
a textual one-line
fixHinta confidence tier (
high/medium/low)a
staticAnalysisHintpointing at the SwiftLint rule that complements the runtime evidence (or an explicit gap notice when no rule exists. Reinforces the differentiator: memorydetective sees what linters miss at parse time)a
fixTemplatewith concrete Swift before/after snippets (new in v1.7) the agent can adapt directly to the user's code via the SourceKit-LSP source-bridging tools
Read & analyze (13)
Tool | What |
| Run |
| Extract just the ROOT CYCLE blocks as flattened chains, with optional |
| "Who is keeping |
| Count instances by class. Provide |
| Cycle-scoped reachability. "How many |
| Compare two |
| Cycle-semantic diff: per-pattern PASS/PARTIAL/FAIL verdict + bytes freed. CI-gateable. |
| Match each ROOT CYCLE against a built-in catalog of 34 named antipatterns (SwiftUI / Combine / Concurrency / UIKit / Core Animation / Core Data / Coordinator / RxSwift / Realm) with confidence + textual |
| Parse |
| Parse |
| Parse |
| Parse |
| Parse |
| One-shot query of macOS unified logging via |
Capture / record (3)
Tool | What | Sim | Device |
| Wrap | ✅ | ✅ |
| Wrap | ✅ | ❌. Use Xcode |
| Wrap | n/a | n/a |
Discover (2)
Tool | What |
| Parse |
| Parse |
Render (1)
Tool | What |
| Read a |
CI / test integration (2)
Tool | What |
| Experimental. Build the workspace for testing, run the named XCUITest, capture |
| Trace-side counterpart to |
Swift source bridging (5)
Pair the memory-graph diagnosis with source-code lookups via SourceKit-LSP. Closes the loop "found this leak in the cycle → find the file/line in your project".
Tool | What |
| Locate the file:line where a Swift symbol is declared. Pre-scans |
| Find every reference to a Swift symbol via SourceKit-LSP |
| List top-level symbols (classes, structs, enums, protocols, free functions) in a Swift file via |
| Type info / docs at a (line, character) position. Disambiguates |
| Pure regex search over a Swift file (no LSP, no index). Catches what LSP misses: closure capture lists, |
These tools require macOS + Xcode (full Xcode, not just Command Line Tools. xcrun sourcekit-lsp must be available). They start a sourcekit-lsp subprocess per project root and reuse it across calls; the subprocess shuts down after a 5-minute idle window.
Why
captureMemgraphdoesn't work on physical iOS devices:leaks(1)only attaches to processes running on the local Mac (which includes iOS simulators). Memory Graph capture from a real device goes through Xcode's debugger over USB/lockdownd. Different mechanism, no public CLI equivalent.
Resources (34)
The cycle-pattern catalog is also surfaced as MCP resources, browsable at memorydetective://patterns/{patternId}. Each resource is a markdown body with the pattern name, a longer description, and the fix hint. Use this to let an agent (or a human in a UI-aware MCP client) browse the catalog without burning a classifyCycle call.
memorydetective://patterns/swiftui.tag-index-projection
memorydetective://patterns/concurrency.async-sequence-on-self
memorydetective://patterns/webkit.wkscriptmessagehandler-bridge
memorydetective://patterns/swiftdata.modelcontext-actor-cycle
…resources/list returns all 34 entries. resources/read resolves any memorydetective://patterns/{id} URI to its markdown body.
Prompts (5)
Investigation playbooks are exposed as MCP prompts (slash commands in clients that surface them, e.g. Claude Code).
Slash command | What it does | Args |
| Runs the canonical 6-step memgraph-leak investigation: |
|
| Diagnose user-visible main-thread hangs from a |
|
| Diagnose dropped frames / animation hitches from a |
|
| Diagnose cold/warm launch slowness from a |
|
| Diff a before/after pair of |
|
Each prompt fills the canonical playbook's argument templates with the user-provided values, then hands the agent a ready-to-execute brief. Calls the same tools listed in Read & analyze. Prompts are an orchestration shortcut, not a separate engine.
CLI mode
The same binary is also a thin CLI for scripting and CI:
memorydetective analyze <path-to-.memgraph> # totals, ROOT CYCLEs, diagnosis
memorydetective classify <path-to-.memgraph> # match patterns + render fix hint
memorydetective --help
memorydetective --versionWhen called with no arguments, the binary starts as an MCP server over stdio.
Requirements
macOS with Xcode Command Line Tools (
xcode-select --install)Node.js ≥ 20
Develop
git clone https://github.com/carloshpdoc/memorydetective
cd memorydetective
npm install
npm test # 61 unit tests
npm run build # build → dist/
npm run dev # tsx, stdio mode (dev mode)
./scripts/demo.sh # full demo against a real .memgraph (set MEMGRAPH=path)Contributing
Contributions are welcome. Bug reports, feature requests, new cycle patterns, all of it.
Bugs / feature requests: open an issue.
PRs: fork → branch →
npm install→ make changes →npm test(206 tests must stay green) → open a PR with a concise description of what changed and why.
Adding a cycle pattern to classifyCycle
classifyCycle ships with 34 built-in patterns covering SwiftUI (incl. Swift 6 / @Observable / SwiftData / NavigationStack), Combine, Swift Concurrency (incl. AsyncSequence-on-self and Observations), UIKit (Timer / CADisplayLink / UIGestureRecognizer / KVO / URLSession / WebKit / DispatchSource), Core Animation, Core Data, the Coordinator pattern, RxSwift, and Realm. To add one:
Edit
src/tools/classifyCycle.ts. Add an entry toPATTERNSwithid,name,fixHint, and amatchfunction.Add a test in
src/tools/readTools.test.tsthat asserts the new pattern fires against a representative memgraph fixture.Add a
staticAnalysisHintentry insrc/runtime/staticAnalysisHints.ts(the test in that file enforces 1:1 coverage withPATTERNS).Add a
fixTemplateentry insrc/runtime/fixTemplates.ts(same 1:1 coverage guard).Open a PR.
Support this project
If memorydetective saves you time, you can support continued development:
Every contribution helps keep this maintained and documented.
License
Apache 2.0. See LICENSE and NOTICE.
Permits commercial use, modification, distribution, patent use. Includes attribution clause via the NOTICE file.
Why "memorydetective"?
Hunting retain cycles in SwiftUI feels like detective work: you have a body (the leaked instance), a crime scene (the .memgraph), and a chain of suspects (the retain chain). The tool helps you read the evidence and name the killer. The brand follows the work.
Maintenance
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/carloshpdoc/memorydetective'
If you have feedback or need assistance with the MCP directory API, please join our Discord server