Skip to main content
Glama

XC-MCP: XCode CLI wrapper

by conorluddy
build.ts6.54 kB
import { validateProjectPath, validateScheme } from '../../utils/validation.js'; import { executeCommand, buildXcodebuildCommand } from '../../utils/command.js'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { responseCache, extractBuildSummary } from '../../utils/response-cache.js'; import { projectCache, type BuildConfig } from '../../state/project-cache.js'; import { simulatorCache } from '../../state/simulator-cache.js'; interface BuildToolArgs { projectPath: string; scheme: string; configuration?: string; destination?: string; sdk?: string; derivedDataPath?: string; } export async function xcodebuildBuildTool(args: any) { const { projectPath, scheme, configuration = 'Debug', destination, sdk, derivedDataPath, } = 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); } } // 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; const responseData = { buildId: cacheId, success: summary.success, summary: { ...summary, scheme: finalConfig.scheme, configuration: finalConfig.configuration, destination: finalConfig.destination, duration, }, intelligence: { usedSmartDestination, usedSmartConfiguration, hasPreferredConfig, simulatorUsageRecorded: !!( finalConfig.destination && finalConfig.destination.includes('Simulator') ), configurationLearned: summary.success, // Successful builds get remembered }, nextSteps: 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`, `Tip: This successful configuration is now cached for future builds`, ] : [ `❌ 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`] : []), ], availableDetails: ['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)}` ); } } async function getSmartDestination( preferredConfig: BuildConfig | null, projectPath: string ): Promise<string | undefined> { // If preferred config has a destination, use it if (preferredConfig?.destination) { return preferredConfig.destination; } // Try to get a smart simulator destination with project-specific preference try { const preferredSim = await simulatorCache.getPreferredSimulator(projectPath); if (preferredSim) { return `platform=iOS Simulator,id=${preferredSim.udid}`; } } catch { // Fallback to no destination if simulator cache fails } // Return undefined to let xcodebuild use its own defaults return undefined; }

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