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'}`);
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It effectively describes key behavioral traits: learning from builds, smart defaults, performance tracking, progressive disclosure of logs, intelligent caching, and better error handling. However, it doesn't mention potential side effects like file system changes or resource consumption.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the main purpose, but contains some redundancy (the 'Features smart caching' sentence repeats earlier points). The bullet points are helpful but could be more concise. Overall, it's reasonably structured but could be tightened to eliminate repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (6 parameters, no output schema, no annotations), the description does a good job explaining the enhanced behavior over raw xcodebuild. It covers the intelligent features well, though it could benefit from mentioning what the tool returns (build status, cache IDs, etc.) since there's no output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all parameters thoroughly. The description doesn't add any parameter-specific information beyond what's in the schema. It mentions 'smart defaults' and 'intelligent caching' which relate to parameter behavior generally, but doesn't provide additional semantic context for individual parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description explicitly states the tool's purpose: 'Intelligent building with learning, caching, and performance tracking' and contrasts it with raw 'xcodebuild'. It clearly distinguishes this tool from its siblings like 'xcodebuild-clean' and 'xcodebuild-list' by emphasizing enhanced features beyond basic Xcode build functionality.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance: 'Prefer this over raw 'xcodebuild'' and includes a dedicated 'Why use this instead of direct xcodebuild' section with bullet points. It clearly positions this tool as the preferred alternative to direct xcodebuild commands, though it doesn't explicitly compare to other sibling tools like 'xcodebuild-clean'.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Related Tools

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