Headless stdio MCP server that lets an AI navigate Xcode Instruments .trace files without dumping raw xctrace XML into context.
Strong tool definitions with comprehensive descriptions and clear schemas. 29 tools all have descriptions (10-300+ chars, well above 20-char floor). Input schemas present for all tools with proper type definitions. Naming follows verb_noun convention consistently (open_trace, close_trace, list_instruments, query, aggregate, find, correlate, relate, timeline, call_tree, etc.). Output schemas documented in descriptions. Error handling via XctraceError with structured JSON responses. Main gaps: no tool annotations (readOnlyHint/destructiveHint/idempotentHint), no explicit parameter validation guidance in descriptions, some parameters lack min/max bounds (e.g., limit defaults to 50 but max 500 not always stated clearly), and no confirmation pattern for destructive ops (close_trace, stop_recording, update_config). Foundation Models lens tools (list_fm_requests, get_fm_request, etc.) are well-scoped with clear separation of concerns.
Group rows by one or more columns and measure (sum/count/avg/min/max) a numeric column. Returns grouped results sorted by measure descending, with topN limit. Use this for summaries, cost attribution, and bounded queries on huge schemas.
Build a call tree (stack trace hierarchy) from a schema with frame/stack columns. Returns a tree view of function calls with aggregated metrics (duration, count, etc.). Use this for profiling and understanding call hierarchies.
Close a trace session and free its resources (SQLite connection, caches, temp files). After this call, the sessionId is invalid.
Join two schemas by time window (intervals overlap events, or events fall within intervals). Returns rows from the first schema with matching rows from the second schema appended. Use this to correlate cause-and-effect across instruments (e.g. hitches with CPU samples, allocations with GC events).
Describe the columns, roles, and metadata for one instrument schema. Returns column names, types, engineering types, roles (e.g. 'duration', 'thread', 'address'), and per-column hints for aggregation/filtering.
No tool annotations (readOnlyHint, destructiveHint, idempotentHint) present in tool definitions. LLMs cannot distinguish safe read-only tools from destructive ones without explicit hints.
Destructive operations (close_trace, stop_recording, update_config) lack confirmation/dry-run pattern. Agents can irreversibly delete sessions or modify config without user approval.
Parameter bounds not consistently documented. 'limit' parameters state max (500) but min is implicit. 'topN' defaults to 10 but max 1000 not always clear in description text.
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 62 | <=2025-11-25 | v2 |
Search for rows matching a predicate (where clause). Returns matching rows sorted by a measure or column. Use this for filtered exploration, anomaly hunting, and bounded queries on huge schemas.
Filter FM requests by predicate — hasError, minDuration, noCitations, needsReformulation. Returns matching requests as compact one-liners (same format as list_fm_requests). `run` defaults to the most recent run. Prefer this over query when you need filtered results.
Search for a .trace file by name or pattern. Returns matching trace files with paths and metadata.
Get the current server configuration (recordings directory, cache directory, etc.).
Return the ordered event timeline for one FM request — all rows sharing the same model-request-id (Prompt phase, Resolve phase, …) with timing, resolve phase, and content summary. Useful for understanding multi-step request flow. `run` defaults to the most recent run. ⚠️ Not for single-row detail — use get_fm_request for individual row breakdown.
Return the full system prompt/instructions for one FM inference row. This is a large field (several KB) — only fetch when needed. `run` defaults to the most recent run. ⚠️ Not for request detail — use get_fm_request for timing, tokens, and error info.
Fetch full detail for one FM inference row by its tableIndex (from list_fm_requests). Returns one aggregate duration, agent, full prompt, parsed response JSON, token breakdown, resolve phase, error info — use get_fm_timing for the prefill-vs-generation split within that duration. instruction/instructions columns are intentionally omitted — they contain the full system prompt which can be several KB. Use get_fm_prompt when you need it explicitly. `run` defaults to the most recent run. ⚠️ Not for reading the system prompt — use get_fm_prompt for that (excluded here to preserve token budget).
Return the parsed response body for one FM inference row. The response column contains JSON (body, needsReformulation, referencedSections, …); this tool parses it so the agent reads structured data instead of an escaped JSON string. `run` defaults to the most recent run. ⚠️ Not for full request detail — use get_fm_request for timing, tokens, and error info.
Prefill-vs-generation latency split and cache stats for a request. Returns timing breakdown (prefill duration, generation duration, time-to-first-token, etc.) and prompt cache hit/miss info. `run` defaults to the most recent run. ⚠️ Not for full request detail — use get_fm_request for tokens, error info, and response.
Get the current status of an active recording (elapsed time, data size, etc.).
Fetch full detail for one row by its tableIndex (from query/aggregate/find results). Returns all columns, formatted values, and raw values for that row.
Get diagnostic information about the running server: package version, process start time, dist build time (ground truth for which code is loaded), and Node.js version. Use this to verify the server is running the expected build.
List devices available for recording (iPhone, iPad, Mac, etc.). Returns device names, model identifiers, OS versions, and availability status.
List Foundation Models inference requests as compact one-liners: start time, duration, agent name, prompt snippet (80 chars), token counts (including cachedTokens — 0 across requests that share a prompt prefix is a missed prompt-caching signal), error flag, and resolve phase. Each user request generates two rows — a Yellow 'Prompt' inference and an Orange 'Resolve' step; the resolve field ('1Prompt', '1Resolve', …) encodes which phase and which request number. Use get_row(tableIndex) for full detail on any row. `run` defaults to the most recent run. Prefer find_fm_requests when you need filtered results — it supports hasError, minDuration, noCitations, and needsReformulation predicates.
List the instruments (schemas) present in the trace. Each instrument is a named schema; some traces carry multiple runs of the same instrument. Returns schema names, run counts, and per-instrument metadata.
List available Instruments recording templates and their bundled instruments. Returns template names, descriptions, and which instruments each template records.
List available .trace files on disk (in the default recordings directory or a custom path). Returns trace file names, sizes, and modification times.
Open an Instruments .trace file and return a sessionId for subsequent queries. Ingest the trace's schema and run metadata, then run the eager-sweep detectors (a small curated allowlist of bounded schemas) to surface high-confidence findings before any row is fetched on demand. Returns sessionId, schema list, run list, and recommended findings.
Query one schema with optional filter, sort, and limit. Returns rows as a table with column metadata. Use this for exploratory browsing, sorted views, and time-window queries. ⚠️ Not for aggregation — use aggregate instead. ⚠️ Not for full-table scans on huge schemas (swiftui-updates, thermal-state, etc.) — use aggregate or find with a filter to stay bounded.
Join two schemas by equality or containment on specified columns. Returns rows from schemaA with matching rows from schemaB appended. Use this for cross-schema lookups (e.g. join a frame address to its symbol, or a process ID to process metadata).
Start a new Instruments recording on the specified device with the given template. Returns a recording ID and status. The recording runs until stop_recording is called.
Stop the current recording and save the resulting .trace file. Returns the path to the saved trace file and a sessionId for analysis.
Render a time-series view of one schema, bucketed by time interval. Returns a timeline of events/intervals grouped by time bucket, useful for seeing temporal patterns and distribution.
Update server configuration (recordings directory, cache directory, etc.). Changes persist to disk.
Complex filter/where parameter formats (object with {op, value} or array of {col, op, value}) lack explicit examples or enum constraints in descriptions. LLMs may struggle to construct valid filters.
Error handling returns structured XctraceError JSON but lacks recovery guidance in tool descriptions. Descriptions do not explain what to do if a sessionId is invalid or a schema is not found.