MCP server for remote debugging and UI automation of iOS/macOS apps via WebSocket relay. Provides screenshot capture, UI tree traversal, synthetic event injection, and performance monitoring.
Strong tool definitions with comprehensive descriptions (avg 180 chars, well above 10-char minimum). All 12 tools have clear, action-oriented names (verb_noun pattern: ping, echo, get_stats, read_logs, prefs, get_perf, ui_traverse, find_objects, screenshot, ui_click, tap_screen, swipe). Input schemas present for all tools with proper JSON Schema structure, types, and constraints (enums, min/max bounds). However, output schemas are not formally documented, responses are inferred from code. Parameter descriptions are detailed and include constraints (e.g., 'limit (1-500, default 50)', 'normalized coordinates 0..1'). Error handling is present but inconsistent: some tools throw TaskException with actionable messages (e.g., 'NOT_FOUND' with recovery hint), others return structured failure objects. Security is well-handled: no secrets in parameters, permission gates via ensureActionsEnabled(). Composition is clean, each tool has one responsibility. Main gaps: no output schema documentation, no pagination support for read_logs (capped at 500 but no cursor/offset), and missing per-tool error classification guidance.
Returns the payload unchanged. Useful for smoke-testing the relay loop.
Find views by text / key (accessibilityIdentifier) / view_type — substrings, case-insensitive, all given filters must match. Returns up to limit (1-200, default 50) objects with rect, center and normalized coordinates.
Process memory stats (resident size and phys footprint) from the kernel. Apple platforms expose no frame-timing API to ordinary apps, so no frame/fps numbers are reported.
Basic runtime stats: lib/app version, platform and OS version, device model, uptime, task count, connection state.
Round-trip liveness probe; echoes sentAt back with pong=true.
UserDefaults get/set/delete/list. action is required (get|set|delete|list); key is required for get/set/delete. set supports value_type coercion (string|int|float|bool) — {"value":"42","value_type":"int"} stores int 42. Values are echoed back with their actual stored type.
Output schemas not formally documented. Tool descriptions explain what is returned (e.g., 'returns rect, center, normalized coordinates'), but no JSON Schema for response objects. LLMs cannot reliably parse or chain results without explicit output type definitions.
read_logs accepts limit (1-500, default 50) but no pagination cursor or offset. Large result sets cannot be iterated; agent must re-query with since_ms to fetch older logs, which is inefficient and error-prone.
Error handling inconsistent across tools. Some throw TaskException with actionable messages ('NOT_FOUND' + recovery hint), others return structured failure objects. No standardized error classification (retryable vs user-fixable vs fatal) to guide agent recovery.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | B | 70 | 2026-07-28+ | v2 |
Apple-side logs collected by this SDK since app start (newest first). Apple OSes expose no past-console API, so only logs the app forwards via OmniDebugLink.recordLog/recordError plus uncaught NSExceptions are captured — there is no history from before link start and no system log. Filter with level (log|warning|error), contains, limit (1-500, default 50) and since_ms.
Capture the key window's content view as JPEG via cacheDisplay. max_size (64-4096, default 1280) caps the longest edge in pixels. Returned in the __odl_file envelope (image/jpeg, base64), compressed automatically to stay under the relay's single-message limit. Pixel origin is TOP-LEFT, matching the normalized coordinate space of tap_screen/swipe. GPU-rendered surfaces (Metal/OpenGL layers) are not captured by cacheDisplay.
Programmatic scroll of the NSScrollView under the start point, animated over duration_ms. On macOS, dragging with the LEFT BUTTON selects/rubber-bands — scrolling comes from scrollWheel events. Synthetic scroll events cannot be routed in-process, so swipe uses programmatic scroll (same approach as iOS; the mechanism is reported honestly in the result).
Mouse click at normalized coordinates, injected into this process as a synthetic NSEvent (no accessibility permission needed; the physical cursor does not move). Coordinates are floats 0..1, origin at the TOP-LEFT corner of the key window. For a pixel (px, py) in a returned screenshot of size W×H (image origin top-left): x=(px+0.5)/W, y=(py+0.5)/H (no vertical flip). Returns the resolved point position and window size.
Click a view: NSControl targets get performClick() (direct programmatic click); anything else gets a synthetic mouse click at its center, injected into this process. Locate by key / text / view_type (substrings, case-insensitive) + index, or exact path; finder and click run atomically in one call. Prefer key. Use tap_screen to click by normalized coordinates instead.
Dump the NSView hierarchy of the key window. Default flat=true returns a flat node list (depth/name/key/text/rect/center(px and 0..1 normalized)/path, token-friendly); flat=false returns a nested tree. AppKit's bottom-left origin is converted to TOP-LEFT so geometry matches screenshots and the other Apple platforms. SwiftUI content appears through its NSHostingView. Optional path starts at a node, depth limits levels below it. Capped at 3000 nodes.
echo tool accepts additionalProperties=true, allowing arbitrary payloads. While useful for testing, this violates the constrained-input pattern and makes schema validation impossible. Consider restricting to a known set of test fields.
No confirmation or dry-run support for destructive operations (prefs delete, ui_click on critical controls). Agents can accidentally delete user preferences or trigger unintended UI actions without a safety gate.