Skip to main content
Glama

MCP Xcode

by Stefan-Nitu
SimulatorBoot.ts6.98 kB
import { execAsync } from '../../utils.js'; import { createModuleLogger } from '../../logger.js'; const logger = createModuleLogger('SimulatorBoot'); /** * Simple utility to boot simulators for Xcode builds * Extracted shared logic from BuildXcodeTool and RunXcodeTool */ export class SimulatorBoot { /** * Boot a simulator */ async boot(deviceId: string): Promise<void> { try { await execAsync(`xcrun simctl boot "${deviceId}"`); logger.debug({ deviceId }, 'Simulator booted successfully'); } catch (error: any) { // Check if already booted if (!error.message?.includes('Unable to boot device in current state: Booted')) { logger.error({ error: error.message, deviceId }, 'Failed to boot simulator'); throw new Error(`Failed to boot simulator: ${error.message}`); } logger.debug({ deviceId }, 'Simulator already booted'); } } /** * Shutdown a simulator */ async shutdown(deviceId: string): Promise<void> { try { await execAsync(`xcrun simctl shutdown "${deviceId}"`); logger.debug({ deviceId }, 'Simulator shutdown successfully'); } catch (error: any) { logger.error({ error: error.message, deviceId }, 'Failed to shutdown simulator'); throw new Error(`Failed to shutdown simulator: ${error.message}`); } } /** * Opens the Simulator app GUI (skipped during tests) */ async openSimulatorApp(): Promise<void> { // Skip opening GUI during tests if (process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID) { logger.debug('Skipping Simulator GUI in test environment'); return; } try { await execAsync('open -g -a Simulator'); logger.debug('Opened Simulator app'); } catch (error: any) { logger.warn({ error: error.message }, 'Failed to open Simulator app'); } } /** * Ensures a simulator is booted for the given platform and device * Returns the booted device ID (UDID) */ async ensureBooted(platform: string, deviceId?: string): Promise<string> { if (!deviceId) { // No specific device requested, use first available return this.bootFirstAvailable(platform); } // Check if the device exists and get its state try { const { stdout } = await execAsync('xcrun simctl list devices --json'); const data = JSON.parse(stdout); // Collect all matching devices first, then pick the best one const matchingDevices: any[] = []; for (const deviceList of Object.values(data.devices)) { for (const device of deviceList as any[]) { if (device.udid === deviceId || device.name === deviceId) { matchingDevices.push(device); } } } if (matchingDevices.length === 0) { throw new Error(`Device '${deviceId}' not found`); } // Sort devices: prefer available ones, then already booted ones matchingDevices.sort((a, b) => { // First priority: available devices if (a.isAvailable && !b.isAvailable) return -1; if (!a.isAvailable && b.isAvailable) return 1; // Second priority: booted devices if (a.state === 'Booted' && b.state !== 'Booted') return -1; if (a.state !== 'Booted' && b.state === 'Booted') return 1; return 0; }); // Use the best matching device const device = matchingDevices[0]; if (!device.isAvailable) { // All matching devices are unavailable const availableErrors = matchingDevices .map(d => `${d.name} (${d.udid}): ${d.availabilityError || 'unavailable'}`) .join(', '); throw new Error(`All devices named '${deviceId}' are unavailable: ${availableErrors}`); } if (device.state === 'Booted') { logger.debug({ deviceId: device.udid, name: device.name }, 'Device already booted'); // Still open the Simulator app to make it visible await this.openSimulatorApp(); return device.udid; } // Boot the device logger.info({ deviceId: device.udid, name: device.name }, 'Booting simulator'); try { await execAsync(`xcrun simctl boot "${device.udid}"`); } catch (error: any) { // Device might already be booted if (!error.message?.includes('Unable to boot device in current state: Booted')) { throw error; } } // Open the Simulator app to show the GUI await this.openSimulatorApp(); return device.udid; } catch (error: any) { logger.error({ error: error.message, deviceId }, 'Failed to boot simulator'); throw new Error(`Failed to boot simulator: ${error.message}`); } } /** * Boots the first available simulator for a platform */ private async bootFirstAvailable(platform: string): Promise<string> { try { const { stdout } = await execAsync('xcrun simctl list devices --json'); const data = JSON.parse(stdout); // Find first available device for the platform for (const [runtime, deviceList] of Object.entries(data.devices)) { const runtimeLower = runtime.toLowerCase(); const platformLower = platform.toLowerCase(); // Handle visionOS which is internally called xrOS const isVisionOS = platformLower === 'visionos' && runtimeLower.includes('xros'); const isOtherPlatform = platformLower !== 'visionos' && runtimeLower.includes(platformLower); if (!isVisionOS && !isOtherPlatform) { continue; } for (const device of deviceList as any[]) { if (!device.isAvailable) continue; // Check if already booted if (device.state === 'Booted') { logger.debug({ deviceId: device.udid, name: device.name }, 'Using already booted device'); // Still open the Simulator app to make it visible await this.openSimulatorApp(); return device.udid; } // Boot this device logger.info({ deviceId: device.udid, name: device.name }, 'Booting first available simulator'); try { await execAsync(`xcrun simctl boot ${device.udid}`); } catch (error: any) { // Device might already be booted if (!error.message?.includes('Unable to boot device in current state: Booted')) { throw error; } } // Open the Simulator app to show the GUI await this.openSimulatorApp(); return device.udid; } } throw new Error(`No available simulators found for platform ${platform}`); } catch (error: any) { logger.error({ error: error.message, platform }, 'Failed to boot simulator'); throw new Error(`Failed to boot simulator: ${error.message}`); } } }

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/Stefan-Nitu/mcp-xcode'

If you have feedback or need assistance with the MCP directory API, please join our Discord server