scaffold_ios_project
Create a modern iOS Xcode project with workspace structure, SPM package for features, and proper iOS configuration from templates.
Instructions
Scaffold a new iOS project from templates. Creates a modern Xcode project with workspace structure, SPM package for features, and proper iOS configuration.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| projectName | Yes | Name of the new project | |
| outputPath | Yes | Path where the project should be created | |
| bundleIdentifier | No | Bundle identifier (e.g., com.example.myapp). If not provided, will use com.example.projectname | |
| displayName | No | App display name (shown on home screen/dock). If not provided, will use projectName | |
| marketingVersion | No | Marketing version (e.g., 1.0, 2.1.3). If not provided, will use 1.0 | |
| currentProjectVersion | No | Build number (e.g., 1, 42, 100). If not provided, will use 1 | |
| customizeNames | No | Whether to customize project names and identifiers. Default is true. | |
| deploymentTarget | No | iOS deployment target (e.g., 18.4, 17.0). If not provided, will use 18.4 | |
| targetedDeviceFamily | No | Target device family. If not provided, will use iPhone+iPad | |
| supportedOrientations | No | Supported orientations for iPhone. If not provided, will use all orientations | |
| supportedOrientationsIpad | No | Supported orientations for iPad. If not provided, will use all orientations |
Implementation Reference
- src/tools/scaffold.ts:42-59 (schema)Zod schema defining input parameters for the scaffold_ios_project tool, extending base schema with iOS-specific fields like deploymentTarget, targetedDeviceFamily, and orientations.const ScaffoldiOSProjectSchema = BaseScaffoldSchema.extend({ deploymentTarget: z .string() .optional() .describe('iOS deployment target (e.g., 18.4, 17.0). If not provided, will use 18.4'), targetedDeviceFamily: z .enum(['iPhone', 'iPad', 'iPhone+iPad']) .optional() .describe('Target device family. If not provided, will use iPhone+iPad'), supportedOrientations: z .array(z.enum(['Portrait', 'LandscapeLeft', 'LandscapeRight'])) .optional() .describe('Supported orientations for iPhone. If not provided, will use all orientations'), supportedOrientationsIpad: z .array(z.enum(['Portrait', 'PortraitUpsideDown', 'LandscapeLeft', 'LandscapeRight'])) .optional() .describe('Supported orientations for iPad. If not provided, will use all orientations'), });
- src/tools/scaffold.ts:380-418 (handler)Core handler function that performs the actual scaffolding: validates input, retrieves template, recursively processes files/directories with customizations, and returns the project path.async function scaffoldProject(params: ScaffoldProjectParams): Promise<string> { const { projectName, outputPath, platform, customizeNames = true } = params; log('info', `Scaffolding project: ${projectName} (${platform}) at ${outputPath}`); // Validate project name if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(projectName)) { throw new ValidationError( 'Project name must start with a letter and contain only letters, numbers, and underscores', ); } // Get template path from TemplateManager let templatePath: string; try { templatePath = await TemplateManager.getTemplatePath(platform); } catch (error) { throw new ValidationError( `Failed to get template for ${platform}: ${error instanceof Error ? error.message : String(error)}`, ); } // Create output directory const projectPath = join(outputPath, customizeNames ? projectName : 'MyProject'); if (existsSync(projectPath)) { throw new ValidationError(`Project directory already exists at ${projectPath}`); } try { // Process the template await processDirectory(templatePath, projectPath, params); return projectPath; } finally { // Clean up downloaded template if needed await TemplateManager.cleanup(templatePath); } }
- src/tools/scaffold.ts:422-475 (registration)Registers the scaffold_ios_project tool with the MCP server, providing name, description, schema, and an async handler that adds platform='iOS' and calls scaffoldProject, returning formatted success/error response.registerTool<ScaffoldiOSProjectParams>( server, 'scaffold_ios_project', 'Scaffold a new iOS project from templates. Creates a modern Xcode project with workspace structure, SPM package for features, and proper iOS configuration.', ScaffoldiOSProjectSchema.shape, async (params) => { try { const projectParams: ScaffoldProjectParams = { ...params, platform: 'iOS' }; const projectPath = await scaffoldProject(projectParams); const response = { success: true, projectPath, platform: 'iOS', message: `Successfully scaffolded iOS project "${params.projectName}" at ${projectPath}`, nextSteps: [ `Open the project: open ${projectPath}/${params.customizeNames ? params.projectName : 'MyProject'}.xcworkspace`, `Build for simulator: build_ios_sim_name_ws --workspace-path "${projectPath}/${params.customizeNames ? params.projectName : 'MyProject'}.xcworkspace" --scheme "${params.customizeNames ? params.projectName : 'MyProject'}" --simulator-name "iPhone 16"`, `Build and run on simulator: build_run_ios_sim_name_ws --workspace-path "${projectPath}/${params.customizeNames ? params.projectName : 'MyProject'}.xcworkspace" --scheme "${params.customizeNames ? params.projectName : 'MyProject'}" --simulator-name "iPhone 16"`, ], }; return { content: [ { type: 'text', text: JSON.stringify(response, null, 2), }, ], }; } catch (error) { log( 'error', `Failed to scaffold iOS project: ${error instanceof Error ? error.message : String(error)}`, ); return { content: [ { type: 'text', text: JSON.stringify( { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred', }, null, 2, ), }, ], }; } }, );
- src/tools/scaffold.ts:149-240 (helper)Helper function to customize .xcconfig files with project details, bundle ID, versions, iOS-specific deployment target, device family, and orientations.function updateXCConfigFile(content: string, params: ScaffoldProjectParams): string { let result = content; // Update project identity settings result = result.replace(/PRODUCT_NAME = .+/g, `PRODUCT_NAME = ${params.projectName}`); result = result.replace( /PRODUCT_DISPLAY_NAME = .+/g, `PRODUCT_DISPLAY_NAME = ${params.displayName || params.projectName}`, ); result = result.replace( /PRODUCT_BUNDLE_IDENTIFIER = .+/g, `PRODUCT_BUNDLE_IDENTIFIER = ${params.bundleIdentifier || `com.example.${params.projectName.toLowerCase().replace(/[^a-z0-9]/g, '')}`}`, ); result = result.replace( /MARKETING_VERSION = .+/g, `MARKETING_VERSION = ${params.marketingVersion || '1.0'}`, ); result = result.replace( /CURRENT_PROJECT_VERSION = .+/g, `CURRENT_PROJECT_VERSION = ${params.currentProjectVersion || '1'}`, ); // Platform-specific updates if (params.platform === 'iOS') { const iosParams = params as ScaffoldiOSProjectParams & { platform: 'iOS' }; // iOS deployment target if (iosParams.deploymentTarget) { result = result.replace( /IPHONEOS_DEPLOYMENT_TARGET = .+/g, `IPHONEOS_DEPLOYMENT_TARGET = ${iosParams.deploymentTarget}`, ); } // Device family if (iosParams.targetedDeviceFamily) { const deviceFamilyValue = deviceFamilyToNumeric(iosParams.targetedDeviceFamily); result = result.replace( /TARGETED_DEVICE_FAMILY = .+/g, `TARGETED_DEVICE_FAMILY = ${deviceFamilyValue}`, ); } // iPhone orientations if (iosParams.supportedOrientations && iosParams.supportedOrientations.length > 0) { // Filter out any empty strings and validate const validOrientations = iosParams.supportedOrientations.filter((o) => o && o.trim() !== ''); if (validOrientations.length > 0) { const orientations = validOrientations.map(orientationToIOSConstant).join(' '); result = result.replace( /INFOPLIST_KEY_UISupportedInterfaceOrientations = .+/g, `INFOPLIST_KEY_UISupportedInterfaceOrientations = ${orientations}`, ); } } // iPad orientations if (iosParams.supportedOrientationsIpad && iosParams.supportedOrientationsIpad.length > 0) { // Filter out any empty strings and validate const validOrientations = iosParams.supportedOrientationsIpad.filter( (o) => o && o.trim() !== '', ); if (validOrientations.length > 0) { const orientations = validOrientations.map(orientationToIOSConstant).join(' '); result = result.replace( /INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = .+/g, `INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = ${orientations}`, ); } } } else if (params.platform === 'macOS') { const macosParams = params as ScaffoldmacOSProjectParams & { platform: 'macOS' }; // macOS deployment target if (macosParams.deploymentTarget) { result = result.replace( /MACOSX_DEPLOYMENT_TARGET = .+/g, `MACOSX_DEPLOYMENT_TARGET = ${macosParams.deploymentTarget}`, ); } // Update entitlements path for macOS result = result.replace( /CODE_SIGN_ENTITLEMENTS = .+/g, `CODE_SIGN_ENTITLEMENTS = ${params.projectName}/${params.projectName}.entitlements`, ); } // Update test bundle identifier and target name result = result.replace(/TEST_TARGET_NAME = .+/g, `TEST_TARGET_NAME = ${params.projectName}`); return result;
- src/tools/scaffold.ts:342-374 (helper)Recursive helper to process template directories, customizing file/directory names and contents based on params.async function processDirectory( sourceDir: string, destDir: string, params: ScaffoldProjectParams, ): Promise<void> { const entries = await readdir(sourceDir, { withFileTypes: true }); for (const entry of entries) { const sourcePath = join(sourceDir, entry.name); let destName = entry.name; if (params.customizeNames) { // Replace MyProject in directory names destName = destName.replace(/MyProject/g, params.projectName); } const destPath = join(destDir, destName); if (entry.isDirectory()) { // Skip certain directories if (entry.name === '.git' || entry.name === 'xcuserdata') { continue; } await mkdir(destPath, { recursive: true }); await processDirectory(sourcePath, destPath, params); } else if (entry.isFile()) { // Skip certain files if (entry.name === '.DS_Store' || entry.name.endsWith('.xcuserstate')) { continue; } await processFile(sourcePath, destPath, params); } }