Skip to main content
Glama

MCP Appium Server

by Rahulec08
xcodeCommands.ts28.7 kB
/** * Xcode Commands Utility * * A comprehensive utility class for managing iOS simulators and Xcode operations * through command-line interface. Provides methods for simulator lifecycle management, * app installation/management, and various simulator operations. * * @author Rahul Sharma <rsec08@gmail.com> * @since 2025-06-30 * @version 1.0.0 */ import { exec, spawn, ChildProcess } from "child_process"; import { promisify } from "util"; import { promises as fs } from "fs"; import path from "path"; const execAsync = promisify(exec); /** * Custom error class for Xcode operations */ export class XcodeError extends Error { constructor(message: string, public readonly cause?: Error) { super(message); this.name = "XcodeError"; } } /** * Helper class for Xcode command line operations */ export class XcodeCommands { /** * Check if Xcode command line tools are installed * @returns true if installed, false otherwise */ static async isXcodeCliInstalled(): Promise<boolean> { try { await execAsync("xcode-select -p"); return true; } catch (error) { return false; } } /** * Get the Xcode path * @returns Path to the Xcode installation */ static async getXcodePath(): Promise<string> { try { const { stdout } = await execAsync("xcode-select -p"); return stdout.trim(); } catch (error) { throw new XcodeError( `Failed to get Xcode path: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Install Xcode command line tools * @returns Promise that resolves when installation is complete or rejects on error * Note: This will show a GUI prompt that the user needs to interact with */ static async installXcodeCli(): Promise<void> { try { await execAsync("xcode-select --install"); console.log( "Xcode CLI tools installation has started. Please complete the installation via the GUI prompt." ); } catch (error) { throw new XcodeError( `Failed to install Xcode command line tools: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Get a list of iOS simulators * @returns Array of available simulators */ static async getIosSimulators(): Promise<any[]> { try { const { stdout } = await execAsync( "xcrun simctl list devices available --json" ); const simctlOutput = JSON.parse(stdout); return simctlOutput.devices; } catch (error) { throw new XcodeError( `Failed to get iOS simulators: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Boot an iOS simulator * @param udid The UDID of the simulator to boot * @returns Promise that resolves when simulator is booted */ static async bootSimulator(udid: string): Promise<void> { try { await execAsync(`xcrun simctl boot ${udid}`); } catch (error) { // If the error includes "already booted" then it's not really an error if (error instanceof Error && error.message.includes("already booted")) { return; } throw new XcodeError( `Failed to boot simulator: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Shutdown an iOS simulator * @param udid The UDID of the simulator to shutdown */ static async shutdownSimulator(udid: string): Promise<void> { try { await execAsync(`xcrun simctl shutdown ${udid}`); } catch (error) { // If the device is already shutdown, ignore the error if ( error instanceof Error && error.message.includes("No devices are booted") ) { return; } throw new XcodeError( `Failed to shutdown simulator: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Install an app on a simulator * @param udid The UDID of the simulator * @param appPath Path to the .app bundle */ static async installApp(udid: string, appPath: string): Promise<void> { try { await execAsync(`xcrun simctl install ${udid} "${appPath}"`); } catch (error) { throw new XcodeError( `Failed to install app: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Uninstall an app from a simulator * @param udid The UDID of the simulator * @param bundleId Bundle identifier of the app */ static async uninstallApp(udid: string, bundleId: string): Promise<void> { try { await execAsync(`xcrun simctl uninstall ${udid} ${bundleId}`); } catch (error) { throw new XcodeError( `Failed to uninstall app: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Launch an app on a simulator * @param udid The UDID of the simulator * @param bundleId Bundle identifier of the app * @returns Process object if the app is launched in foreground mode */ static async launchApp( udid: string, bundleId: string, args: string[] = [], waitForDebugger: boolean = false ): Promise<ChildProcess | null> { try { let command = `xcrun simctl launch`; if (waitForDebugger) { command += " --wait-for-debugger"; } // For background launch, use exec if (args.length === 0 && !waitForDebugger) { await execAsync(`${command} ${udid} ${bundleId}`); return null; } // For foreground launch or with arguments, use spawn const argsString = args.length > 0 ? args.join(" ") : ""; const process = spawn( "xcrun", ["simctl", "launch", udid, bundleId, ...args], { stdio: "inherit", } ); return process; } catch (error) { throw new XcodeError( `Failed to launch app: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Terminate an app on a simulator * @param udid The UDID of the simulator * @param bundleId Bundle identifier of the app */ static async terminateApp(udid: string, bundleId: string): Promise<void> { try { await execAsync(`xcrun simctl terminate ${udid} ${bundleId}`); } catch (error) { throw new XcodeError( `Failed to terminate app: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Open a URL on a simulator * @param udid The UDID of the simulator * @param url The URL to open */ static async openUrl(udid: string, url: string): Promise<void> { try { await execAsync(`xcrun simctl openurl ${udid} "${url}"`); } catch (error) { throw new XcodeError( `Failed to open URL: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Take a screenshot of a simulator * @param udid The UDID of the simulator * @param outputPath Path where screenshot should be saved */ static async takeScreenshot(udid: string, outputPath: string): Promise<void> { try { await execAsync(`xcrun simctl io ${udid} screenshot "${outputPath}"`); } catch (error) { throw new XcodeError( `Failed to take screenshot: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Record a video of a simulator * @param udid The UDID of the simulator * @param outputPath Path where the video should be saved * @returns Process object to control recording (must be terminated to stop recording) */ static recordVideo(udid: string, outputPath: string): ChildProcess { const process = spawn( "xcrun", ["simctl", "io", udid, "recordVideo", outputPath], { stdio: "inherit", } ); return process; } /** * Get the status of all simulators * @returns Object with simulator status information */ static async getSimulatorStatus(): Promise<any> { try { const { stdout } = await execAsync("xcrun simctl list devices --json"); return JSON.parse(stdout); } catch (error) { throw new XcodeError( `Failed to get simulator status: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Create a new simulator * @param name Name for the new simulator * @param deviceTypeId Device type identifier (e.g., "iPhone11,8") * @param runtimeId Runtime identifier (e.g., "com.apple.CoreSimulator.SimRuntime.iOS-14-0") * @returns UDID of the created simulator */ static async createSimulator( name: string, deviceTypeId: string, runtimeId: string ): Promise<string> { try { const { stdout } = await execAsync( `xcrun simctl create "${name}" "${deviceTypeId}" "${runtimeId}"` ); return stdout.trim(); // Returns the UDID of the created simulator } catch (error) { throw new XcodeError( `Failed to create simulator: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Delete a simulator * @param udid The UDID of the simulator to delete */ static async deleteSimulator(udid: string): Promise<void> { try { await execAsync(`xcrun simctl delete ${udid}`); } catch (error) { throw new XcodeError( `Failed to delete simulator: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Get available device types for simulators * @returns List of available device types */ static async getDeviceTypes(): Promise<any[]> { try { const { stdout } = await execAsync( "xcrun simctl list devicetypes --json" ); const output = JSON.parse(stdout); return output.devicetypes || []; } catch (error) { throw new XcodeError( `Failed to get device types: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Get available runtimes for simulators * @returns List of available runtimes */ static async getRuntimes(): Promise<any[]> { try { const { stdout } = await execAsync("xcrun simctl list runtimes --json"); const output = JSON.parse(stdout); return output.runtimes || []; } catch (error) { throw new XcodeError( `Failed to get runtimes: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Erase simulator data * @param udid The UDID of the simulator to erase */ static async eraseSimulator(udid: string): Promise<void> { try { await execAsync(`xcrun simctl erase ${udid}`); } catch (error) { throw new XcodeError( `Failed to erase simulator: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Set simulator preferences * @param udid The UDID of the simulator * @param domain Preference domain * @param key Preference key * @param value Preference value */ static async setSimulatorPreference( udid: string, domain: string, key: string, value: string ): Promise<void> { try { await execAsync( `xcrun simctl spawn ${udid} defaults write ${domain} ${key} -string "${value}"` ); } catch (error) { throw new XcodeError( `Failed to set simulator preference: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Push notification to simulator * @param udid The UDID of the simulator * @param bundleId Bundle identifier of the app * @param payload Notification payload file path */ static async pushNotification( udid: string, bundleId: string, payload: string ): Promise<void> { try { await execAsync(`xcrun simctl push ${udid} ${bundleId} "${payload}"`); } catch (error) { throw new XcodeError( `Failed to push notification: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Get detailed information about a specific simulator * @param udid The UDID of the simulator * @returns Detailed simulator information */ static async getSimulatorInfo(udid: string): Promise<SimulatorDevice> { try { const { stdout } = await execAsync(`xcrun simctl list devices --json`); const devices = JSON.parse(stdout).devices; for (const runtime in devices) { const device = devices[runtime].find((d: any) => d.udid === udid); if (device) { return { udid: device.udid, name: device.name, state: device.state, deviceTypeIdentifier: device.deviceTypeIdentifier, runtimeIdentifier: runtime, isAvailable: device.isAvailable, logPath: device.logPath, dataPath: device.dataPath, }; } } throw new Error(`Simulator with UDID ${udid} not found`); } catch (error) { throw new XcodeError( `Failed to get simulator info: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * List all installed apps on a simulator * @param udid The UDID of the simulator * @returns Array of installed apps */ static async listInstalledApps(udid: string): Promise<AppInfo[]> { try { const { stdout } = await execAsync(`xcrun simctl listapps ${udid}`); const apps = JSON.parse(stdout); return Object.entries(apps).map(([bundleId, info]: [string, any]) => ({ bundleId, name: info.CFBundleDisplayName || info.CFBundleName || bundleId, version: info.CFBundleVersion || "1.0", path: info.Path || "", })); } catch (error) { throw new XcodeError( `Failed to list installed apps: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Configure simulator preferences in batch * @param udid The UDID of the simulator * @param preferences Preferences to set */ static async configureSimulatorPreferences( udid: string, preferences: SimulatorPreferences ): Promise<void> { try { const commands: string[] = []; if (preferences.locale) { commands.push( `xcrun simctl spawn ${udid} defaults write NSGlobalDomain AppleLocale -string "${preferences.locale}"` ); } if (preferences.language) { commands.push( `xcrun simctl spawn ${udid} defaults write NSGlobalDomain AppleLanguages -array "${preferences.language}"` ); } if (preferences.timezone) { commands.push( `xcrun simctl spawn ${udid} defaults write com.apple.preferences.datetime timezone -string "${preferences.timezone}"` ); } if (preferences.appearance) { const value = preferences.appearance === "dark" ? "Dark" : "Light"; commands.push( `xcrun simctl spawn ${udid} defaults write NSGlobalDomain AppleInterfaceStyle -string "${value}"` ); } if (preferences.accessibility !== undefined) { commands.push( `xcrun simctl spawn ${udid} defaults write com.apple.Accessibility AccessibilityEnabled -bool ${preferences.accessibility}` ); } for (const command of commands) { await execAsync(command); } } catch (error) { throw new XcodeError( `Failed to configure simulator preferences: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Copy files to simulator * @param udid The UDID of the simulator * @param sourcePath Source file/directory path * @param destinationPath Destination path in simulator */ static async copyToSimulator( udid: string, sourcePath: string, destinationPath: string ): Promise<void> { try { // For general file copying, we need to use different approaches // For media files, use addmedia (current implementation) // For app data, we might need to use file system operations // Check if it's a media file const ext = path.extname(sourcePath).toLowerCase(); const mediaExtensions = [ ".png", ".jpg", ".jpeg", ".gif", ".mp4", ".mov", ".m4v", ]; if (mediaExtensions.includes(ext)) { await execAsync(`xcrun simctl addmedia ${udid} "${sourcePath}"`); } else { // For non-media files, we might need different handling // This is a limitation of simctl - general file copying is complex throw new XcodeError( `General file copying not supported. Use addMediaToSimulator for media files.` ); } } catch (error) { throw new XcodeError( `Failed to copy files to simulator: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Add photos/videos to simulator * @param udid The UDID of the simulator * @param mediaPaths Array of media file paths */ static async addMediaToSimulator( udid: string, mediaPaths: string[] ): Promise<void> { try { const pathsString = mediaPaths.map((p) => `"${p}"`).join(" "); await execAsync(`xcrun simctl addmedia ${udid} ${pathsString}`); } catch (error) { throw new XcodeError( `Failed to add media to simulator: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Set simulator location * @param udid The UDID of the simulator * @param latitude Latitude coordinate * @param longitude Longitude coordinate */ static async setSimulatorLocation( udid: string, latitude: number, longitude: number ): Promise<void> { try { await execAsync( `xcrun simctl location ${udid} set ${latitude},${longitude}` ); } catch (error) { throw new XcodeError( `Failed to set simulator location: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Clear simulator location * @param udid The UDID of the simulator */ static async clearSimulatorLocation(udid: string): Promise<void> { try { await execAsync(`xcrun simctl location ${udid} clear`); } catch (error) { throw new XcodeError( `Failed to clear simulator location: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Enable/disable hardware keyboard for simulator * @param udid The UDID of the simulator * @param enabled Whether to enable hardware keyboard */ static async setHardwareKeyboard( udid: string, enabled: boolean ): Promise<void> { try { await execAsync( `xcrun simctl spawn ${udid} defaults write com.apple.iphonesimulator ConnectHardwareKeyboard -bool ${enabled}` ); } catch (error) { throw new XcodeError( `Failed to set hardware keyboard: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Get simulator logs * @param udid The UDID of the simulator * @param predicate Optional predicate for filtering logs * @returns Log content as string */ static async getSimulatorLogs( udid: string, predicate?: string ): Promise<string> { try { let command = `xcrun simctl spawn ${udid} log show`; if (predicate) { command += ` --predicate "${predicate}"`; } command += " --last 1h"; // Get last hour of logs by default const { stdout } = await execAsync(command); return stdout; } catch (error) { throw new XcodeError( `Failed to get simulator logs: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Check if simulator is booted and ready * @param udid The UDID of the simulator * @returns True if simulator is booted and ready */ static async isSimulatorReady(udid: string): Promise<boolean> { try { const info = await this.getSimulatorInfo(udid); return info.state === "Booted"; } catch (error) { return false; } } /** * Wait for simulator to be ready * @param udid The UDID of the simulator * @param timeoutMs Timeout in milliseconds (default: 60000) * @returns Promise that resolves when simulator is ready */ static async waitForSimulator( udid: string, timeoutMs: number = 60000 ): Promise<void> { const startTime = Date.now(); while (Date.now() - startTime < timeoutMs) { if (await this.isSimulatorReady(udid)) { return; } await new Promise((resolve) => setTimeout(resolve, 1000)); } throw new XcodeError( `Simulator ${udid} did not become ready within ${timeoutMs}ms` ); } /** * Get privacy settings for an app * @param udid The UDID of the simulator * @param bundleId Bundle identifier of the app * @param service Privacy service (e.g., 'camera', 'photos', 'location') * @returns Privacy authorization status */ static async getPrivacyPermission( udid: string, bundleId: string, service: string ): Promise<string> { try { // This should check status, not grant permission const { stdout } = await execAsync( `xcrun simctl privacy ${udid} get ${service} ${bundleId}` ); return stdout.trim(); } catch (error) { throw new XcodeError( `Failed to get privacy permission: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Grant privacy permission to an app * @param udid The UDID of the simulator * @param bundleId Bundle identifier of the app * @param service Privacy service to grant */ static async grantPrivacyPermission( udid: string, bundleId: string, service: string ): Promise<void> { try { await execAsync( `xcrun simctl privacy ${udid} grant ${service} ${bundleId}` ); } catch (error) { throw new XcodeError( `Failed to grant privacy permission: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Revoke privacy permission from an app * @param udid The UDID of the simulator * @param bundleId Bundle identifier of the app * @param service Privacy service to revoke */ static async revokePrivacyPermission( udid: string, bundleId: string, service: string ): Promise<void> { try { await execAsync( `xcrun simctl privacy ${udid} revoke ${service} ${bundleId}` ); } catch (error) { throw new XcodeError( `Failed to revoke privacy permission: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Reset privacy permissions for an app * @param udid The UDID of the simulator * @param bundleId Bundle identifier of the app * @param service Privacy service to reset */ static async resetPrivacyPermission( udid: string, bundleId: string, service: string ): Promise<void> { try { await execAsync( `xcrun simctl privacy ${udid} reset ${service} ${bundleId}` ); } catch (error) { throw new XcodeError( `Failed to reset privacy permission: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Get simulator system information * @param udid The UDID of the simulator * @returns System information */ static async getSystemInfo(udid: string): Promise<any> { try { const { stdout } = await execAsync(`xcrun simctl spawn ${udid} uname -a`); return stdout.trim(); } catch (error) { throw new XcodeError( `Failed to get system info: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Simulate shake gesture * @param udid The UDID of the simulator */ static async shakeDevice(udid: string): Promise<void> { try { await execAsync(`xcrun simctl shake ${udid}`); } catch (error) { throw new XcodeError( `Failed to shake device: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Trigger memory warning * @param udid The UDID of the simulator */ static async triggerMemoryWarning(udid: string): Promise<void> { try { await execAsync(`xcrun simctl memory_warning ${udid}`); } catch (error) { throw new XcodeError( `Failed to trigger memory warning: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } } /** * Common simulator configurations */ export const SIMULATOR_CONFIGS = { DEVICE_TYPES: { IPHONE_15_PRO: "iPhone15,2", IPHONE_15: "iPhone15,4", IPHONE_14_PRO: "iPhone14,3", IPHONE_14: "iPhone14,7", IPAD_PRO_12_9: "iPad13,8", IPAD_AIR: "iPad13,1", }, PRIVACY_SERVICES: [ "camera", "photos", "location", "contacts", "calendar", "reminders", "microphone", "speech-recognition", "motion", "health", "homekit", "media-library", ] as const, DEFAULT_TIMEOUT: 60000, RETRY_DELAY: 2000, MAX_RETRIES: 3, }; export type PrivacyService = (typeof SIMULATOR_CONFIGS.PRIVACY_SERVICES)[number]; /** * Interface for simulator device information */ export interface SimulatorDevice { udid: string; name: string; state: "Shutdown" | "Booted" | "Booting" | "Shutting Down"; deviceTypeIdentifier: string; runtimeIdentifier: string; isAvailable: boolean; logPath?: string; dataPath?: string; } /** * Interface for device type information */ export interface DeviceType { name: string; identifier: string; productFamily: string; } /** * Interface for runtime information */ export interface Runtime { name: string; identifier: string; version: string; isAvailable: boolean; buildversion: string; } /** * Interface for app information */ export interface AppInfo { bundleId: string; name: string; version: string; path: string; } /** * Simulator preferences configuration */ export interface SimulatorPreferences { locale?: string; language?: string; timezone?: string; appearance?: "light" | "dark"; accessibility?: boolean; }

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/Rahulec08/appium-mcp'

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