Skip to main content
Glama
conorluddy

XC-MCP: XCode CLI wrapper

by conorluddy

xcodebuild-build

Automate and optimize Xcode builds with intelligent caching, performance tracking, and smart defaults. Learns successful configurations, suggests optimal simulators, and handles large logs efficiently for improved CLI workflows.

Instructions

Prefer this over raw 'xcodebuild' - Intelligent building with learning, caching, and performance tracking.

Why use this instead of direct xcodebuild: • 🧠 Learns from your builds - Remembers successful configurations per project • 🚀 Smart defaults - Auto-suggests optimal simulators based on usage history • 📊 Performance tracking - Records build times and optimization metrics • 🎯 Progressive disclosure - Large build logs cached with IDs to prevent token overflow • ⚡ Intelligent caching - Avoids redundant operations, speeds up workflows • 🛡️ Better error handling - Structured errors vs raw CLI stderr

Features smart caching that remembers your last successful build configuration and suggests optimal simulators.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
configurationNoBuild configuration (Debug, Release, etc.)Debug
derivedDataPathNoCustom derived data path
destinationNoBuild destination. If not provided, uses intelligent defaults based on project history and available simulators.
projectPathYesPath to .xcodeproj or .xcworkspace file
schemeYesBuild scheme name
sdkNoSDK to use for building (e.g., "iphonesimulator", "iphoneos")

Implementation Reference

  • Primary handler function implementing the core logic of the 'xcodebuild-build' tool: input parsing/validation, smart caching/defaults, command construction/execution, output processing/caching, auto-install option, and structured JSON response with progressive disclosure via buildId.
    export async function xcodebuildBuildTool(args: any) { const { projectPath, scheme, configuration = 'Debug', destination, sdk, derivedDataPath, autoInstall = false, simulatorUdid, bootSimulator = true, } = args as BuildToolArgs; try { // Validate inputs await validateProjectPath(projectPath); validateScheme(scheme); // Get smart defaults from cache const preferredConfig = await projectCache.getPreferredBuildConfig(projectPath); const smartDestination = destination || (await getSmartDestination(preferredConfig, projectPath)); // Build final configuration const finalConfig: BuildConfig = { scheme, configuration: configuration || preferredConfig?.configuration || 'Debug', destination: smartDestination, sdk: sdk || preferredConfig?.sdk, derivedDataPath: derivedDataPath || preferredConfig?.derivedDataPath, }; // Build command const command = buildXcodebuildCommand('build', projectPath, finalConfig as any); console.error(`[xcodebuild-build] Executing: ${command}`); // Execute command with extended timeout for builds const startTime = Date.now(); const result = await executeCommand(command, { timeout: 600000, // 10 minutes for builds maxBuffer: 50 * 1024 * 1024, // 50MB buffer for build logs }); const duration = Date.now() - startTime; // Extract build summary const summary = extractBuildSummary(result.stdout, result.stderr, result.code); // Record build result in project cache projectCache.recordBuildResult(projectPath, finalConfig, { timestamp: new Date(), success: summary.success, duration, errorCount: summary.errorCount, warningCount: summary.warningCount, buildSizeBytes: summary.buildSizeBytes, }); // Record simulator usage if destination was used if (finalConfig.destination && finalConfig.destination.includes('Simulator')) { const udidMatch = finalConfig.destination.match(/id=([A-F0-9-]+)/); if (udidMatch) { simulatorCache.recordSimulatorUsage(udidMatch[1], projectPath); // Save simulator preference to project config if build succeeded if (summary.success) { try { const configManager = createConfigManager(projectPath); const simulator = await simulatorCache.findSimulatorByUdid(udidMatch[1]); await configManager.recordSuccessfulBuild(projectPath, udidMatch[1], simulator?.name); } catch (configError) { console.warn('Failed to save simulator preference:', configError); // Continue - config is optional } } } } // Store full output in cache const cacheId = responseCache.store({ tool: 'xcodebuild-build', fullOutput: result.stdout, stderr: result.stderr, exitCode: result.code, command, metadata: { projectPath, scheme: finalConfig.scheme, configuration: finalConfig.configuration, destination: finalConfig.destination, sdk: finalConfig.sdk, duration, success: summary.success, errorCount: summary.errorCount, warningCount: summary.warningCount, smartDestinationUsed: !destination && smartDestination !== destination, smartConfigurationUsed: !args.configuration && finalConfig.configuration !== 'Debug', }, }); // Create concise response with smart defaults transparency const usedSmartDestination = !destination && smartDestination; const usedSmartConfiguration = !configuration && finalConfig.configuration !== 'Debug'; const hasPreferredConfig = !!preferredConfig; // Handle auto-install if enabled and build succeeded let autoInstallResult = undefined; if (autoInstall && summary.success) { try { console.error('[xcodebuild-build] Starting auto-install...'); autoInstallResult = await performAutoInstall({ projectPath, scheme, configuration: finalConfig.configuration, simulatorUdid, bootSimulator, }); } catch (installError) { console.error('[xcodebuild-build] Auto-install failed:', installError); autoInstallResult = { success: false, error: installError instanceof Error ? installError.message : String(installError), }; } } const responseData = { buildId: cacheId, success: summary.success, summary: { ...summary, scheme: finalConfig.scheme, configuration: finalConfig.configuration, destination: finalConfig.destination, duration, }, autoInstall: autoInstallResult, intelligence: { usedSmartDestination, usedSmartConfiguration, hasPreferredConfig, simulatorUsageRecorded: !!( finalConfig.destination && finalConfig.destination.includes('Simulator') ), configurationLearned: summary.success, // Successful builds get remembered autoInstallAttempted: autoInstall && summary.success, }, guidance: summary.success ? [ `Build completed successfully in ${duration}ms`, ...(usedSmartDestination ? [`Used smart simulator: ${finalConfig.destination}`] : []), ...(hasPreferredConfig ? [`Applied cached project preferences`] : []), `Use 'xcodebuild-get-details' with buildId '${cacheId}' for full logs`, `Successful configuration cached for future builds`, ...(autoInstall ? [ autoInstallResult?.success ? `✅ Auto-install succeeded. App ready to launch with: simctl-launch udid="${autoInstallResult.udid}" bundleId="${autoInstallResult.bundleId}"` : `❌ Auto-install failed: ${autoInstallResult?.error}. Try manual install with simctl-install.`, ] : []), ] : [ `Build failed with ${summary.errorCount} errors, ${summary.warningCount} warnings`, `First error: ${summary.firstError || 'Unknown error'}`, `Use 'xcodebuild-get-details' with buildId '${cacheId}' for full logs and errors`, ...(usedSmartDestination ? [`Try simctl-list to see other available simulators`] : []), ], cacheDetails: { note: 'Use xcodebuild-get-details with buildId for full logs', availableTypes: ['full-log', 'errors-only', 'warnings-only', 'summary', 'command'], }, }; const responseText = JSON.stringify(responseData, null, 2); return { content: [ { type: 'text' as const, text: responseText, }, ], isError: !summary.success, }; } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError( ErrorCode.InternalError, `xcodebuild-build failed: ${error instanceof Error ? error.message : String(error)}` ); } }
  • MCP server registration of the 'xcodebuild-build' tool, including Zod input schema, description from docs, and wrapper handler that delegates to xcodebuildBuildTool after validation.
    server.registerTool( 'xcodebuild-build', { description: getDescription(XCODEBUILD_BUILD_DOCS, XCODEBUILD_BUILD_DOCS_MINI), inputSchema: { projectPath: z.string(), scheme: z.string(), configuration: z.string().default('Debug'), destination: z.string().optional(), sdk: z.string().optional(), derivedDataPath: z.string().optional(), }, ...DEFER_LOADING_CONFIG, }, async args => { try { await validateXcodeInstallation(); return await xcodebuildBuildTool(args); } catch (error) { if (error instanceof McpError) throw error; throw new McpError( ErrorCode.InternalError, `Tool execution failed: ${error instanceof Error ? error.message : String(error)}` ); } } );
  • TypeScript interface defining the expected input parameters for the xcodebuildBuildTool handler, used for type safety.
    interface BuildToolArgs { projectPath: string; scheme: string; configuration?: string; destination?: string; sdk?: string; derivedDataPath?: string; // Auto-install options autoInstall?: boolean; simulatorUdid?: string; bootSimulator?: boolean; }
  • Helper function for optional auto-installation of built app to simulator, integrating with build artifacts detection, simulator cache, and simctl install/boot tools.
    async function performAutoInstall(args: AutoInstallArgs): Promise<any> { const { projectPath, scheme, configuration, simulatorUdid, bootSimulator } = args; // Dynamic imports to avoid circular dependencies const { findBuildArtifacts } = await import('../../utils/build-artifacts.js'); const { simctlBootTool } = await import('../simctl/boot.js'); const { simctlInstallTool } = await import('../simctl/install.js'); // Step 1: Find build artifacts console.error('[auto-install] Finding build artifacts...'); const artifacts = await findBuildArtifacts(projectPath, scheme, configuration); if (!artifacts.appPath) { throw new Error(`Could not find .app bundle for scheme "${scheme}"`); } // Step 2: Determine simulator to install to let targetUdid = simulatorUdid; let targetName = ''; if (!targetUdid) { // Try to suggest best simulator const suggestion = await simulatorCache.getBestSimulator(projectPath); if (suggestion) { targetUdid = suggestion.simulator.udid; targetName = suggestion.simulator.name; console.error(`[auto-install] Auto-selected simulator: ${targetName}`); } else { throw new Error('No suitable simulator found. Create a simulator or specify simulatorUdid.'); } } else { // Get name of specified simulator const sim = await simulatorCache.findSimulatorByUdid(targetUdid); targetName = sim?.name || targetUdid; } // Step 3: Boot simulator if needed if (bootSimulator) { console.error(`[auto-install] Booting simulator: ${targetName}`); try { await simctlBootTool({ udid: targetUdid }); } catch (bootError) { // Don't fail completely if boot fails, simulator might already be booted console.warn('[auto-install] Boot failed (may already be booted):', bootError); } } // Step 4: Install app console.error(`[auto-install] Installing app to ${targetName}...`); const installResult = await simctlInstallTool({ udid: targetUdid, appPath: artifacts.appPath, }); if (!installResult.isError && installResult.content?.[0]?.text) { const installText = installResult.content[0].text; const parsedInstall = typeof installText === 'string' ? JSON.parse(installText) : installText; return { success: true, udid: targetUdid, simulatorName: targetName, appPath: artifacts.appPath, bundleId: artifacts.bundleIdentifier || parsedInstall.bundleId, duration: Date.now(), }; } throw new Error(`Installation failed: ${installResult.content?.[0]?.text || 'Unknown error'}`); }

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