Skip to main content
Glama

XC-MCP: XCode CLI wrapper

by conorluddy
status-bar.ts12 kB
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { executeCommand } from '../../utils/command.js'; import { simulatorCache } from '../../state/simulator-cache.js'; interface SimctlStatusBarToolArgs { udid: string; operation: 'override' | 'clear'; time?: string; dataNetwork?: string; wifiMode?: string; batteryState?: string; batteryLevel?: number; } /** * Manage simulator status bar appearance for testing * * Examples: * - Override appearance: udid: "device-123", operation: "override", time: "9:41", batteryLevel: 100 * - Clear overrides: udid: "device-123", operation: "clear" * - Set poor signal: udid: "device-123", operation: "override", dataNetwork: "none", wifiMode: "failed" * * Parameters (for override): * - time: 24-hour format (e.g., "9:41", "23:59") * - dataNetwork: none, 1x, 3g, 4g, 5g, lte, lte-a * - wifiMode: active, searching, failed * - batteryState: charging, charged, discharging * - batteryLevel: 0-100 * * **Full documentation:** See simctl/status-bar.md for detailed parameters and examples */ export async function simctlStatusBarTool(args: any) { const { udid, operation, time, dataNetwork, wifiMode, batteryState, batteryLevel } = args as SimctlStatusBarToolArgs; try { // Validate inputs if (!udid || udid.trim().length === 0) { throw new McpError(ErrorCode.InvalidRequest, 'UDID is required and cannot be empty'); } if (!operation || !['override', 'clear'].includes(operation)) { throw new McpError( ErrorCode.InvalidRequest, 'Operation must be either "override" or "clear"' ); } // Validate simulator exists const simulator = await simulatorCache.findSimulatorByUdid(udid); if (!simulator) { throw new McpError( ErrorCode.InvalidRequest, `Simulator with UDID "${udid}" not found. Use simctl-list to see available simulators.` ); } // For override operation, validate parameters if (operation === 'override') { // Validate time format if provided if (time) { const timeRegex = /^\d{1,2}:\d{2}$/; if (!timeRegex.test(time)) { throw new McpError( ErrorCode.InvalidRequest, 'Time must be in 24-hour format (e.g., "9:41", "23:59")' ); } } // Validate dataNetwork if provided if (dataNetwork) { const validDataNetworks = ['none', '1x', '3g', '4g', '5g', 'lte', 'lte-a']; if (!validDataNetworks.includes(dataNetwork)) { throw new McpError( ErrorCode.InvalidRequest, `Data network must be one of: ${validDataNetworks.join(', ')}. Received: "${dataNetwork}"` ); } } // Validate wifiMode if provided if (wifiMode) { const validWifiModes = ['active', 'searching', 'failed']; if (!validWifiModes.includes(wifiMode)) { throw new McpError( ErrorCode.InvalidRequest, `WiFi mode must be one of: ${validWifiModes.join(', ')}. Received: "${wifiMode}"` ); } } // Validate batteryState if provided if (batteryState) { const validBatteryStates = ['charging', 'charged', 'discharging']; if (!validBatteryStates.includes(batteryState)) { throw new McpError( ErrorCode.InvalidRequest, `Battery state must be one of: ${validBatteryStates.join(', ')}. Received: "${batteryState}"` ); } } // Validate battery level if provided if (batteryLevel !== undefined) { if (batteryLevel < 0 || batteryLevel > 100) { throw new McpError(ErrorCode.InvalidRequest, 'Battery level must be between 0 and 100'); } } } // Build command let command = `xcrun simctl status_bar "${udid}" ${operation}`; // Add parameters for override if (operation === 'override') { const options = []; if (time) { options.push(`--time "${time}"`); } if (dataNetwork) { options.push(`--dataNetwork "${dataNetwork}"`); } if (wifiMode) { options.push(`--wifiMode "${wifiMode}"`); } if (batteryState) { options.push(`--batteryState "${batteryState}"`); } if (batteryLevel !== undefined) { options.push(`--batteryLevel "${batteryLevel}"`); } if (options.length > 0) { command += ' ' + options.join(' '); } } console.error(`[simctl-status-bar] Executing: ${command}`); const result = await executeCommand(command, { timeout: 10000, }); const success = result.code === 0; // Build guidance messages const guidanceMessages: string[] = []; if (success) { if (operation === 'override') { guidanceMessages.push( `✅ Status bar ${operation}d on "${simulator.name}"`, `Time: ${time || 'default'}`, `Network: ${dataNetwork || 'unchanged'}`, `WiFi: ${wifiMode || 'unchanged'}`, `Battery: ${batteryLevel !== undefined ? `${batteryLevel}%` : 'unchanged'} (${batteryState || 'unchanged'})`, `Take screenshot to verify: simctl-io ${udid} screenshot` ); } else { guidanceMessages.push( `✅ Status bar overrides cleared on "${simulator.name}"`, `Status bar will show actual device state` ); } } else { guidanceMessages.push( `❌ Failed to ${operation} status bar: ${result.stderr || 'Unknown error'}`, `Check simulator version compatibility`, `Verify simulator is available: simctl-health-check` ); } // Add warnings for simulator state regardless of success if (simulator.state !== 'Booted') { guidanceMessages.push( `⚠️ Warning: Simulator is in ${simulator.state} state. Boot the simulator for optimal functionality: simctl-boot ${udid}` ); } if (simulator.isAvailable === false) { guidanceMessages.push( `⚠️ Warning: Simulator is marked as unavailable. This may cause issues with operations.` ); } const responseData = { success, udid, operation, parameters: operation === 'override' ? { time, dataNetwork, wifiMode, batteryState, batteryLevel, } : undefined, simulatorInfo: { name: simulator.name, udid: simulator.udid, state: simulator.state, isAvailable: simulator.isAvailable, }, command, output: result.stdout, error: result.stderr || undefined, exitCode: result.code, guidance: guidanceMessages, }; const responseText = JSON.stringify(responseData, null, 2); return { content: [ { type: 'text' as const, text: responseText, }, ], isError: !success, }; } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError( ErrorCode.InternalError, `simctl-status-bar failed: ${error instanceof Error ? error.message : String(error)}` ); } } export const SIMCTL_STATUS_BAR_DOCS = ` # simctl-status-bar Override or clear simulator status bar appearance for consistent screenshots and UI testing. ## What it does Controls the simulator's status bar appearance, allowing you to set specific time, network status, battery level, and WiFi state. Useful for creating consistent screenshots and testing app behavior under different device conditions. ## Parameters - **udid** (string, required): Simulator UDID (from simctl-list) - **operation** (string, required): "override" or "clear" - **time** (string, optional): Time in 24-hour format (e.g., "9:41", "23:59") - **dataNetwork** (string, optional): Network type - none, 1x, 3g, 4g, 5g, lte, lte-a - **wifiMode** (string, optional): WiFi state - active, searching, failed - **batteryState** (string, optional): Battery state - charging, charged, discharging - **batteryLevel** (number, optional): Battery percentage 0-100 ## Returns JSON response with: - Status bar modification status - Applied parameters (for override operation) - Guidance for verification and testing ## Examples ### Override with classic Apple time \`\`\`typescript await simctlStatusBarTool({ udid: 'device-123', operation: 'override', time: '9:41', batteryLevel: 100 }) \`\`\` ### Simulate poor network conditions \`\`\`typescript await simctlStatusBarTool({ udid: 'device-123', operation: 'override', dataNetwork: 'none', wifiMode: 'failed' }) \`\`\` ### Simulate low battery \`\`\`typescript await simctlStatusBarTool({ udid: 'device-123', operation: 'override', batteryState: 'discharging', batteryLevel: 15 }) \`\`\` ### Clear all overrides \`\`\`typescript await simctlStatusBarTool({ udid: 'device-123', operation: 'clear' }) \`\`\` ## Common Use Cases 1. **Consistent screenshots**: Set time to 9:41 and battery to 100% for app store screenshots 2. **Network condition testing**: Test app behavior with different network types 3. **Low battery testing**: Verify app handles low battery warnings correctly 4. **UI testing**: Ensure status bar doesn't interfere with visual regression tests 5. **Demo mode**: Clean status bar for presentations and demos ## Status Bar Parameters ### Time - Format: 24-hour "HH:MM" (e.g., "9:41", "14:30", "23:59") - Apple default: "9:41" (time of original iPhone announcement) ### Data Network - **none**: No cellular data - **1x**: 2G network - **3g**: 3G network - **4g**: 4G network - **5g**: 5G network - **lte**: LTE network - **lte-a**: LTE Advanced ### WiFi Mode - **active**: Connected and active - **searching**: Searching for network - **failed**: Connection failed ### Battery State - **charging**: Device is charging - **charged**: Fully charged - **discharging**: Running on battery ### Battery Level - Range: 0-100 - Shows percentage in status bar ## Important Notes - **Screenshot consistency**: Apply overrides before taking screenshots for consistent results - **Demo mode**: Apple often uses 9:41 time and 100% battery for marketing materials - **Simulator only**: Status bar overrides only work on simulators, not real devices - **Persistent**: Overrides persist until cleared or simulator is reset - **Version compatibility**: Some parameters may not work on older iOS versions ## Error Handling - **Invalid time format**: Error if time is not in HH:MM format - **Invalid network type**: Error if dataNetwork is not in allowed list - **Invalid battery level**: Error if batteryLevel is not 0-100 - **Simulator not found**: Validates simulator exists in cache ## App Store Screenshot Best Practices For app store screenshots, Apple recommends: 1. Time: "9:41" (Apple's standard) 2. Battery: 100% (shows full battery icon) 3. Signal: Full bars (use "lte" or "5g") 4. WiFi: Active (shows connected) 5. No notifications or indicators \`\`\`typescript await simctlStatusBarTool({ udid: 'device-123', operation: 'override', time: '9:41', dataNetwork: '5g', wifiMode: 'active', batteryState: 'charged', batteryLevel: 100 }) \`\`\` ## Testing Workflow 1. **Apply overrides**: Set desired status bar state 2. **Take screenshot**: \`simctl-io <udid> screenshot\` to verify 3. **Test app**: Launch app and verify it handles the conditions 4. **Clear overrides**: Reset to normal state when done ## Visual Verification After applying overrides, always take a screenshot to verify the status bar appears correctly: \`\`\` simctl-io <udid> screenshot \`\`\` The status bar changes are visible immediately and affect all screenshots taken while overrides are active. ## When to Clear Overrides - After taking app store screenshots - Before testing features that depend on actual device state - When switching between different test scenarios - At the end of automated test runs `;

Latest Blog Posts

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/conorluddy/xc-mcp'

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