Apple Debug MCP is a privileged local developer tool. It can start debuggers and, when explicitly enabled, alter an authorized target. The default process is read-only discovery and inspection; every state-changing boundary is opt-in and scoped.
- The MCP client is the local caller and may start the server through stdio or connect to the authenticated loopback daemon endpoint.
- The server inherits the caller’s macOS identity and developer-tool permissions.
- Xcode, LLDB, Simulator, CoreDevice, and unified logging are external authority-bearing tools.
- Checkpoint artifacts are local evidence files; replay relaunches only the previously authorized local macOS launch target and does not restore arbitrary process or kernel state.
- Kernel-lab sessions are a separate user-authorized two-machine boundary; KDK, KDP, SIP, boot security, and target preparation remain outside MCP automation.
- A physical iOS device and its development-signed application are separate trust targets.
- Stock App Store applications are outside the supported target boundary.
| Boundary | Invariant | Enforcer | Verification |
|---|---|---|---|
| MCP tools | Unknown tools fail closed | ToolCatalog.call |
make check |
| Toolchain | Only fixed executable paths and explicit argument arrays are used | ToolchainProbe, adapters |
CapabilitiesTests and code review |
| Artifact files | Mach-O and crash inputs are bounded; binary diff and symbolication resolve only an explicitly supplied absolute regular file, .app main executable, or direct .dSYM DWARF payload |
MachOInspector, AppleArtifactLayoutResolver, CrashReportAnalyzer, AppleBinaryDiffService, SymbolicationService |
MachOIdentityTests, AppleArtifactLayoutTests, CrashSymbolicationIdentityTests, symbolication-crash-smoke |
| Target launch | Requires APPLE_DEBUG_ALLOW_TARGET_LAUNCH=1 and a regular target |
DebugPolicy.validateLaunchTarget |
DebugSessionTests, macOS fixture smoke |
| Target attach | Requires APPLE_DEBUG_ALLOW_TARGET_ATTACH=1 and a positive process ID |
DebugPolicy.validateAttach |
DebugSessionTests, iOS Simulator smoke |
| Expression evaluation | Requires APPLE_DEBUG_ALLOW_EVALUATE=1 and a 16 KiB expression limit |
DebugPolicy.validateEvaluate |
DebugSessionTests and fixture smoke |
| Memory write | Requires APPLE_DEBUG_ALLOW_MEMORY_WRITE=1 and a 4096-byte limit |
DebugPolicy.validateMemoryWrite |
DebugSessionTests |
| Variable write | Requires APPLE_DEBUG_ALLOW_VARIABLE_WRITE=1 and bounded name/value fields |
DebugPolicy.validateVariableWrite |
DebugSessionTests |
| Debugger commands | MCP exposes named DAP requests, not arbitrary shell execution | ToolCatalog, DebugSessionManager |
Tool schema review and make check |
| Assembly generation | Assembly input is bounded, self-contained, and compiled to a temporary object with fixed clang arguments; generated bytes are inert until a separate memory-write grant is used | AppleAssemblerService |
assembler-smoke and AppleAssemblerTests |
| Assembly patching | Code bytes can be written only through expected-byte transactional patch/rollback and APPLE_DEBUG_ALLOW_MEMORY_WRITE=1 |
apple_debug_patch_assembly, DebugSessionManager.patchMemory |
macOS fixture smoke |
| Runtime diagnostics | Heap/leaks/malloc-history/sample commands use fixed allowlisted modes and attach authorization; no arbitrary flags or shell execution are accepted | RuntimeDiagnosticsService |
runtime-diagnostics-smoke |
| Reverse and kernel boundaries | Unsupported reverse/time-travel and kernel memory operations are reported fail-closed; the server does not emulate or bypass Apple security controls | ReverseExecutionService, AppleKernelCapabilityService |
reverse-capability-smoke |
| Checkpoint replay | Checkpoints are bounded, non-overwriting JSON artifacts; replay requires a prior local launch configuration and stops at a recorded source location without claiming exact state restoration | CheckpointReplayManager, DebugSessionManager |
ReplayTests, replay-smoke |
| Kernel lab | Remote KDP uses a fixed target-create/symbols/kdp-remote sequence, an explicit APPLE_DEBUG_ALLOW_KERNEL_LAB=1 grant, validated paths/host, and read-only DAP inspection; memory writes and arbitrary commands are unavailable |
KernelLabSessionManager, KernelLabService |
AppleKernelCapabilitiesTests, reverse-capability-smoke, external KDK lab evidence when available |
| Static binary analysis | CFG/shared-cache parsers read bounded regular files and never execute or rewrite them; dyld runtime cross-references are direct-pointer evidence and chained fixups are metadata-only | AppleControlFlowService, AppleDyldSharedCacheService |
control-flow-smoke and dyld unit test |
| Memory snapshots | vmmap snapshots write only to an explicit non-existing JSON path and require target attach authorization | AppleMemoryMapService |
memory-map-smoke |
| Simulator environment | Fixed simctl operations validate enum values, paths, payload sizes, and mutation policy; arbitrary spawn/shell is not exposed | AppleSimulatorEnvironmentService |
simulator-environment-smoke |
| Repro bundles | Evidence capture writes to an explicit new directory with bounded copied trace/crash inputs and no device erase/pairing | AppleReproBundleService |
repro-bundle-smoke |
| Plugin boundary | External JSON manifests are read-only metadata; no arbitrary dylib or executable plugin is loaded by MCP | AppleDebugPluginManifestService, AppleDebugPluginRegistry |
plugin-smoke |
| Plugin host | Candidate XPC service executables must pass codesign audit and optional team-ID matching; the production transport connects to an independently signed App Sandbox XPC service with explicit grant, timeout, and bounded payloads. The profile transport is legacy-only and explicit |
ApplePluginHostService, AppleDebugPluginXPCProtocol |
plugin-xpc-smoke and plugin-smoke |
| Session cleanup | Failed launch, explicit close, and server shutdown terminate only owned adapters | DebugSessionManager, LLDBDAPSession.stop |
Session tests and fixture smoke |
| Menu bar supervisor | The menu bar app launches only the bundled apple-debug-mcp --daemon executable, owns its lifecycle, writes output to the user log directory, and surfaces only the authenticated loopback endpoint |
MCPServerController, AppleDebugDaemonEndpoint, signed app bundle, SMAppService.mainApp |
make mcp-daemon-smoke, script/build_and_run.sh --verify, package inspection, and menu bar UI smoke |
| Simulator mutation | Known UDID and APPLE_DEBUG_ALLOW_SIMULATOR_MUTATION=1 are required |
SimulatorService.mutate |
AppleSimulatorTests, iOS smoke |
| Arbitrary Simulator UI probe | Generated XCUITest project may launch/interact only with the explicitly supplied installed bundle ID and selected Simulator; no target app source or binary is modified | SimulatorUIService, fixed generated project, mutation gate |
ios-arbitrary-ui-smoke |
| Coordinate UI actions | Coordinate taps/presses/swipes are normalized, bounded, mutation-gated XCUITest events; they do not bypass accessibility, app sandbox, or target entitlements | SimulatorUIService.validate, generated XCTest runner |
ios-ui-tree-smoke, ios-arbitrary-ui-smoke |
| Physical devices | Install/launch/process mutation/sysdiagnose require APPLE_DEBUG_ALLOW_DEVICE_MUTATION=1, a known identifier, paired/available development state, a successful CoreDevice tunnel refresh when needed, and an existing signed app |
AppleDeviceService.mutate, refreshCoreDeviceTunnels |
AppleDeviceTests, live inventory, and authorized physical smoke |
| Physical-device LLDB | Session creation requires APPLE_DEBUG_ALLOW_DEVICE_DEBUG=1; CoreDevice sessions use UUID selection plus named device process attach --pid commands, optional signed .app symbol loading, and bounded initial-stop synchronization; legacy xcdevice UDIDs additionally require APPLE_DEBUG_ALLOW_DEVICE_MUTATION=1, a signed .app path, and the installed ios-deploy tool. The legacy path owns a generated LLDB-Python bridge and neither path loads arbitrary user scripts |
DebugSessionManager.create, LegacyDeviceDebugTransport, LLDBDAPSession |
DebugSessionTests, AppleDeviceTests, LegacyDeviceDebugTransportTests, authorized physical smoke |
| Xcode builds | Require APPLE_DEBUG_ALLOW_XCODE_BUILD=1 and explicit project/scheme/configuration/destination |
XcodeService.build |
AppleXcodeTests |
| Unified logs | Duration is bounded, predicates are single-line/limited, and output is capped at 2 MB | AppleLogService |
AppleLogsTests |
| Network | The daemon binds only to 127.0.0.1, publishes a random bearer token in a user-only endpoint file, validates localhost origin/host, and caps HTTP request bodies; it does not expose LAN/TLS/remote access |
AppleDebugMCPDaemonServer, AppleDebugDaemonEndpoint, NIO adapter |
make mcp-daemon-smoke and endpoint permission tests |
| Secrets | No credentials or tokens are stored in the repository | Repository contract | make check and review |
Symbolication is fail-closed and non-executing. The caller supplies every
artifact path; the resolver requires an absolute path, canonicalizes it, and
allows a bundle child or symlink only when its canonical target remains inside
the supplied .app or .dSYM root. The resolver inspects the app main
executable or a direct regular DWARF entry only. It does not recurse into
frameworks, search arbitrary symbol roots, or select a payload from an image
name.
Each thin Mach-O slice must contain exactly one non-zero LC_UUID, and the
normalized UUID is paired with the exact CPU subtype architecture. Crash
Binary Images and .ips image records must provide the same identity. An
executable/app image and a dSYM are complementary providers; when no
executable/app is supplied, one exact dSYM may serve as the identity and debug
provider. Multiple distinct providers in one role are ambiguous. Wrong UUID, wrong architecture, missing
debug data, ambiguous providers, malformed layouts, and changed file identity
remain typed non-success results. The payload path, device/inode when
available, size, and modification time are snapshotted and revalidated before
atos.
Crash locations are either absolute or image-relative. .ips image-relative
offsets use the explicitly indexed image and checked base + offset; text
reports map an absolute frame to exactly one Binary Images range. Preferred
__TEXT VM address, runtime base, slide, and atos input remain separate
values. Overflow, underflow, missing load data, and out-of-range addresses are
rejected before tool invocation. atos is fixed to /usr/bin/xcrun with an
argument array, a 4 KiB batch diagnostic cap, 5 seconds per batch, 30 seconds
cumulative, at most 32 batches, and no invocation for known pre-resolution
identity/address failures.
Post-fix verification is casebook orchestration, not a generic MCP execution
surface. scripts/adaptive_verification.py defaults to quick (one baseline and
one candidate, 5 seconds per attempt, 15 seconds cumulative); standard is at
most two plus two at 15/60 seconds and strict is at most three plus three at
30/180 seconds. A monotonic deadline is passed into each callback, including
fresh-state and cleanup operations. Automatic escalation is limited to typed
timing, concurrency, state-leakage, inconsistent-observation, or known-
flakiness evidence; explicit levels are recorded as policy choices.
The runner never treats an arbitrary replay manifest as identity. Reuse
requires a computed executable SHA-256, Mach-O UUID/architecture, build/source,
environment, target, and scenario/oracle match. A decisive candidate or
guardrail regression is failed even if cleanup fails afterward. Missing,
stale, contaminated, conflicting, exhausted, or non-fresh evidence without a
decisive regression is inconclusive; verified requires reliable baseline,
candidate thresholds, guardrails, fresh/restored/notRequired state, and cleanup.
The device adapter reports CoreDevice pairing/tunnel state and legacy xcdevice availability before any mutation. A paired CoreDevice may require the read-only lock-state probe to activate its tunnel; a successful probe, not a device name or bundle identifier, is the authorization signal. CoreDevice launch uses --terminate-existing, returns a PID only from the bounded process inventory, and CoreDevice process actions accept only positive PIDs and an allowlisted signal set. CoreDevice/legacy session cleanup owns only the adapter and explicitly selected fixture process. Legacy lifecycle and LLDB-DAP mutation require ios-deploy, an existing signed .app, the explicit device-debug grant, and the separate device-mutation grant. Neither transport loads arbitrary user scripts.
Do not use the server to access software or devices without authorization. Security findings should be recorded in the active ExecPlan and fixed with a reproducing test or a documented blocker. Release, signing, notarization, and external issue reporting require explicit authorization.