memorydetective
memorydetective is an MCP server for diagnosing iOS memory leaks, retain cycles, and performance issues — no Xcode required.
Leak detection & analysis — run
leaks(1)against.memgraphfiles to find ROOT CYCLEs, retain chains, and class instance countsCycle classification — match cycles against a catalog of known antipatterns (SwiftUI, Combine, UIKit, Task, NotificationCenter, etc.) with confidence scores and fix hints
Retain cycle tools — find who keeps a class alive (
findRetainers), filter cycles by class, count reachable instances from a cycle root, and render cycles as Mermaid/Graphviz diagramsMemory snapshot diffing — compare two
.memgraphsnapshots to see which cycles appeared, disappeared, or persisted, and verify fixes reduced instance counts and freed bytesTime Profiler trace analysis — parse
.tracebundles for main-thread hangs, animation hitches, allocations, app launch phase breakdown, and top CPU symbolsPerformance regression verification — compare before/after
.tracebundles for hangs, hitches, or launch times with PASS/FAIL thresholdsCapture & record — snapshot live
.memgraphfrom running processes and record Time Profiler traces from simulators or devicesProduction diagnostics — ingest Apple MetricKit
.mxdiagnosticpayloads from TestFlight/App Store with crash clustering and hang hotspot analysisSwift source bridging — locate symbol declarations, find references, get hover type info, and regex-search Swift files via SourceKit-LSP to connect leaks to code
CI integration — run leak detection in XCUITest/XCTest suites with HTML reports, pass/fail gates, and allowlists
Log analysis — query and stream macOS unified logs with NSPredicate filters
Investigation playbooks — retrieve canonical step-by-step tool sequences for memgraph-leak, perf-hangs, UI jank, app-launch-slow, or verify-fix workflows
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
English · Português brasileiro
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.18 (2026-05-17): MetricKit + audit-close.
analyzeMetricKitPayloadis the 42nd MCP tool: ingests Apple MetricKit.mxdiagnosticJSON 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 afindCyclessuggestion). Plus three audit-close items: open-enumSupportStatusKind(downstream consumers add kinds without a breaking type bump), invocation-scopedschemaDiscoverycache (summarizeTraceend-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.tracebundles (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,
verifyFixwhitelist match modes (exact / substring / regex),recordViaInstrumentsAppcatches traces saved outsidewatchDir,inspectTracefault-tolerant fallback, configurablecountAliveframework-noise filter, variable-size class min/max/median.And v1.16: macOS 26.x recording-unblock release. New
recordViaInstrumentsAppMCP tool wraps the Instruments.app GUI flow: opens the app, surfaces step-by-step instructions, watches a directory for the saved.trace, and chains intoinspectTraceon success. Until Apple fixes thexcrun xctrace recordregression 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).summarizeTracenow chainsanalyzeNetworkActivity.replayScenariocaptures simulator screenshots per step.Earlier: v1.14 trace-side reliability,
analyzeNetworkActivity, unifiedsupportStatus[], FLEX-inspiredcountAlivesize view, MLeaksFinder + DebugSwift-inspiredverifyFixwhitelist. v1.13 shippedsummarizeTrace+/summarize-traceMCP 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.
Heads up for macOS 26.x users: Apple shipped a
task_for_pidkernel regression on macOS 26.x that blocksleaks --outputGraph,heap, ANDxctrace --template Allocationsagainst iOS simulator processes regardless ofMallocStackLogging. Even Xcode's "View Memory Graph Hierarchy" hits it unlessMalloc Stack Loggingis enabled in the scheme's Diagnostics tab. memorydetective surfaces this as a proactiveplatformAdvisoryon the first capture-class tool call, plus aworkaroundNoticewithissue: "macos-26-task-for-pid-broken"ifleaksis 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 2026-05-12 where three independent CLI memory-introspection paths all failed before iOS 18 was identified as the working escape hatch. SetMEMORYDETECTIVE_SUPPRESS_PLATFORM_ADVISORY=1to silence the notice once you have settled on a workaround.
Also on macOS 26.x:
xctrace recordis broken for simulator targets. Independent from thetask_for_pidregression above,xcrun xctrace record --time-limit Nsagainst iOS simulator processes wedges past the time limit, eventually exits when killed, and the resulting.tracebundle is missing template metadata.xctrace export --tocthen fails withDocument Missing Template Error. Re-validated against Xcode 26.5 (build 17F42, xctrace 16.0) 2026-05-15: regression survives the update. This hits the entirexctrace-based ecosystem the same way (memorydetective.recordTimeProfile, XcodeTraceMCP, and nakedxcrun xctrace recordcalls all fail identically). Workarounds: (1) userecordViaInstrumentsApp(v1.16, hardened in v1.17), which opens Instruments.app for you, prompts you to record + save the.trace, then chains intoinspectTraceautomatically once the bundle appears. v1.17 also catches saves outside the watch directory via an Instruments.app AppleScript document query, returningsavedOutsideWatchDir: trueplus 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 onrecordTimeProfile(bundleStatus: "wedged"when the on-disk bundle is the 52K stub) and oninspectTrace(returnsok: truewith diagnosis text instead of throwing). USAGE.md > Troubleshooting has a step-by-step.
Related MCP server: xcodeinstrumentmcp
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.
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
MyAppfor 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 (UDIDABCD-...). LaunchedMyApp(PID49581) withMallocStackLogging=1. Bundle idcom.example.MyApp.(calls
replayScenariowith 5 taps on the carousel + back-button cycle, repeat 5) Executed 25 actions in 12.3s. No failures.(calls
captureScenarioState({label:"before"})) Wrotebefore.memgraph(63,201 leaks),before.png,before.ui.json.MallocStackLoggingactive, no workaroundNotice.(you ship the fix and rebuild)
(calls
bootAndLaunchForLeakInvestigation+ replay +captureScenarioState({label:"after"}))(calls
diffMemgraphs→verifyFix)DetailViewModel: 15 → 0.swiftui.tag-index-projectionPASS. 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 (analyzeTimeProfilereturns a structured workaround notice whenxctracecan't symbolicate. See CHANGELOG known limits).
Analyze MetricKit payloads from real-user crashes (v1.18)
You: I have a folder of
.mxdiagnosticfiles 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 surfacesfindCyclesnext.Second: 5.4s hang on
libsqlite3.dylib(3 samples). Patterndb-lock. If you reproduce locally, chain intoanalyzeHangswithincludeStackClassification: truefor themainThreadViolations[]classifier.No symbolicated frames in v1.18 (raw
binaryUUID + offsetshipped; 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 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 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.
Environment variables
Every boolean MEMORYDETECTIVE_* flag below accepts the strtobool truthy set (case-insensitive): 1 / true / t / yes / y / on (truthy) and 0 / false / f / no / n / off (falsy). Unrecognized values emit a one-time stderr warning per variable and fall back to the documented default. Pre-v1.17 the parser was 1-only, which caused silent no-ops when operators exported =true or =yes. The advisory warning is gated on MEMORYDETECTIVE_SUPPRESS_PLATFORM_ADVISORY.
Variable | Default | Effect |
|
| Output scrubbing applied to every tool response. |
| unset | Boolean (strtobool). Allows |
|
| Cap on |
|
| Directory used when |
| unset | Boolean (strtobool). Allows |
| unset | Boolean (strtobool). Silences the macOS 26.x platform advisory that captureMemgraph, captureScenarioState, and bootAndLaunchForLeakInvestigation emit on first use. Also silences the v1.17 stderr warnings emitted on unrecognized boolean values (any |
| unset | Boolean (strtobool). Makes |
| unset (auto) | Boolean (strtobool) + |
API
42 MCP tools + 34 Resources + 7 Prompts, grouped by purpose. Tool descriptions are tagged with a category prefix ([mg.memory], [mg.trace], [mg.build], [mg.scenario], [mg.code], [mg.log], [mg.render], [mg.ci], [mg.discover], [ops], [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 36 named antipatterns spanning SwiftUI (including the Swift 6 / @Observable / SwiftData / NavigationStack era, plus the v1.9 swiftui.observable-write-on-every-render shape), Combine, Swift Concurrency (incl. AsyncSequence-on-self and the new Observations API), UIKit (Timer/CADisplayLink/UIGestureRecognizer/KVO/URLSession/WebKit/DispatchSource, plus the v1.9 uikit.viewcontroller-retained-after-pop shape), 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 (14)
All 9 trace-side analyzers below accept an optional second argument
AnalyzeTraceOptions(v1.18 D-02). When called bysummarizeTrace(which runs schema discovery once up front), the cache is forwarded so the per-analyzerxctrace --toccalls are skipped. Direct callers leave the option unset and behavior is identical to v1.17.
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 |
| Diff two |
| Cycle-semantic diff: per-pattern PASS/PARTIAL/FAIL verdict + bytes freed. CI-gateable. |
| Match each ROOT CYCLE against a built-in catalog of 36 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 (4)
Tool | What | Sim | Device |
| Wrap | ✅ | ✅ |
| macOS 26.x escape hatch (v1.16). Opens Instruments.app via | ✅ | ✅ |
| Wrap | ✅ | ❌. Use Xcode |
| Wrap | n/a | n/a |
Verify-fix orchestration (3, v1.8)
These three tools combine into a single deterministic verify-fix loop: launch the app with MallocStackLogging=1 so leaks works, drive the UI to amplify the suspected leak, snapshot before, ship the fix, snapshot after, then diffMemgraphs.
Tool | What |
| Single-call build + boot + install + launch with |
| Drive the iOS Simulator through tap / swipe / wait / type actions with a |
| Composite snapshot for verify-fix: writes |
Discover (3)
Tool | What |
| Parse |
| Parse |
| Orientation tool for |
Synthesize (1)
Tool | What |
| Single call that chains |
Production diagnostics (1, v1.18)
Tool | What |
| Ingest Apple MetricKit |
Render (1)
Tool | What |
| Read a |
Ops (1)
Tool | What |
| Preview and delete |
CI / test integration (3)
Tool | What |
| Build the unit-test scheme, run with an optional |
| XCUITest sibling: build the workspace, run the named XCUITest, capture |
| Trace-side counterpart to |
Add memorydetective to your CI in 5 minutes
detectLeaksInXCTest + outputHtmlPath are the building blocks for a per-PR leak gate. The job below runs the named unit-test scheme on every push and PR, uploads the HTML report as a workflow artifact, and fails when new ROOT CYCLEs appear outside the allowlist. Copy the file into .github/workflows/leaks.yml and adjust the workspace + scheme + test identifier:
name: leaks
on: [push, pull_request]
jobs:
detect-leaks:
runs-on: macos-14
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- run: sudo xcode-select -s /Applications/Xcode_15.4.app
- run: npm install -g memorydetective
- run: |
xcrun simctl boot "iPhone 15" || true
xcrun simctl bootstatus "iPhone 15" -b
- run: |
cat > leaks.json <<EOF
{
"workspace": "DemoApp.xcworkspace",
"scheme": "DemoAppTests",
"destination": "platform=iOS Simulator,name=iPhone 15,OS=18.0",
"testCaseFilter": "DemoTests/LeakSensitiveCase",
"outputHtmlPath": "${GITHUB_WORKSPACE}/leak-report.html",
"allowlistPatterns": ["SwiftUI", "_TtC"]
}
EOF
memorydetective tool detectLeaksInXCTest --input leaks.json
- if: always()
uses: actions/upload-artifact@v4
with:
name: leak-report
path: leak-report.html
retention-days: 14The same file is in examples/ci/github-actions-leaks.yml if you want to copy it verbatim. Notes:
Simulator runtime: pin to iOS 18 on
macos-14runners. The macOS 26.x kernel regression (task_for_pid) breaksleaksagainst iOS 26 sims; iOS 18 is the canonical escape hatch (see the Highlights callout above).Allowlist patterns: substrings matched against the leaking ROOT CYCLE's root class. Use them to mask known pre-existing leaks while you work the backlog.
_TtCcovers Swift mangled class prefixes that occasionally show up in SwiftUI internals.HTML artifact: the report is self-contained (inline CSS, no external assets), so PR-comment bots and reviewers can preview it directly from the artifact URL.
Build cache: add
actions/cache@v4keyed onPackage.resolved+*.xcconfigto skipbuild-for-testingrebuilds across runs. Then pass--skipBuildto the second invocation when chaining multipledetectLeaksInXCTestcalls on the same job.
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 (7)
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 |
|
| Single-call cross-schema summary card for a |
|
| Post-mortem flow for Apple MetricKit |
|
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 tool <toolName> --input <json> # generic dispatcher for any MCP tool
memorydetective --help
memorydetective --versionWhen called with no arguments, the binary starts as an MCP server over stdio.
The tool subcommand dispatches to any registered MCP tool by name, reading inputs from a JSON file. Exit code is 0 when the tool returns ok && passed !== false, 1 otherwise, so it slots cleanly into CI gates. Currently supported tool names: detectLeaksInXCTest, detectLeaksInXCUITest (the CI recipe above uses this).
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 # 758 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(758 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 36 built-in patterns covering SwiftUI (incl. Swift 6 / @Observable / SwiftData / NavigationStack / the v1.9 observable-write-on-every-render and viewcontroller-retained-after-pop shapes), 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.
Available Tools
42 toolsanalyzeAbandonedMemoryDiff reference-tree class counts and classify abandoned-memory shapeA
[mg.memory] Compare two .memgraph snapshots on heap reference-tree class counts (NOT cycle list) and classify each class's growth shape. Surfaces the family of bugs the cycle-only diffMemgraphs misses: orphaned KVO observers, never-removed NotificationCenter handlers, caches that never evict, singleton-retained payloads, and the long tail of unknown-growth worth manual inspection.
Pair with the verify-fix loop: captureScenarioState({label:'before'}) -> ship fix -> captureScenarioState({label:'after'}) -> analyzeAbandonedMemory(beforePath, afterPath). Validated end-to-end on the notelet investigation where AVPlayerItem went 342 to 0 across a fix that was invisible in standard leaks output (leakCount: 0 both sides).
Returns growthByClass[] ranked by absolute delta, each entry tagged with classification (kvo-observer-orphaned, notificationcenter-observer-leaked, cache-too-aggressive, singleton-retains-payload, unknown-growth) + confidence tier + hint. The classifier escalates large co-occurrence growth: if NSKeyValueObservance grew, other large-delta classes are assumed to be the observed types being retained, classified as kvo-observer-orphaned with confidence scaling by delta size.
Inputs: beforePath (required), afterPath (required), topN, classFilter, +1 more.
analyzeAllocationsAnalyze allocations from a .trace bundleB
[mg.trace] Parse the allocations schema from a .trace recorded with the Allocations Instruments template. Returns per-category aggregates (cumulative bytes, allocation count, lifecycle = transient/persistent/mixed), top allocators by size and by count, and a one-liner diagnosis identifying the dominant allocator.
Inputs: tracePath (required), topN, minBytes, outputFormat.
analyzeAnimationHitchesAnalyze animation hitches from a .trace bundleA
[mg.trace] Parse the animation-hitches schema from a .trace recorded with the Animation Hitches Instruments template. Returns hitch totals, by-type counts, longest hitches, and how many crossed the user-perceptible 100ms threshold.
Inputs: tracePath (required), topN, minDurationMs, timeRangeMs, +1 more.
analyzeAppLaunchAnalyze cold/warm launch breakdownA
[mg.trace] Parse the app-launch schema from a .trace recorded with the App Launch Instruments template. Returns total launch time, launch type (cold/warm), per-phase breakdown (process-creation, dyld-init, ObjC-init, AppDelegate, first-frame), and the slowest phase.
Inputs: tracePath (required), outputFormat.
analyzeEnergyImpactAnalyze energy use / battery drain from an Energy Log traceA
[mg.trace] Parse the energy-impact schema from a .trace recorded with an Energy Log template. Returns per-sample bucket classification (idle / passive / active / high), aggregate wakeup count, active-state ratio, top-N samples by energy cost. The 'why is my app draining battery?' investigation. Distinct from analyzeTimeProfile (CPU sampling); reads the OS power-management subsystem directly. v1.15+.
Inputs: tracePath (required), topN, outputFormat.
analyzeHangsAnalyze potential hangs from a .trace bundleA
[mg.trace] Run xcrun xctrace export against a .trace bundle for the potential-hangs schema and return aggregated stats (Hang vs Microhang counts, longest, average, total duration) plus the top N longest hangs sorted by duration. Use minDurationMs: 250 to filter to user-visible hangs only. Pass topFramesByHangStartNs: { '<startNs>': '<topFrame>' } to enrich each top hang with a mainThreadViolations[] field that classifies the kind of work blocking the main thread (sync-io, db-lock, network, lock-contention). The map keys are stringified startNs values; the typical pipeline is to call analyzeTimeProfile separately on the same trace, correlate samples to the hang windows by timestamp, then re-call analyzeHangs with the resulting map.
Inputs: tracePath (required), topN, minDurationMs, timeRangeMs, +3 more.
analyzeLeakTimelineAnalyze leaks as a time series (xctrace Leaks instrument)A
[mg.trace] Parse the leaks schema from a .trace recorded with a Leaks template. Distinct from leaks(1) CLI (snapshot): this is a time series of leak events captured throughout the recording. Returns per-class first-seen-at timestamp, peak instance count, peak bytes, event count. Useful for answering 'when in the timeline did the leak appear?' which the snapshot CLI cannot. v1.15+.
Inputs: tracePath (required), topN, outputFormat.
analyzeMemgraphAnalyze a .memgraph fileA
[mg.memory] Run leaks(1) against a .memgraph file (exported from Xcode Memory Graph Debugger) and return a structured summary: header info, totals, top-level ROOT CYCLE blocks with chain length, plain-English diagnosis. Set fullChains: true to also include the full nested retain forest.
Pipeline: → classifyCycle (named-antipattern + fix hint) → reachableFromCycle (scope blame to a single root). The response includes suggestedNextCalls so the agent can chain without re-reasoning.
Inputs: path (required), fullChains, verbosity, maxClassesInChain, +2 more.
analyzeMemoryFootprintAnalyze process VM footprint (resident / dirty / virtual)A
[mg.trace] Parse the memory-footprint schema from a .trace recorded with Allocations or System Trace template. Returns peak resident bytes (RAM in use), peak dirty bytes (the OOM-kill discriminator on iOS), peak VM regions, per-sample timeline. Distinct from analyzeAllocations (cumulative malloc bytes by category). Use when investigating 'why is my app getting jetsam-killed?'. v1.15+.
Inputs: tracePath (required), topN, outputFormat.
analyzeMetricKitPayloadAnalyze a .mxdiagnostic payload from MetricKit (production diagnostics)A
[mg.production] Parse Apple MetricKit .mxdiagnostic payloads from real-device TestFlight / App Store builds. Aggregates crashes (clustered by exception type / binary / top frame), hang hotspots (sorted by duration, with localized-string handling for hangDuration), CPU exceptions, and disk-write exceptions. Inputs: payloadPath (single file), payloadDir (aggregate across files), or payloadJson (raw). Each output entry includes the raw binaryUUID + offset for downstream dSYM symbolication. Returns 3 most-actionable sections + cross-tool chain hints (e.g. objc_release top frame -> findCycles, sqlite top frame -> analyzeHangs with main-thread-violation classifier). No symbolication in v1; that's a separate tool. Simulator does NOT generate MetricKit payloads (Apple-side limitation) — frame this as post-mortem analysis. New in v1.18.
Inputs: payloadPath, payloadDir, payloadJson, topN, +1 more.
analyzeNetworkActivityAnalyze HTTP / connection activity from a Network traceA
[mg.trace] Parse the network-connections schema from a .trace recorded with a Network template. Returns per-request URL/host, method, status code, response time, bytes in/out. Top-N rankings by duration (which calls blocked the user) and by bytes (which calls bloat the budget) plus per-host aggregates surfacing chatty SDKs. v1.14+.
Inputs: tracePath (required), topN, minBytes, outputFormat.
analyzeTimeProfileAnalyze a Time Profiler traceA
[mg.trace] Export the time-profile schema from a .trace bundle and return top symbols by sample count. Note: heavy/unsymbolicated traces may crash xctrace export — when that happens, the tool returns a notice field with workarounds (open in Instruments first to symbolicate, or re-record shorter).
Inputs: tracePath (required), topN, outputFormat.
bootAndLaunchForLeakInvestigationBuild, boot, install, and launch an iOS app for leak investigationA
[mg.build] Single-call orchestration that runs xcodebuild build (optional), boots the iOS Simulator, installs the .app, and launches it with MallocStackLogging=1 propagated via SIMCTL_CHILD_*. Required because leaks --outputGraph regressed on macOS 26.x and only works when the target was launched with malloc-stack-logging in its environment. Returns the host PID + simulator UDID + bundle id ready to chain into captureMemgraph. Auto-discovers BUILT_PRODUCTS_DIR, WRAPPER_NAME, EXECUTABLE_NAME, and PRODUCT_BUNDLE_IDENTIFIER from xcodebuild -showBuildSettings -json. Required: scheme and exactly one of workspace or project.
Inputs: scheme (required), workspace, project, configuration, +7 more.
captureMemgraphCapture a .memgraph from a running processA
[mg.memory] Wrapper around leaks --outputGraph. Resolves appName to a PID via pgrep -x (or accepts pid directly), then writes a .memgraph snapshot. Limitation: only works for processes running on the local Mac (Mac apps + iOS simulator). Does NOT work for physical iOS devices, use Xcode's Memory Graph button there.
Inputs: output (required), pid, appName.
captureScenarioStateCapture a labeled before/after snapshot for verify-fixA
[mg.scenario] Composite snapshot: writes a .memgraph, a .png screenshot, and a .ui.json accessibility tree into outputDir, all prefixed by label (e.g. before / after). Designed to bracket a fix or a replayScenario call so you can chain into diffMemgraphs and validate that a cycle actually closed. Sub-captures are best-effort: if leaks fails (macOS 26.x minimal-corpse), the screenshot + UI tree still complete and the captureMemgraph workaroundNotice is surfaced for follow-up. Required: simulatorUDID, outputDir, and exactly one of pid / appName.
Inputs: simulatorUDID (required), outputDir (required), pid, appName, +2 more.
classifyCycleClassify ROOT CYCLEs against known patternsA
[mg.memory] Match each ROOT CYCLE against a built-in catalog of 8 known antipatterns (TagIndexProjection cycle, ForEachState retention, Combine sink-store-self, Task-without-weak-self, NotificationCenter observer, viewmodel-wrapped-strong closure, UINavigationController host, _DictionaryStorage internal). Returns patternId, confidence, and a fixHint per cycle.
Pipeline: this is the killer tool — after the result, follow suggestedNextCalls which pre-translates each match to a Swift regex (swiftSearchPattern) + the captured class name (swiftGetSymbolDefinition). Discovery is data, not inference.
Inputs: path (required), maxResults.
cleanupTracesPreview and delete `.trace` bundles under TRACE_ROOTA
[ops] Triage and clean up .trace bundles produced by recordTimeProfile. Each bundle is typically tens to hundreds of MB; after a few sessions the trace root fills up fast and v1.8 had no built-in cleanup.
Default-safe: dryRun: true by default. The tool returns the list of candidates with path, sizeMB, and ageDays (sorted oldest-first) but deletes nothing. Pass dryRun: false only when the user has reviewed the candidates and authorized deletion.
Scope: restricted to MEMORYDETECTIVE_TRACE_ROOT by default. To clean up an arbitrary directory, pass root: <path> AND set MEMORYDETECTIVE_ALLOW_EXTERNAL_CLEANUP=1 in the env. Without the env var the tool returns ok: false with the failure reason and deletes nothing; destructive disk operations outside the configured boundary are default-deny.
Recursion boundary: the tool walks subdirectories looking for *.trace directories, but stops at the .trace boundary (does NOT descend INTO bundles). xctrace writes structured content inside (Run1, Form1.template, etc.) that must not be treated as nested bundles.
Use olderThanDays: N to keep recent traces and only target stale ones (e.g. older than 7 days). Omit to consider all bundles regardless of age.
Inputs: olderThanDays, dryRun, root, outputFormat.
compareTracesByPatternCompare before/after .trace bundles for a perf regression targetA
[mg.trace][mg.ci] Trace-side counterpart to verifyFix. Compares two .trace bundles for a specific perf category (hangs, animation-hitches, or app-launch) and emits a PASS/PARTIAL/FAIL verdict plus before/after stats and deltas. Apply thresholds: hangs PASS when longest is below hangsMaxLongestMs (default 0); hitches PASS when longest is below hitchesMaxLongestMs (default 100ms — Apple's user-perceptible threshold); app-launch PASS when total is below appLaunchMaxTotalMs (default 1000ms).
Pipeline: capture before/after .trace (via recordTimeProfile or Xcode), then point this at the pair. The natural followup to a hangs/jank/launch fix PR.
Inputs: before (required), after (required), category (required), thresholds, +2 more.
countAliveCount instances by classA
[mg.memory] Count how many times each class appears in a .memgraph's leaked nodes. Provide className (substring) for a single number, or omit it to get the top N most-leaked classes. Use this to confirm whether a fix actually reduced instance counts.
Inputs: path (required), className, topN, includeReferenceTree, +5 more.
detectLeaksInXCTestRun an XCTest unit-test bundle with leak detection (CI-runnable)A
[mg.ci] Sibling to detectLeaksInXCUITest, targeting XCTest unit-test schemes. Build for testing, launch the test bundle with an optional -only-testing:<TestTarget>/<TestClass>[/<testMethod>] filter, poll for the runner process (xctest by default, configurable via processName for app-hosted test bundles), capture a baseline .memgraph once the runner appears, run the test to completion, capture an after .memgraph, and diff. Returns passed: false when new ROOT CYCLE blocks appear that are not in the allowlistPatterns list. Per-test granularity: call once per test method with different testCaseFilter values; aggregation is the caller's responsibility, keeping the response tied to a single, well-defined before/after pair. If the runner exits before the after-capture window (common for fast unit tests with no host), the response carries an explicit failureReason pointing at the tearDown workaround. Designed for CI gating: non-zero exit code on failure.
Inputs: scheme (required), workspace, project, destination, +7 more.
detectLeaksInXCUITestRun an XCUITest with leak detection (CI-runnable)A
[mg.ci] Build the workspace for testing, launch the test cycle, capture a baseline .memgraph once the app appears, run the test to completion, capture an after .memgraph, and diff. Returns passed: false when new ROOT CYCLE blocks appear that aren't in the allowlistPatterns list. Designed for CI gating: non-zero exit code on failure.
Inputs: workspace (required), scheme (required), testIdentifier (required), appName (required), +5 more.
diffMemgraphsDiff two memgraph snapshotsA
[mg.memory] Compare a baseline .memgraph (before) against a comparison .memgraph (after). Returns total leak/byte deltas, classes whose counts increased or decreased, and ROOT CYCLE signatures bucketed into newInAfter / goneFromBefore / persisted. The killer feature for verifying that a fix actually worked.
Inputs: before (required), after (required), outputFormat.
findCyclesFind ROOT CYCLE blocks in a .memgraphA
[mg.memory] Extract just the ROOT CYCLE blocks from a .memgraph as flattened chains (depth + edge + retainKind + className + address). Optionally filter to cycles touching a specific class name (substring match). Use this when you want to inspect chains without the noise of standalone leaks.
Inputs: path (required), className, maxDepth, verbosity.
findRetainersFind what retains a classA
[mg.memory] Walk the cycle forest from a .memgraph and return every retain chain that ends in a node whose className contains the given substring. Useful for answering "who is keeping alive?". Returns paths from a top-level node down to the matching node.
Inputs: path (required), className (required), maxResults, includeReferenceTree.
getInvestigationPlaybookGet the canonical tool sequence for a known investigation kindA
[meta] Returns a versioned, declarative pipeline for a known investigation flow (memgraph-leak, perf-hangs, ui-jank, app-launch-slow, verify-fix). Each step has a tool name, purpose, and argsTemplate. Use this once at the start of an investigation so any LLM agent can follow the right sequence without rediscovering it from individual tool descriptions.
Inputs: kind (required).
inspectTraceInspect a .trace bundle's TOC + suggest analyzersA
[mg.discover] Single-call orientation tool for .trace bundles. Runs xcrun xctrace export --xpath '/trace-toc/run' and returns the schemas present (potential-hangs, animation-hitches, time-profile, allocations, app-launch, ...), their row counts, the device model, the OS version, the template name, the recording timestamp, and a suggestedNextCalls[] array mapping each populated schema to its matching analyze* tool with pre-populated args. Use this as the FIRST call when handed a .trace so you do not have to chain 5 analyzers blindly. Empty traces return schemas: [] with a diagnosis pointing at Instruments.app for manual triage. Fallback path: when /trace-toc/run returns non-zero, retries with /trace-toc (older xctrace versions).
Inputs: tracePath (required).
listTraceDevicesList physical devices and simulatorsA
[mg.discover] Run xcrun xctrace list devices and return parsed devices/simulators with their UDIDs. The LLM should call this before recordTimeProfile to discover the right UDID without asking the user. Set includeOffline: true to include disconnected devices.
Inputs: includeOffline.
listTraceTemplatesList xctrace recording templatesA
[mg.discover] Run xcrun xctrace list templates and return parsed standard + custom templates. Useful when picking a template name for recordTimeProfile (e.g. "Time Profiler", "Animation Hitches", "Allocations").
Inputs: no named parameters.
logShowQuery macOS unified logging (one-shot)A
[mg.log] Wrap log show --style compact --last <window> with optional NSPredicate filter, process and subsystem sugar. Returns parsed entries (timestamp, type, process, pid, subsystem, category, message) bounded by maxEntries. Use this to look back at app logs without leaving chat.
Inputs: last, predicate, process, subsystem, +2 more.
logStreamStream macOS unified logging for a bounded windowB
[mg.log] Wrap log stream --style compact for a bounded duration (≤60 s — MCP requests should not block longer). Returns parsed entries collected during the window. Useful for capturing a specific user flow without setting up a full Console.app session.
Inputs: durationSec, predicate, process, subsystem, +2 more.
reachableFromCycleCount instances reachable from a specific cycle rootA
[mg.memory] Cycle-scoped reachability + class counting. Answers questions like "how many NSURLSessionConfiguration instances are reachable from the cycle rooted at DetailViewModel?" — distinguishing the actual culprit (the cycle root) from its retained dependencies. Pick a cycle by zero-based cycleIndex or by rootClassName substring. Returns per-class counts ranked by occurrence, plus the total reachable node count.
Inputs: path (required), cycleIndex, rootClassName, className, +2 more.
recordTimeProfileRecord a Time Profiler traceA
[mg.trace] Wrapper around xcrun xctrace record. Capture a .trace bundle from a running app on a device or simulator. Required: exactly 1 of deviceId/simulatorId, exactly 1 of attachAppName/attachPid/launchBundleId, an output path ending in .trace. Defaults: template = "Time Profiler", durationSec = 90.
Inputs: output (required), template, deviceId, simulatorId, +4 more.
recordViaInstrumentsAppRecord a .trace via Instruments.app GUI (macOS 26.x workaround)A
[mg.build] Open Instruments.app, prompt the user to record + save a .trace, then poll a watchDir for the new bundle and chain into inspectTrace. The macOS 26.x escape hatch: xcrun xctrace record wedges on this OS but Instruments.app GUI still produces valid traces. Returns instructions[] for the user-in-loop step, tracePath when found, plus a chained inspectTrace summary. Times out after timeoutSec (default 600s). v1.16+.
Inputs: template, watchDir, timeoutSec, preexistingTraces.
renderCycleGraphRender a retain cycle as Mermaid or DOT graphA
[mg.render] Read a .memgraph, pick a ROOT CYCLE by index, and emit the chain as a Mermaid graph definition (default — embeddable in markdown / GitHub) or a Graphviz DOT file. App-level classes are highlighted; CYCLE BACK terminators are styled distinctly. Use cycleIndex to render cycles other than the first.
Inputs: path (required), cycleIndex, format, maxDepth, +1 more.
replayScenarioReplay a UI scenario to amplify a suspected leakA
[mg.scenario] Drive the iOS Simulator through a sequence of UI actions (tap, swipe, wait, type) and optionally repeat the sequence N times to amplify a leak that only manifests after iteration. Tied to verify-fix: pair with captureScenarioState before/after to make leak reproductions deterministic. Soft dependency on axe (https://github.com/cameroncooke/AXe) — when missing, returns a structured workaroundNotice with install instructions. Tap targets accept label, elementId, or explicit coords.
Inputs: simulatorUDID (required), actions (required), repeat, settleBetweenActionsMs, +2 more.
summarizeTraceSingle-call cross-schema summary card for a .trace bundleA
[mg.synthesize] The trace-to-summary-card-in-one-call play. Chains inspectTrace + the matching analyze* tools (potential-hangs, animation-hitches, time-profile, allocations, app-launch) and returns BOTH a structured per-area result AND a pre-rendered compact markdown card (< 10 KB at default settings). Use this as the FIRST call when handed a .trace if you want one synthesis pass instead of chaining 5-6 analyzers manually. The markdown card carries a 1-sentence headline naming the biggest user-impact finding, then per-area sub-sections, then suggestedNextCalls[] for drilling in. Empty schemas are suppressed from the card to reduce noise. Failed analyzers (e.g. xctrace SIGSEGV on time-profile) surface inline with their workaround notice. Pass verbose: true to expand each section's top-N from 5 to 15+. Pass focus: "hangs" | "hitches" | "allocations" | "launch" to bias the summary toward a specific area.
Inputs: tracePath (required), focus, verbose.
swiftFindSymbolReferencesFind every reference to a Swift symbolA
[mg.code] Locates the symbol's declaration in filePath, then asks SourceKit-LSP for textDocument/references. Returns every callsite + capture across the project, with a snippet of each line. Requires an IndexStoreDB at <projectRoot>/.build/index/store for cross-file references — build it with swift build -Xswiftc -index-store-path -Xswiftc <projectRoot>/.build/index/store. The result includes a needsIndex: true hint when the index is missing.
Inputs: symbolName (required), filePath (required), projectRoot, includeDeclaration.
swiftGetHoverInfoGet type info / docs at a Swift source positionA
[mg.code] SourceKit-LSP textDocument/hover at a (line, character) position. Returns the markdown / plaintext hover content plus a best-effort extracted declaration fragment. Use to disambiguate self captures: a class self in a closure can leak; a struct self can't.
Inputs: filePath (required), line (required), character (required), projectRoot.
swiftGetSymbolDefinitionLocate a Swift symbol's source declarationA
[mg.code] Find the file:line where a Swift symbol (class, struct, enum, protocol, func, var, etc.) is declared. Pre-scans candidatePaths (or hint.filePath) with a fast regex first, then asks SourceKit-LSP for jump-to-definition. Returns the position even when LSP can't follow through. Use after findRetainers / classifyCycle surface a class name from a memgraph cycle to land in the actual source file.
Inputs: symbolName (required), hint, projectRoot, candidatePaths.
swiftGetSymbolsOverviewList top-level symbols in a Swift fileA
[mg.code] Cheap orientation: returns the top-level symbols (classes, structs, enums, protocols, free functions) declared in a Swift file via SourceKit-LSP's documentSymbol. Set topLevelOnly: false for nested children too. Useful right after swiftGetSymbolDefinition lands you in a new file.
Inputs: filePath (required), projectRoot, topLevelOnly.
swiftSearchPatternRegex-search a Swift file (no LSP)A
[mg.code] Pure regex search over a file's contents — no SourceKit-LSP, no IndexStoreDB. Catches what LSP misses: closure capture lists ([weak self], [unowned self]), Task { ... self ... } blocks, and any other pattern the agent constructs from a leak chain. Returns matches with line/character positions and a trimmed snippet.
Inputs: filePath (required), pattern (required), flags, maxMatches.
verifyFixVerify a fix actually closed the targeted retain cycleA
[mg.memory] Cycle-semantic diff. Classifies both before and after .memgraph snapshots and emits a per-pattern PASS/PARTIAL/FAIL verdict plus bytes freed and instances released. Use as a CI gate: if expectedPatternId is provided, expectedPatternVerdict tells you in one field whether the fix landed.
Pipeline: this is the natural followup to classifyCycle after you've shipped a fix. Capture a fresh .memgraph, point this at the before/after pair.
Inputs: before (required), after (required), expectedPatternId, expectedAliveClasses, +2 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 42 tools
Most tools are clearly distinct in purpose, but several memgraph-analysis tools (analyzeMemgraph, findCycles, classifyCycle, reachableFromCycle) overlap in theme and input, though they return different views. Descriptions are detailed enough to help an agent avoid misselection, but the boundary between some trace analyzers (e.g., analyzeAllocations vs analyzeMemoryFootprint) could still confuse.
All 42 tools follow a consistent camelCase convention with a clear verb-noun pattern (analyze*, capture*, list*, find*, etc.). The naming makes it easy to predict tool behavior from its name, and there are no mixed conventions or chaotic verb styles.
42 tools is far beyond the typical well-scoped range and even exceeds the 'heavy' threshold. The server covers many subdomains (leaks, traces, logs, code navigation, CI), but many analyzers could be consolidated into parameterized tools (e.g., a single analyzeTrace with a schema argument). The high count increases selection difficulty and cognitive load.
The tool surface covers the full investigation lifecycle: capture (captureMemgraph, recordTimeProfile, bootAndLaunchForLeakInvestigation), analysis (findCycles, analyzeHangs, analyzeAppLaunch), diffing/verification (diffMemgraphs, verifyFix, compareTracesByPattern), CI integration (detectLeaksInXCTest/UI), and even production diagnostics (analyzeMetricKitPayload). No obvious gaps for the stated purpose.
Maintenance
Related MCP Connectors
A Model Context Protocol (MCP) application for automated GitHub PR analysis and issue management.…
Scan iOS and Android project files for common App Store and Play review issues over MCP.
Deterministic memory & code intelligence MCP for 15 IDEs (100% LongMemEval in 12.9ms, 0 LLM tokens).
MEOK MCP Hardening MCP — automated security red-team for any MCP server. Maps OWASP LLM Top 10
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceMCP server and CLI for iOS development — build, test, automate, and diagnose from any AI agent or terminal.2MIT
- AlicenseNot gradedqualityDmaintenanceLocal-first CLI and MCP server for turning Xcode Instruments artifacts into bounded, agent-sized evidence, with support for analyses like Time Profiler, Allocations, Network, and more.2MIT
- FlicenseAqualityDmaintenanceEnables natural language profiling of iOS apps using xctrace (Instruments) for performance analysis like launch time, memory leaks, CPU usage, and network requests.12-
- FlicenseNot gradedqualityDmaintenancemacOS native profiling and debugging MCP server that wraps sample, atos, vmmap, leaks, and lldb to enable an agent to profile, symbolicate, and debug running or freshly launched processes without manual path or port handling.1-