Skip to main content
Glama
carloshpdoc

memorydetective

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault

No arguments

Capabilities

Features and capabilities supported by this server

CapabilityDetails
tools
{
  "listChanged": true
}
prompts
{
  "listChanged": true
}
resources
{
  "listChanged": true
}

Tools

Functions exposed to the LLM to take actions

NameDescription
analyzeMemgraphA

[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.

findCyclesA

[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.

findRetainersA

[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.

countAliveA

[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.

diffMemgraphsA

[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.

analyzeAbandonedMemoryA

[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.

cleanupTracesA

[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.

classifyCycleA

[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.

analyzeHangsA

[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.

analyzeTimeProfileA

[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).

listTraceDevicesA

[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.

listTraceTemplatesA

[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").

inspectTraceA

[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).

summarizeTraceA

[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.

recordTimeProfileA

[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.

captureMemgraphA

[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.

bootAndLaunchForLeakInvestigationA

[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.

replayScenarioA

[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.

captureScenarioStateA

[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.

analyzeAnimationHitchesA

[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.

analyzeAllocationsB

[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.

analyzeAppLaunchA

[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.

analyzeNetworkActivityA

[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+.

analyzeMemoryFootprintA

[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+.

analyzeEnergyImpactA

[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+.

analyzeLeakTimelineA

[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+.

recordViaInstrumentsAppA

[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+.

analyzeMetricKitPayloadA

[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.

renderCycleGraphA

[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.

logShowA

[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.

logStreamB

[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.

detectLeaksInXCUITestA

[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.

detectLeaksInXCTestA

[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.

reachableFromCycleA

[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.

swiftGetSymbolDefinitionA

[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.

swiftFindSymbolReferencesA

[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.

swiftGetSymbolsOverviewA

[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.

swiftGetHoverInfoA

[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.

swiftSearchPatternA

[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.

getInvestigationPlaybookA

[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.

verifyFixA

[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.

compareTracesByPatternA

[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.

Prompts

Interactive templates invoked by user choice

NameDescription
investigate-leakRun the canonical 6-step memgraph-leak investigation: analyzeMemgraph → classifyCycle → reachableFromCycle → swiftSearchPattern → swiftGetSymbolDefinition → swiftFindSymbolReferences.
investigate-hangsDiagnose main-thread hangs from a `.trace` recorded with the Time Profiler or Hangs template.
investigate-jankDiagnose dropped frames from a `.trace` recorded with the Animation Hitches template.
investigate-launchDiagnose cold/warm launch slowness from a `.trace` recorded with the App Launch template.
summarize-traceCall `summarizeTrace` on the user-supplied .trace and present the markdown card. Surfaces hangs, hitches, time-profile hotspots, allocations, app launch, and cross-correlations in one pass. Use this as the FIRST step on any new .trace.
investigate-metrickitRun the post-mortem flow on Apple MetricKit `.mxdiagnostic` payloads collected from real-device TestFlight / App Store builds. Calls `analyzeMetricKitPayload` to surface clustered crashes, hang hotspots, CPU / disk exceptions, then follows cross-tool chain hints into `findCycles` / `analyzeHangs` when the top frame matches a known shape. Use this when the user has `.mxdiagnostic` files, not a local repro.
verify-cycle-fixDiff a before/after pair of `.memgraph` snapshots to confirm a fix actually closed the originally-classified cycle.

Resources

Contextual data attached and managed by the client

NameDescription
SwiftUI .tag(...) closure-over-self cycleSwiftUI .tag(...) closure-over-self cycle
SwiftUI _DictionaryStorage<…WeakBox<AnyLocationBase>> internal cycleSwiftUI _DictionaryStorage<…WeakBox<AnyLocationBase>> internal cycle
SwiftUI ForEachState retained by tap-gesture closureSwiftUI ForEachState retained by tap-gesture closure
Closure capturing `_viewModel.wrappedValue` stronglyClosure capturing `_viewModel.wrappedValue` strongly
UIViewControllerRepresentable + UINavigationController host cycleUIViewControllerRepresentable + UINavigationController host cycle
Combine .sink/.assign closure capturing self via AnyCancellableCombine .sink/.assign closure capturing self via AnyCancellable
Swift `Task { }` body strongly capturing selfSwift `Task { }` body strongly capturing self
NotificationCenter observer block capturing selfNotificationCenter observer block capturing self
Combine `.assign(to: \.x, on: self)` capturing selfCombine `.assign(to: \.x, on: self)` capturing self
Swift `Task { }` inside a SwiftUI View capturing selfSwift `Task { }` inside a SwiftUI View capturing self
NotificationCenter observer never deregisteredNotificationCenter observer never deregistered
`delegate` property declared without `weak``delegate` property declared without `weak`
Timer.scheduledTimer(target:selector:) retains its targetTimer.scheduledTimer(target:selector:) retains its target
CADisplayLink retains its targetCADisplayLink retains its target
UIGestureRecognizer / UIControl `addTarget` retains targetUIGestureRecognizer / UIControl `addTarget` retains target
`NSKeyValueObservation` token retains its change handler`NSKeyValueObservation` token retains its change handler
URLSession retains its delegate stronglyURLSession retains its delegate strongly
SwiftUI `@EnvironmentObject` with back-reference to UIView/UIViewControllerSwiftUI `@EnvironmentObject` with back-reference to UIView/UIViewController
`AsyncStream` continuation retains self via producer / onTermination`AsyncStream` continuation retains self via producer / onTermination
`WKUserContentController.add(_:name:)` retains the handler`WKUserContentController.add(_:name:)` retains the handler
DispatchSource event handler closure retains selfDispatchSource event handler closure retains self
RxSwift `DisposeBag` retains subscription closures capturing selfRxSwift `DisposeBag` retains subscription closures capturing self
Realm `NotificationToken` retains its change closureRealm `NotificationToken` retains its change closure
Coordinator pattern: child holds parent stronglyCoordinator pattern: child holds parent strongly
CAAnimation retains its delegate (Apple's documented quirk)CAAnimation retains its delegate (Apple's documented quirk)
Custom CALayer delegate pointing at non-UIView ownerCustom CALayer delegate pointing at non-UIView owner
NSFetchedResultsController retains its delegateNSFetchedResultsController retains its delegate
@Observable class held as @State across modal presentation@Observable class held as @State across modal presentation
NavigationPath retains every element ever pushed into itNavigationPath retains every element ever pushed into it
`for await` over an infinite AsyncSequence pins self via the consuming Task`for await` over an infinite AsyncSequence pins self via the consuming Task
`for await ... in NotificationCenter.notifications(named:)` retains self forever`for await ... in NotificationCenter.notifications(named:)` retains self forever
Swift 6.2 `Observations { }` closure retains selfSwift 6.2 `Observations { }` closure retains self
WKScriptMessageHandler bridge: handler ↔ webview ↔ contentController cycleWKScriptMessageHandler bridge: handler ↔ webview ↔ contentController cycle
UIViewController alive in heap with no parent/presenting nav linkUIViewController alive in heap with no parent/presenting nav link
@Observable mutated inside `body`, triggering infinite re-render and a heap-side closure chain@Observable mutated inside `body`, triggering infinite re-render and a heap-side closure chain
SwiftData ModelContext retained by actor through DefaultSerialModelExecutorSwiftData ModelContext retained by actor through DefaultSerialModelExecutor

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