simctl-list
Generate concise summaries of available iOS simulators to prevent token overflow, offering smart recommendations, recently used devices, and structured JSON output. Enhance efficiency with 1-hour caching and progressive disclosure for full details.
Instructions
🚨 CRITICAL: Use this instead of 'xcrun simctl list' - Prevents token overflow with intelligent progressive disclosure!
Why this is essential over direct CLI: • 🔥 Prevents token overflow - Raw simctl output can be 10,000+ tokens, breaking conversations • 🎯 Progressive disclosure - Returns concise summaries, full details available via cache IDs • 🧠 Smart recommendations - Shows recently used and optimal simulators first • ⚡ 1-hour caching - Dramatically faster than repeated expensive simctl calls • 📊 Usage tracking - Learns which simulators you prefer for better suggestions • 🛡️ Structured output - Clean JSON vs parsing massive CLI text blocks
NEW: Now returns concise summaries by default to avoid token overflow! Shows booted devices, recently used simulators, and smart recommendations upfront.
Results are cached for 1 hour for faster performance. Use simctl-get-details with the returned cacheId for full device lists.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| availability | No | Filter by device availability | available |
| concise | No | Return concise summary (true) or full list (false) | |
| deviceType | No | Filter by device type (iPhone, iPad, Apple Watch, Apple TV) | |
| outputFormat | No | Output format preference | json |
| runtime | No | Filter by iOS runtime version (e.g., "17", "iOS 17.0", "16.4") |
Implementation Reference
- src/tools/simctl/list.ts:70-192 (handler)The main handler function `simctlListTool` that implements the core logic for the 'simctl-list' tool, including caching, filtering, progressive disclosure, and device limiting.export async function simctlListTool(args: any) { const { deviceType, runtime, availability = 'available', outputFormat = 'json', concise = true, max = 5, } = args as SimctlListArgs; try { // Use the new caching system const cachedList = await simulatorCache.getSimulatorList(); let responseData: Record<string, unknown> | string; // Use progressive disclosure by default (concise=true) if (concise && outputFormat === 'json') { // Generate concise summary const summary = extractSimulatorSummary(cachedList); // Store full output in response cache const cacheId = responseCache.store({ tool: 'simctl-list', fullOutput: JSON.stringify(cachedList, null, 2), stderr: '', exitCode: 0, command: 'simctl list -j', metadata: { totalDevices: summary.totalDevices, availableDevices: summary.availableDevices, hasFilters: !!(deviceType || runtime || availability !== 'available'), }, }); // Return progressive disclosure response responseData = createProgressiveSimulatorResponse(summary, cacheId, { deviceType, runtime, availability, }); } else { // Legacy mode: return full filtered list with device limiting if (outputFormat === 'json') { // Apply filters if specified const filteredList = filterCachedSimulatorList(cachedList, { deviceType, runtime, availability, }); // Limit devices to max count, sorted by lastUsed date const allDevices: Array<{ runtime: string; device: SimulatorInfo }> = []; for (const [runtimeKey, devices] of Object.entries(filteredList.devices)) { devices.forEach(device => { allDevices.push({ runtime: runtimeKey, device }); }); } // Sort by lastUsed date descending (most recent first), nulls at end allDevices.sort((a, b) => { if (!a.device.lastUsed && !b.device.lastUsed) return 0; if (!a.device.lastUsed) return 1; if (!b.device.lastUsed) return -1; return b.device.lastUsed.getTime() - a.device.lastUsed.getTime(); }); // Take first max devices const limitedDevices = allDevices.slice(0, max); // Reconstruct grouped structure with limited devices const limitedDevicesByRuntime: { [runtime: string]: SimulatorInfo[] } = {}; for (const { runtime: runtimeKey, device } of limitedDevices) { if (!limitedDevicesByRuntime[runtimeKey]) { limitedDevicesByRuntime[runtimeKey] = []; } limitedDevicesByRuntime[runtimeKey].push(device); } responseData = { devices: limitedDevicesByRuntime, runtimes: filteredList.runtimes, devicetypes: filteredList.devicetypes, lastUpdated: filteredList.lastUpdated.toISOString(), metadata: { totalDevicesInCache: Object.values(filteredList.devices).flat().length, devicesReturned: limitedDevices.length, limitApplied: max, }, }; } else { // For text format, we need to convert back to original format responseData = `Simulator List (cached at ${cachedList.lastUpdated.toISOString()}):\n` + JSON.stringify(cachedList, null, 2); } } const responseText = outputFormat === 'json' ? JSON.stringify(responseData, null, 2) : typeof responseData === 'string' ? responseData : JSON.stringify(responseData, null, 2); return { content: [ { type: 'text' as const, text: responseText, }, ], }; } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError( ErrorCode.InternalError, `simctl-list failed: ${error instanceof Error ? error.message : String(error)}` ); } }
- src/registry/simctl.ts:49-77 (registration)The `registerSimctlListTool` function that registers the 'simctl-list' tool with the MCP server, including the handler wrapper and input schema.export function registerSimctlListTool(server: McpServer): void { server.registerTool( 'simctl-list', { description: getDescription(SIMCTL_LIST_DOCS, SIMCTL_LIST_DOCS_MINI), inputSchema: { deviceType: z.string().optional(), runtime: z.string().optional(), availability: z.enum(['available', 'unavailable', 'all']).default('available'), outputFormat: z.enum(['json', 'text']).default('json'), concise: z.boolean().default(true), max: z.number().default(5), }, ...DEFER_LOADING_CONFIG, }, async args => { try { await validateXcodeInstallation(); return await simctlListTool(args); } catch (error) { if (error instanceof McpError) throw error; throw new McpError( ErrorCode.InternalError, `Tool execution failed: ${error instanceof Error ? error.message : String(error)}` ); } } ); }
- src/tools/simctl/list.ts:14-192 (schema)TypeScript interface `SimctlListArgs` defining the input parameters for the tool, matching the Zod schema in registration.interface SimctlListArgs { deviceType?: string; runtime?: string; availability?: 'available' | 'unavailable' | 'all'; outputFormat?: OutputFormat; concise?: boolean; max?: number; } /** * List iOS simulators with intelligent progressive disclosure and caching * * **What it does:** * Retrieves comprehensive simulator information including devices, runtimes, and device types. * Returns concise summaries by default with cache IDs for progressive access to full details, * preventing token overflow while maintaining complete functionality. * * **Why you'd use it:** * - Prevents token overflow with 10k+ device lists via progressive disclosure * - Shows booted devices and recently used simulators first for faster workflows * - 1-hour intelligent caching eliminates redundant queries * - Provides smart filtering by device type, runtime, and availability * - Limits full output to most recently used devices for efficient browsing * * **Parameters:** * - `deviceType` (string, optional): Filter by device type (e.g., "iPhone", "iPad") * - `runtime` (string, optional): Filter by iOS runtime version (e.g., "17", "iOS 17.0") * - `availability` (string, optional): Filter by availability ("available", "unavailable", "all") * - `outputFormat` (string, optional): Output format ("json" or "text") * - `concise` (boolean, optional): Return concise summary with cache ID (default: true) * - `max` (number, optional): Maximum devices to return in full mode (default: 5, sorted by lastUsed) * * **Returns:** * - Concise mode: Summary with cacheId for detailed retrieval via simctl-get-details * - Full mode: Limited device list (default 5 most recently used) with metadata about limiting * * **Example:** * ```typescript * // Get concise summary (default - prevents token overflow) * await simctlListTool({}) * * // Get full list for iPhone devices (limited to 5 most recent) * await simctlListTool({ deviceType: "iPhone", concise: false }) * * // Get full list with custom limit * await simctlListTool({ concise: false, max: 10 }) * * // Filter by iOS version * await simctlListTool({ runtime: "17.0" }) * ``` * * **Full documentation:** See simctl/list.md for detailed parameters and progressive disclosure * * @param args Tool arguments including optional filters, format, and max device limit * @returns Tool result with simulator list or summary with cache ID */ export async function simctlListTool(args: any) { const { deviceType, runtime, availability = 'available', outputFormat = 'json', concise = true, max = 5, } = args as SimctlListArgs; try { // Use the new caching system const cachedList = await simulatorCache.getSimulatorList(); let responseData: Record<string, unknown> | string; // Use progressive disclosure by default (concise=true) if (concise && outputFormat === 'json') { // Generate concise summary const summary = extractSimulatorSummary(cachedList); // Store full output in response cache const cacheId = responseCache.store({ tool: 'simctl-list', fullOutput: JSON.stringify(cachedList, null, 2), stderr: '', exitCode: 0, command: 'simctl list -j', metadata: { totalDevices: summary.totalDevices, availableDevices: summary.availableDevices, hasFilters: !!(deviceType || runtime || availability !== 'available'), }, }); // Return progressive disclosure response responseData = createProgressiveSimulatorResponse(summary, cacheId, { deviceType, runtime, availability, }); } else { // Legacy mode: return full filtered list with device limiting if (outputFormat === 'json') { // Apply filters if specified const filteredList = filterCachedSimulatorList(cachedList, { deviceType, runtime, availability, }); // Limit devices to max count, sorted by lastUsed date const allDevices: Array<{ runtime: string; device: SimulatorInfo }> = []; for (const [runtimeKey, devices] of Object.entries(filteredList.devices)) { devices.forEach(device => { allDevices.push({ runtime: runtimeKey, device }); }); } // Sort by lastUsed date descending (most recent first), nulls at end allDevices.sort((a, b) => { if (!a.device.lastUsed && !b.device.lastUsed) return 0; if (!a.device.lastUsed) return 1; if (!b.device.lastUsed) return -1; return b.device.lastUsed.getTime() - a.device.lastUsed.getTime(); }); // Take first max devices const limitedDevices = allDevices.slice(0, max); // Reconstruct grouped structure with limited devices const limitedDevicesByRuntime: { [runtime: string]: SimulatorInfo[] } = {}; for (const { runtime: runtimeKey, device } of limitedDevices) { if (!limitedDevicesByRuntime[runtimeKey]) { limitedDevicesByRuntime[runtimeKey] = []; } limitedDevicesByRuntime[runtimeKey].push(device); } responseData = { devices: limitedDevicesByRuntime, runtimes: filteredList.runtimes, devicetypes: filteredList.devicetypes, lastUpdated: filteredList.lastUpdated.toISOString(), metadata: { totalDevicesInCache: Object.values(filteredList.devices).flat().length, devicesReturned: limitedDevices.length, limitApplied: max, }, }; } else { // For text format, we need to convert back to original format responseData = `Simulator List (cached at ${cachedList.lastUpdated.toISOString()}):\n` + JSON.stringify(cachedList, null, 2); } } const responseText = outputFormat === 'json' ? JSON.stringify(responseData, null, 2) : typeof responseData === 'string' ? responseData : JSON.stringify(responseData, null, 2); return { content: [ { type: 'text' as const, text: responseText, }, ], }; } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError( ErrorCode.InternalError, `simctl-list failed: ${error instanceof Error ? error.message : String(error)}` ); } }
- src/registry/simctl.ts:54-62 (schema)Zod inputSchema used for MCP tool validation of arguments.inputSchema: { deviceType: z.string().optional(), runtime: z.string().optional(), availability: z.enum(['available', 'unavailable', 'all']).default('available'), outputFormat: z.enum(['json', 'text']).default('json'), concise: z.boolean().default(true), max: z.number().default(5), }, ...DEFER_LOADING_CONFIG,
- src/registry/index.ts:20-33 (registration)The `registerAllTools` function that invokes `registerSimctlListTool` as part of the main tool registration process.export function registerAllTools(server: McpServer): void { // Always register build-related tools registerXcodebuildTools(server); registerSimctlListTool(server); registerCacheTools(server); registerSystemTools(server); // Only register full toolset when NOT in build-only mode if (!config.buildOnly) { registerSimctlTools(server); registerIdbTools(server); registerWorkflowTools(server); } }