build_ios_dev_ws
Compiles iOS applications for physical devices by specifying a workspace path and scheme. Supports custom build configurations, derived data paths, and additional xcodebuild arguments for tailored setups.
Instructions
Builds an iOS app from a workspace for a physical device. IMPORTANT: Requires workspacePath and scheme. Example: build_ios_dev_ws({ workspacePath: '/path/to/MyProject.xcworkspace', scheme: 'MyScheme' })
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| configuration | No | Build configuration (Debug, Release, etc.) | |
| derivedDataPath | No | Path where build products and other derived data will go | |
| extraArgs | No | Additional xcodebuild arguments | |
| preferXcodebuild | No | If true, prefers xcodebuild over the experimental incremental build system, useful for when incremental build system fails. | |
| scheme | Yes | The scheme to use (Required) | |
| workspacePath | Yes | Path to the .xcworkspace file (Required) |
Implementation Reference
- src/tools/build_ios_device.ts:36-71 (registration)Direct registration of the 'build_ios_dev_ws' tool, including schema definition and inline handler function.export function registerIOSDeviceBuildWorkspaceTool(server: McpServer): void { type Params = BaseWorkspaceParams; registerTool<Params>( server, 'build_ios_dev_ws', "Builds an iOS app from a workspace for a physical device. IMPORTANT: Requires workspacePath and scheme. Example: build_ios_dev_ws({ workspacePath: '/path/to/MyProject.xcworkspace', scheme: 'MyScheme' })", { workspacePath: workspacePathSchema, scheme: schemeSchema, configuration: configurationSchema, derivedDataPath: derivedDataPathSchema, extraArgs: extraArgsSchema, preferXcodebuild: preferXcodebuildSchema, }, async (params: Params) => { const workspaceValidation = validateRequiredParam('workspacePath', params.workspacePath); if (!workspaceValidation.isValid) return workspaceValidation.errorResponse!; const schemeValidation = validateRequiredParam('scheme', params.scheme); if (!schemeValidation.isValid) return schemeValidation.errorResponse!; return executeXcodeBuildCommand( { ...params, configuration: params.configuration ?? 'Debug', // Default config }, { platform: XcodePlatform.iOS, logPrefix: 'iOS Device Build', }, params.preferXcodebuild, 'build', ); }, ); }
- src/tools/build_ios_device.ts:50-69 (handler)The handler function for executing the tool: validates required parameters and invokes the shared Xcode build command with iOS device configuration.async (params: Params) => { const workspaceValidation = validateRequiredParam('workspacePath', params.workspacePath); if (!workspaceValidation.isValid) return workspaceValidation.errorResponse!; const schemeValidation = validateRequiredParam('scheme', params.scheme); if (!schemeValidation.isValid) return schemeValidation.errorResponse!; return executeXcodeBuildCommand( { ...params, configuration: params.configuration ?? 'Debug', // Default config }, { platform: XcodePlatform.iOS, logPrefix: 'iOS Device Build', }, params.preferXcodebuild, 'build', ); },
- src/utils/register-tools.ts:258-262 (registration)Top-level tool group registration that enables the iOS device build tools (including build_ios_dev_ws) based on environment variable.{ register: registerIOSDeviceBuildTools, groups: [ToolGroup.IOS_DEVICE_WORKFLOW], envVar: 'XCODEBUILDMCP_TOOL_IOS_DEVICE_BUILD_TOOLS', },
- src/utils/build-utils.ts:43-341 (helper)Core helper utility that implements the actual Xcode build logic: constructs the xcodebuild command, supports xcodemake for incremental builds, parses output for warnings/errors, and formats responses.export async function executeXcodeBuildCommand( params: SharedBuildParams, platformOptions: PlatformBuildOptions, preferXcodebuild: boolean = false, buildAction: string = 'build', ): Promise<ToolResponse> { // Collect warnings, errors, and stderr messages from the build output const buildMessages: { type: 'text'; text: string }[] = []; function grepWarningsAndErrors(text: string): { type: 'warning' | 'error'; content: string }[] { return text .split('\n') .map((content) => { if (/warning:/i.test(content)) return { type: 'warning', content }; if (/error:/i.test(content)) return { type: 'error', content }; return null; }) .filter(Boolean) as { type: 'warning' | 'error'; content: string }[]; } log('info', `Starting ${platformOptions.logPrefix} ${buildAction} for scheme ${params.scheme}`); // Check if xcodemake is enabled and available const isXcodemakeEnabledFlag = isXcodemakeEnabled(); let xcodemakeAvailableFlag = false; if (isXcodemakeEnabledFlag && buildAction === 'build') { xcodemakeAvailableFlag = await isXcodemakeAvailable(); if (xcodemakeAvailableFlag && preferXcodebuild) { log( 'info', 'xcodemake is enabled but preferXcodebuild is set to true. Falling back to xcodebuild.', ); buildMessages.push({ type: 'text', text: '⚠️ incremental build support is enabled but preferXcodebuild is set to true. Falling back to xcodebuild.', }); } else if (!xcodemakeAvailableFlag) { buildMessages.push({ type: 'text', text: '⚠️ xcodemake is enabled but not available. Falling back to xcodebuild.', }); log('info', 'xcodemake is enabled but not available. Falling back to xcodebuild.'); } else { log('info', 'xcodemake is enabled and available, using it for incremental builds.'); buildMessages.push({ type: 'text', text: 'ℹ️ xcodemake is enabled and available, using it for incremental builds.', }); } } try { const command = ['xcodebuild']; let projectDir = ''; if (params.workspacePath) { projectDir = path.dirname(params.workspacePath); command.push('-workspace', params.workspacePath); } else if (params.projectPath) { projectDir = path.dirname(params.projectPath); command.push('-project', params.projectPath); } command.push('-scheme', params.scheme); command.push('-configuration', params.configuration); command.push('-skipMacroValidation'); // Construct destination string based on platform let destinationString: string; const isSimulatorPlatform = [ XcodePlatform.iOSSimulator, XcodePlatform.watchOSSimulator, XcodePlatform.tvOSSimulator, XcodePlatform.visionOSSimulator, ].includes(platformOptions.platform); if (isSimulatorPlatform) { if (platformOptions.simulatorId) { destinationString = constructDestinationString( platformOptions.platform, undefined, platformOptions.simulatorId, ); } else if (platformOptions.simulatorName) { destinationString = constructDestinationString( platformOptions.platform, platformOptions.simulatorName, undefined, platformOptions.useLatestOS, ); } else { return createTextResponse( `For ${platformOptions.platform} platform, either simulatorId or simulatorName must be provided`, true, ); } } else if (platformOptions.platform === XcodePlatform.macOS) { destinationString = constructDestinationString( platformOptions.platform, undefined, undefined, false, platformOptions.arch, ); } else if (platformOptions.platform === XcodePlatform.iOS) { destinationString = 'generic/platform=iOS'; } else if (platformOptions.platform === XcodePlatform.watchOS) { destinationString = 'generic/platform=watchOS'; } else if (platformOptions.platform === XcodePlatform.tvOS) { destinationString = 'generic/platform=tvOS'; } else if (platformOptions.platform === XcodePlatform.visionOS) { destinationString = 'generic/platform=visionOS'; } else { return createTextResponse(`Unsupported platform: ${platformOptions.platform}`, true); } command.push('-destination', destinationString); if (params.derivedDataPath) { command.push('-derivedDataPath', params.derivedDataPath); } if (params.extraArgs && params.extraArgs.length > 0) { command.push(...params.extraArgs); } command.push(buildAction); // Execute the command using xcodemake or xcodebuild let result; if ( isXcodemakeEnabledFlag && xcodemakeAvailableFlag && buildAction === 'build' && !preferXcodebuild ) { // Check if Makefile already exists const makefileExists = doesMakefileExist(projectDir); log('debug', 'Makefile exists: ' + makefileExists); // Check if Makefile log already exists const makeLogFileExists = doesMakeLogFileExist(projectDir, command); log('debug', 'Makefile log exists: ' + makeLogFileExists); if (makefileExists && makeLogFileExists) { // Use make for incremental builds buildMessages.push({ type: 'text', text: 'ℹ️ Using make for incremental build', }); result = await executeMakeCommand(projectDir, platformOptions.logPrefix); } else { // Generate Makefile using xcodemake buildMessages.push({ type: 'text', text: 'ℹ️ Generating Makefile with xcodemake (first build may take longer)', }); // Remove 'xcodebuild' from the command array before passing to executeXcodemakeCommand result = await executeXcodemakeCommand( projectDir, command.slice(1), platformOptions.logPrefix, ); } } else { // Use standard xcodebuild result = await executeCommand(command, platformOptions.logPrefix); } // Grep warnings and errors from stdout (build output) const warningOrErrorLines = grepWarningsAndErrors(result.output); warningOrErrorLines.forEach(({ type, content }) => { buildMessages.push({ type: 'text', text: type === 'warning' ? `⚠️ Warning: ${content}` : `❌ Error: ${content}`, }); }); // Include all stderr lines as errors if (result.error) { result.error.split('\n').forEach((content) => { if (content.trim()) { buildMessages.push({ type: 'text', text: `❌ [stderr] ${content}` }); } }); } if (!result.success) { log('error', `${platformOptions.logPrefix} ${buildAction} failed: ${result.error}`); // Create concise error response with warnings/errors included const errorResponse = createTextResponse( `❌ ${platformOptions.logPrefix} ${buildAction} failed for scheme ${params.scheme}.`, true, ); if (buildMessages.length > 0 && errorResponse.content) { errorResponse.content.unshift(...buildMessages); } // If using xcodemake and build failed but no compiling errors, suggest using xcodebuild if ( warningOrErrorLines.length == 0 && isXcodemakeEnabledFlag && xcodemakeAvailableFlag && buildAction === 'build' && !preferXcodebuild ) { errorResponse.content.push({ type: 'text', text: `💡 Incremental build using xcodemake failed, suggest using preferXcodebuild option to try build again using slower xcodebuild command.`, }); } return errorResponse; } log('info', `✅ ${platformOptions.logPrefix} ${buildAction} succeeded.`); // Create additional info based on platform and action let additionalInfo = ''; // Add xcodemake info if relevant if ( isXcodemakeEnabledFlag && xcodemakeAvailableFlag && buildAction === 'build' && !preferXcodebuild ) { additionalInfo += `xcodemake: Using faster incremental builds with xcodemake. Future builds will use the generated Makefile for improved performance. `; } // Only show next steps for 'build' action if (buildAction === 'build') { if (platformOptions.platform === XcodePlatform.macOS) { additionalInfo = `Next Steps: 1. Get App Path: get_macos_app_path_${params.workspacePath ? 'workspace' : 'project'} 2. Get Bundle ID: get_macos_bundle_id 3. Launch App: launch_macos_app`; } else if (platformOptions.platform === XcodePlatform.iOS) { additionalInfo = `Next Steps: 1. Get App Path: get_ios_device_app_path_${params.workspacePath ? 'workspace' : 'project'} 2. Get Bundle ID: get_ios_bundle_id`; } else if (isSimulatorPlatform) { const idOrName = platformOptions.simulatorId ? 'id' : 'name'; const simIdParam = platformOptions.simulatorId ? 'simulatorId' : 'simulatorName'; const simIdValue = platformOptions.simulatorId || platformOptions.simulatorName; additionalInfo = `Next Steps: 1. Get App Path: get_simulator_app_path_by_${idOrName}_${params.workspacePath ? 'workspace' : 'project'}({ ${simIdParam}: '${simIdValue}', scheme: '${params.scheme}' }) 2. Get Bundle ID: get_ios_bundle_id({ appPath: 'APP_PATH_FROM_STEP_1' }) 3. Choose one of the following options: - Option 1: Launch app normally: launch_app_in_simulator({ simulatorUuid: 'SIMULATOR_UUID', bundleId: 'APP_BUNDLE_ID' }) - Option 2: Launch app with logs (captures both console and structured logs): launch_app_with_logs_in_simulator({ simulatorUuid: 'SIMULATOR_UUID', bundleId: 'APP_BUNDLE_ID' }) - Option 3: Launch app normally, then capture structured logs only: launch_app_in_simulator({ simulatorUuid: 'SIMULATOR_UUID', bundleId: 'APP_BUNDLE_ID' }) start_simulator_log_capture({ simulatorUuid: 'SIMULATOR_UUID', bundleId: 'APP_BUNDLE_ID' }) - Option 4: Launch app normally, then capture all logs (will restart app): launch_app_in_simulator({ simulatorUuid: 'SIMULATOR_UUID', bundleId: 'APP_BUNDLE_ID' }) start_simulator_log_capture({ simulatorUuid: 'SIMULATOR_UUID', bundleId: 'APP_BUNDLE_ID', captureConsole: true }) When done capturing logs, use: stop_and_get_simulator_log({ logSessionId: 'SESSION_ID' })`; } } const successResponse: ToolResponse = { content: [ ...buildMessages, { type: 'text', text: `✅ ${platformOptions.logPrefix} ${buildAction} succeeded for scheme ${params.scheme}.`, }, ], }; // Only add additional info if we have any if (additionalInfo) { successResponse.content.push({ type: 'text', text: additionalInfo, }); } return successResponse; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); log('error', `Error during ${platformOptions.logPrefix} ${buildAction}: ${errorMessage}`); return createTextResponse( `Error during ${platformOptions.logPrefix} ${buildAction}: ${errorMessage}`, true, ); } }
- src/tools/common.ts:22-60 (schema)Zod schema definitions for common parameters used in the build_ios_dev_ws tool schema.export const workspacePathSchema = z.string().describe('Path to the .xcworkspace file (Required)'); export const projectPathSchema = z.string().describe('Path to the .xcodeproj file (Required)'); export const schemeSchema = z.string().describe('The scheme to use (Required)'); export const configurationSchema = z .string() .optional() .describe('Build configuration (Debug, Release, etc.)'); export const derivedDataPathSchema = z .string() .optional() .describe('Path where build products and other derived data will go'); export const extraArgsSchema = z .array(z.string()) .optional() .describe('Additional xcodebuild arguments'); export const simulatorNameSchema = z .string() .describe("Name of the simulator to use (e.g., 'iPhone 16') (Required)"); export const simulatorIdSchema = z .string() .describe('UUID of the simulator to use (obtained from listSimulators) (Required)'); export const useLatestOSSchema = z .boolean() .optional() .describe('Whether to use the latest OS version for the named simulator'); export const appPathSchema = z .string() .describe('Path to the .app bundle (full path to the .app directory)'); export const bundleIdSchema = z .string() .describe("Bundle identifier of the app (e.g., 'com.example.MyApp')"); export const launchArgsSchema = z .array(z.string()) .optional() .describe('Additional arguments to pass to the app'); export const preferXcodebuildSchema = z .boolean() .optional() .describe(