mcp-xcode
Version:
MCP server that wraps Xcode command-line tools for iOS/macOS development workflows
132 lines • 5.75 kB
JavaScript
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 } from '../../state/project-cache.js';
import { simulatorCache } from '../../state/simulator-cache.js';
export async function xcodebuildBuildTool(args) {
const { projectPath, scheme, configuration = 'Debug', destination, sdk, derivedDataPath, } = args;
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));
// Build final configuration
const finalConfig = {
scheme: scheme || preferredConfig?.scheme || scheme,
configuration: configuration || preferredConfig?.configuration || 'Debug',
destination: smartDestination,
sdk: sdk || preferredConfig?.sdk,
derivedDataPath: derivedDataPath || preferredConfig?.derivedDataPath,
};
// Build command
const command = buildXcodebuildCommand('build', projectPath, finalConfig);
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,
summary,
smartDefaultsUsed: {
destination: !destination && smartDestination !== destination,
configuration: !args.configuration && finalConfig.configuration !== 'Debug',
},
},
});
// Create concise response
const responseData = {
buildId: cacheId,
success: summary.success,
summary: {
...summary,
scheme: finalConfig.scheme,
configuration: finalConfig.configuration,
destination: finalConfig.destination,
duration,
},
nextSteps: summary.success
? [
`✅ Build completed successfully in ${duration}ms`,
`Use 'xcodebuild-get-details' with buildId '${cacheId}' for full logs`,
]
: [
`❌ 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`,
],
availableDetails: ['full-log', 'errors-only', 'warnings-only', 'summary', 'command'],
};
const responseText = JSON.stringify(responseData, null, 2);
return {
content: [
{
type: 'text',
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) {
// If preferred config has a destination, use it
if (preferredConfig?.destination) {
return preferredConfig.destination;
}
// Try to get a smart simulator destination
try {
const preferredSim = await simulatorCache.getPreferredSimulator();
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;
}
//# sourceMappingURL=build.js.map