Skip to main content
Glama

xcode_build

Build Xcode projects or workspaces with specified schemes using the active or provided destination. Automates compilation processes directly within Xcode for development workflows.

Instructions

Build a specific Xcode project or workspace with the specified scheme. If destination is not provided, uses the currently active destination. ⏱️ Can take minutes to hours - do not timeout.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
xcodeprojYesAbsolute path to the .xcodeproj file to build (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj
schemeYesName of the scheme to build
destinationNoBuild destination (optional - uses active destination if not provided)

Implementation Reference

  • Primary handler function for xcode_build tool execution. Handles project validation, Xcode automation via JXA to set scheme/destination and start build, build log monitoring/parsing, error/warning extraction, and result formatting.
    export class BuildTools { public static async build( projectPath: string, schemeName: string, destination: string | null = null, openProject: OpenProjectCallback ): Promise<McpResult> { const validationError = PathValidator.validateProjectPath(projectPath); if (validationError) return validationError; await openProject(projectPath); // Normalize the scheme name for better matching const normalizedSchemeName = ParameterNormalizer.normalizeSchemeName(schemeName); const setSchemeScript = ` (function() { ${getWorkspaceByPathScript(projectPath)} const schemes = workspace.schemes(); const schemeNames = schemes.map(scheme => scheme.name()); // Try exact match first let targetScheme = schemes.find(scheme => scheme.name() === ${JSON.stringify(normalizedSchemeName)}); // If not found, try original name if (!targetScheme) { targetScheme = schemes.find(scheme => scheme.name() === ${JSON.stringify(schemeName)}); } if (!targetScheme) { throw new Error('Scheme not found. Available: ' + JSON.stringify(schemeNames)); } workspace.activeScheme = targetScheme; return 'Scheme set to ' + targetScheme.name(); })() `; try { await JXAExecutor.execute(setSchemeScript); } catch (error) { const enhancedError = ErrorHelper.parseCommonErrors(error as Error); if (enhancedError) { return { content: [{ type: 'text', text: enhancedError }] }; } const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes('not found')) { try { // Get available schemes const availableSchemes = await this._getAvailableSchemes(projectPath); // Try to find a close match with fuzzy matching const bestMatch = ParameterNormalizer.findBestMatch(schemeName, availableSchemes); let message = `❌ Scheme '${schemeName}' not found\n\nAvailable schemes:\n`; availableSchemes.forEach(scheme => { if (scheme === bestMatch) { message += ` • ${scheme} ← Did you mean this?\n`; } else { message += ` • ${scheme}\n`; } }); return { content: [{ type: 'text', text: message }] }; } catch { return { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`Scheme '${schemeName}' not found`, ErrorHelper.getSchemeNotFoundGuidance(schemeName)) }] }; } } return { content: [{ type: 'text', text: `Failed to set scheme '${schemeName}': ${errorMessage}` }] }; } if (destination) { // Normalize the destination name for better matching const normalizedDestination = ParameterNormalizer.normalizeDestinationName(destination); const setDestinationScript = ` (function() { ${getWorkspaceByPathScript(projectPath)} const destinations = workspace.runDestinations(); const destinationNames = destinations.map(dest => dest.name()); // Try exact match first let targetDestination = destinations.find(dest => dest.name() === ${JSON.stringify(normalizedDestination)}); // If not found, try original name if (!targetDestination) { targetDestination = destinations.find(dest => dest.name() === ${JSON.stringify(destination)}); } if (!targetDestination) { throw new Error('Destination not found. Available: ' + JSON.stringify(destinationNames)); } workspace.activeRunDestination = targetDestination; return 'Destination set to ' + targetDestination.name(); })() `; try { await JXAExecutor.execute(setDestinationScript); } catch (error) { const enhancedError = ErrorHelper.parseCommonErrors(error as Error); if (enhancedError) { return { content: [{ type: 'text', text: enhancedError }] }; } const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes('not found')) { try { // Extract available destinations from error message if present let availableDestinations: string[] = []; if (errorMessage.includes('Available:')) { const availablePart = errorMessage.split('Available: ')[1]; // Find the JSON array part const jsonMatch = availablePart?.match(/\[.*?\]/); if (jsonMatch) { try { availableDestinations = JSON.parse(jsonMatch[0]); } catch { availableDestinations = await this._getAvailableDestinations(projectPath); } } } else { availableDestinations = await this._getAvailableDestinations(projectPath); } // Try to find a close match with fuzzy matching const bestMatch = ParameterNormalizer.findBestMatch(destination, availableDestinations); let guidance = ErrorHelper.getDestinationNotFoundGuidance(destination, availableDestinations); if (bestMatch && bestMatch !== destination) { guidance += `\n• Did you mean '${bestMatch}'?`; } return { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`Destination '${destination}' not found`, guidance) }] }; } catch { return { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`Destination '${destination}' not found`, ErrorHelper.getDestinationNotFoundGuidance(destination)) }] }; } } return { content: [{ type: 'text', text: `Failed to set destination '${destination}': ${errorMessage}` }] }; } } const buildScript = ` (function() { ${getWorkspaceByPathScript(projectPath)} workspace.build(); return 'Build started'; })() `; const buildStartTime = Date.now(); try { await JXAExecutor.execute(buildScript); // Check for and handle "replace existing build" alert await this._handleReplaceExistingBuildAlert(); } catch (error) { const enhancedError = ErrorHelper.parseCommonErrors(error as Error); if (enhancedError) { return { content: [{ type: 'text', text: enhancedError }] }; } const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: 'text', text: `Failed to start build: ${errorMessage}` }] }; } Logger.info('Waiting for new build log to appear after build start...'); let attempts = 0; let newLog = null; const initialWaitAttempts = 3600; // 1 hour max to wait for build log while (attempts < initialWaitAttempts) { const currentLog = await BuildLogParser.getLatestBuildLog(projectPath); if (currentLog) { const logTime = currentLog.mtime.getTime(); const buildTime = buildStartTime; Logger.debug(`Checking log: ${currentLog.path}, log time: ${logTime}, build time: ${buildTime}, diff: ${logTime - buildTime}ms`); if (logTime > buildTime) { newLog = currentLog; Logger.info(`Found new build log created after build start: ${newLog.path}`); break; } } else { Logger.debug(`No build log found yet, attempt ${attempts + 1}/${initialWaitAttempts}`); } await new Promise(resolve => setTimeout(resolve, 1000)); attempts++; } if (!newLog) { return { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`Build started but no new build log appeared within ${initialWaitAttempts} seconds`, ErrorHelper.getBuildLogNotFoundGuidance()) }] }; } Logger.info(`Monitoring build completion for log: ${newLog.path}`); attempts = 0; const maxAttempts = 3600; // 1 hour max for build completion let lastLogSize = 0; let stableCount = 0; while (attempts < maxAttempts) { try { const logStats = await stat(newLog.path); const currentLogSize = logStats.size; if (currentLogSize === lastLogSize) { stableCount++; if (stableCount >= 1) { Logger.debug(`Log stable for ${stableCount}s, trying to parse...`); const results = await BuildLogParser.parseBuildLog(newLog.path); Logger.debug(`Parse result has ${results.errors.length} errors, ${results.warnings.length} warnings`); const isParseFailure = results.errors.some(error => typeof error === 'string' && error.includes('XCLogParser failed to parse the build log.') ); if (results && !isParseFailure) { Logger.info(`Build completed, log parsed successfully: ${newLog.path}`); break; } } } else { lastLogSize = currentLogSize; stableCount = 0; } } catch (error) { const currentLog = await BuildLogParser.getLatestBuildLog(projectPath); if (currentLog && currentLog.path !== newLog.path && currentLog.mtime.getTime() > buildStartTime) { Logger.debug(`Build log changed to: ${currentLog.path}`); newLog = currentLog; lastLogSize = 0; stableCount = 0; } } await new Promise(resolve => setTimeout(resolve, 1000)); attempts++; } if (attempts >= maxAttempts) { return { content: [{ type: 'text', text: `Build timed out after ${maxAttempts} seconds` }] }; } const results = await BuildLogParser.parseBuildLog(newLog.path); let message = ''; const schemeInfo = schemeName ? ` for scheme '${schemeName}'` : ''; const destInfo = destination ? ` and destination '${destination}'` : ''; Logger.info(`Build completed${schemeInfo}${destInfo} - ${results.errors.length} errors, ${results.warnings.length} warnings, status: ${results.buildStatus || 'unknown'}`); // Handle stopped/interrupted builds if (results.buildStatus === 'stopped') { message = `⏹️ BUILD INTERRUPTED${schemeInfo}${destInfo}\n\nThe build was stopped or interrupted before completion.\n\n💡 This may happen when:\n • The build was cancelled manually\n • Xcode was closed during the build\n • System resources were exhausted\n\nTry running the build again to complete it.`; return { content: [{ type: 'text', text: message }] }; } if (results.errors.length > 0) { message = `❌ BUILD FAILED${schemeInfo}${destInfo} (${results.errors.length} errors)\n\nERRORS:\n`; results.errors.forEach(error => { message += ` • ${error}\n`; Logger.error('Build error:', error); }); throw new McpError( ErrorCode.InternalError, message ); } else if (results.warnings.length > 0) { message = `⚠️ BUILD COMPLETED WITH WARNINGS${schemeInfo}${destInfo} (${results.warnings.length} warnings)\n\nWARNINGS:\n`; results.warnings.forEach(warning => { message += ` • ${warning}\n`; Logger.warn('Build warning:', warning); }); } else { message = `✅ BUILD SUCCESSFUL${schemeInfo}${destInfo}`; } return { content: [{ type: 'text', text: message }] }; }
  • Dispatch handler in XcodeServer's CallToolRequestSchema that validates parameters and delegates to BuildTools.build
    case 'xcode_build': if (!args.xcodeproj) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); } if (!args.scheme) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: scheme`); } return await BuildTools.build( args.xcodeproj as string, args.scheme as string, (args.destination as string) || null, this.openProject.bind(this) );
  • Tool definition including name, description, and input schema/JSON Schema validation for xcode_build
    { name: 'xcode_build', description: 'Build a specific Xcode project or workspace with the specified scheme. If destination is not provided, uses the currently active destination. ⏱️ Can take minutes to hours - do not timeout.', inputSchema: { type: 'object', properties: { xcodeproj: { type: 'string', description: preferredXcodeproj ? `Absolute path to the .xcodeproj file to build (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` : 'Absolute path to the .xcodeproj file to build (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', }, scheme: { type: 'string', description: preferredScheme ? `Name of the scheme to build - defaults to ${preferredScheme}` : 'Name of the scheme to build', }, destination: { type: 'string', description: 'Build destination (optional - uses active destination if not provided)', }, }, required: [ ...(!preferredXcodeproj ? ['xcodeproj'] : []), ...(!preferredScheme ? ['scheme'] : []) ], }, },
  • Registration of all tools including xcode_build via ListToolsRequestSchema handler using getToolDefinitions()
    this.server.setRequestHandler(ListToolsRequestSchema, async () => { const toolOptions: { includeClean: boolean; preferredScheme?: string; preferredXcodeproj?: string; } = { includeClean: this.includeClean }; if (this.preferredScheme) toolOptions.preferredScheme = this.preferredScheme; if (this.preferredXcodeproj) toolOptions.preferredXcodeproj = this.preferredXcodeproj; const toolDefinitions = getToolDefinitions(toolOptions); return { tools: toolDefinitions.map(tool => ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema })), }; });
  • Tool definitions array where xcode_build is registered/defined for use in ListTools response.
    const tools: ToolDefinition[] = [ { name: 'xcode_open_project', description: 'Open an Xcode project or workspace', inputSchema: { type: 'object', properties: { xcodeproj: { type: 'string', description: preferredXcodeproj ? `Absolute path to the .xcodeproj file (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` : 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', }, }, required: preferredXcodeproj ? [] : ['xcodeproj'], }, }, { name: 'xcode_close_project', description: 'Close the currently active Xcode project or workspace (automatically stops any running actions first)', inputSchema: { type: 'object', properties: { xcodeproj: { type: 'string', description: preferredXcodeproj ? `Absolute path to the .xcodeproj file (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` : 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', }, }, required: preferredXcodeproj ? [] : ['xcodeproj'], }, }, { name: 'xcode_build', description: 'Build a specific Xcode project or workspace with the specified scheme. If destination is not provided, uses the currently active destination. ⏱️ Can take minutes to hours - do not timeout.', inputSchema: { type: 'object', properties: { xcodeproj: { type: 'string', description: preferredXcodeproj ? `Absolute path to the .xcodeproj file to build (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` : 'Absolute path to the .xcodeproj file to build (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', }, scheme: { type: 'string', description: preferredScheme ? `Name of the scheme to build - defaults to ${preferredScheme}` : 'Name of the scheme to build', }, destination: { type: 'string', description: 'Build destination (optional - uses active destination if not provided)', }, }, required: [ ...(!preferredXcodeproj ? ['xcodeproj'] : []), ...(!preferredScheme ? ['scheme'] : []) ], }, },

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/lapfelix/XcodeMCP'

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