swift_package_run
Execute a target from a Swift Package using 'swift run' by specifying the package path, executable name, and optional arguments. Manage build configurations, timeouts, and background execution for streamlined development workflows.
Instructions
Runs an executable target from a Swift Package with swift run
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| arguments | No | Arguments to pass to the executable | |
| background | No | Run in background and return immediately (default: false) | |
| configuration | No | Build configuration: 'debug' (default) or 'release' | |
| executableName | No | Name of executable to run (defaults to package name) | |
| packagePath | Yes | Path to the Swift package root (Required) | |
| parseAsLibrary | No | Add -parse-as-library flag for @main support (default: false) | |
| timeout | No | Timeout in seconds (default: 30, max: 300) |
Implementation Reference
- src/tools/run-swift-package.ts:38-211 (handler)The primary handler function that implements the logic for 'swift_package_run'. Validates input, builds and executes 'swift run' command, handles stdout/stderr, timeouts, foreground/background execution, and tracks processes in activeProcesses map.async (params: { packagePath: string; executableName?: string; arguments?: string[]; configuration?: 'debug' | 'release'; timeout?: number; background?: boolean; parseAsLibrary?: boolean; }): Promise<ToolResponse> => { const pkgValidation = validateRequiredParam('packagePath', params.packagePath); if (!pkgValidation.isValid) return pkgValidation.errorResponse!; const resolvedPath = path.resolve(params.packagePath); const timeout = Math.min(params.timeout || 30, 300) * 1000; // Convert to ms, max 5 minutes const args: string[] = ['run', '--package-path', resolvedPath]; if (params.configuration && params.configuration.toLowerCase() === 'release') { args.push('-c', 'release'); } else if (params.configuration && params.configuration.toLowerCase() !== 'debug') { return createTextResponse("Invalid configuration. Use 'debug' or 'release'.", true); } if (params.parseAsLibrary) { args.push('-Xswiftc', '-parse-as-library'); } if (params.executableName) { args.push(params.executableName); } // Add double dash before executable arguments if (params.arguments && params.arguments.length > 0) { args.push('--'); args.push(...params.arguments); } log('info', `Running swift ${args.join(' ')}`); try { const child = spawn('swift', args, { cwd: resolvedPath, env: { ...process.env }, }); let output = ''; let errorOutput = ''; let processExited = false; let timeoutHandle: NodeJS.Timeout | null = null; // Set up output collection child.stdout?.on('data', (data) => { const text = data.toString(); output += text; }); child.stderr?.on('data', (data) => { const text = data.toString(); errorOutput += text; }); // Handle process exit child.on('exit', (_code, _signal) => { processExited = true; if (child.pid) { activeProcesses.delete(child.pid); } if (timeoutHandle) clearTimeout(timeoutHandle); }); child.on('error', (error) => { processExited = true; if (child.pid) { activeProcesses.delete(child.pid); } if (timeoutHandle) clearTimeout(timeoutHandle); errorOutput += `\nProcess error: ${error.message}`; }); // Store the process by PID if (child.pid) { activeProcesses.set(child.pid, { process: child, startedAt: new Date(), packagePath: resolvedPath, executableName: params.executableName, }); } if (params.background) { // Background mode: return immediately return { content: [ { type: 'text' as const, text: `🚀 Started executable in background (PID: ${child.pid})\n` + `💡 Process is running independently. Use swift_package_stop with PID ${child.pid} to terminate when needed.`, }, ], }; } else { // Foreground mode: wait for completion or timeout, but always complete tool call return await new Promise((resolve) => { let resolved = false; // Set up timeout - this will fire and complete the tool call timeoutHandle = setTimeout(() => { if (!resolved && !processExited) { resolved = true; // Don't kill the process - let it continue running const content: Array<{ type: 'text'; text: string }> = [ { type: 'text', text: `⏱️ Process timed out after ${timeout / 1000} seconds but continues running.`, }, { type: 'text', text: `PID: ${child.pid}`, }, { type: 'text', text: `💡 Process is still running. Use swift_package_stop with PID ${child.pid} to terminate when needed.`, }, { type: 'text', text: output || '(no output so far)' }, ]; if (errorOutput) { content.push({ type: 'text', text: `Errors:\n${errorOutput}` }); } resolve({ content }); } }, timeout); // Wait for process to exit (only if it happens before timeout) child.on('exit', (code, signal) => { if (!resolved) { resolved = true; if (timeoutHandle) clearTimeout(timeoutHandle); if (code === 0) { resolve({ content: [ { type: 'text', text: '✅ Swift executable completed successfully.' }, { type: 'text', text: '💡 Process finished cleanly. Check output for results.', }, { type: 'text', text: output || '(no output)' }, ], }); } else { const exitReason = signal ? `killed by signal ${signal}` : `exited with code ${code}`; const content: Array<{ type: 'text'; text: string }> = [ { type: 'text', text: `❌ Swift executable ${exitReason}.` }, { type: 'text', text: output || '(no output)' }, ]; if (errorOutput) { content.push({ type: 'text', text: `Errors:\n${errorOutput}` }); } resolve({ content }); } } }); }); } } catch (error) { const message = error instanceof Error ? error.message : String(error); log('error', `Swift run failed: ${message}`); // Note: No need to delete from activeProcesses since child.pid won't exist if spawn failed return createErrorResponse('Failed to execute swift run', message, 'SystemError'); } },
- src/tools/run-swift-package.ts:24-37 (schema)Input schema definition for the 'swift_package_run' tool using Zod, including parameters for package path, executable, args, config, timeout, background, and parse-as-library.packagePath: z.string().describe('Path to the Swift package root (Required)'), executableName: z .string() .optional() .describe('Name of executable to run (defaults to package name)'), arguments: z.array(z.string()).optional().describe('Arguments to pass to the executable'), configuration: swiftConfigurationSchema, timeout: z.number().optional().describe('Timeout in seconds (default: 30, max: 300)'), background: z .boolean() .optional() .describe('Run in background and return immediately (default: false)'), parseAsLibrary: parseAsLibrarySchema, },
- src/utils/register-tools.ts:171-175 (registration)Top-level conditional registration entry for the swift_package_run tool. Calls registerRunSwiftPackageTool(server) if the environment variable XCODEBUILDMCP_TOOL_SWIFT_PACKAGE_RUN is truthy.register: registerRunSwiftPackageTool, groups: [ToolGroup.SWIFT_PACKAGE_WORKFLOW], envVar: 'XCODEBUILDMCP_TOOL_SWIFT_PACKAGE_RUN', isWriteTool: true, },
- src/tools/run-swift-package.ts:19-213 (registration)Local registration of the 'swift_package_run' tool on the MCP server using the registerTool utility, specifying name, description, input schema, and handler.registerTool( server, 'swift_package_run', 'Runs an executable target from a Swift Package with swift run', { packagePath: z.string().describe('Path to the Swift package root (Required)'), executableName: z .string() .optional() .describe('Name of executable to run (defaults to package name)'), arguments: z.array(z.string()).optional().describe('Arguments to pass to the executable'), configuration: swiftConfigurationSchema, timeout: z.number().optional().describe('Timeout in seconds (default: 30, max: 300)'), background: z .boolean() .optional() .describe('Run in background and return immediately (default: false)'), parseAsLibrary: parseAsLibrarySchema, }, async (params: { packagePath: string; executableName?: string; arguments?: string[]; configuration?: 'debug' | 'release'; timeout?: number; background?: boolean; parseAsLibrary?: boolean; }): Promise<ToolResponse> => { const pkgValidation = validateRequiredParam('packagePath', params.packagePath); if (!pkgValidation.isValid) return pkgValidation.errorResponse!; const resolvedPath = path.resolve(params.packagePath); const timeout = Math.min(params.timeout || 30, 300) * 1000; // Convert to ms, max 5 minutes const args: string[] = ['run', '--package-path', resolvedPath]; if (params.configuration && params.configuration.toLowerCase() === 'release') { args.push('-c', 'release'); } else if (params.configuration && params.configuration.toLowerCase() !== 'debug') { return createTextResponse("Invalid configuration. Use 'debug' or 'release'.", true); } if (params.parseAsLibrary) { args.push('-Xswiftc', '-parse-as-library'); } if (params.executableName) { args.push(params.executableName); } // Add double dash before executable arguments if (params.arguments && params.arguments.length > 0) { args.push('--'); args.push(...params.arguments); } log('info', `Running swift ${args.join(' ')}`); try { const child = spawn('swift', args, { cwd: resolvedPath, env: { ...process.env }, }); let output = ''; let errorOutput = ''; let processExited = false; let timeoutHandle: NodeJS.Timeout | null = null; // Set up output collection child.stdout?.on('data', (data) => { const text = data.toString(); output += text; }); child.stderr?.on('data', (data) => { const text = data.toString(); errorOutput += text; }); // Handle process exit child.on('exit', (_code, _signal) => { processExited = true; if (child.pid) { activeProcesses.delete(child.pid); } if (timeoutHandle) clearTimeout(timeoutHandle); }); child.on('error', (error) => { processExited = true; if (child.pid) { activeProcesses.delete(child.pid); } if (timeoutHandle) clearTimeout(timeoutHandle); errorOutput += `\nProcess error: ${error.message}`; }); // Store the process by PID if (child.pid) { activeProcesses.set(child.pid, { process: child, startedAt: new Date(), packagePath: resolvedPath, executableName: params.executableName, }); } if (params.background) { // Background mode: return immediately return { content: [ { type: 'text' as const, text: `🚀 Started executable in background (PID: ${child.pid})\n` + `💡 Process is running independently. Use swift_package_stop with PID ${child.pid} to terminate when needed.`, }, ], }; } else { // Foreground mode: wait for completion or timeout, but always complete tool call return await new Promise((resolve) => { let resolved = false; // Set up timeout - this will fire and complete the tool call timeoutHandle = setTimeout(() => { if (!resolved && !processExited) { resolved = true; // Don't kill the process - let it continue running const content: Array<{ type: 'text'; text: string }> = [ { type: 'text', text: `⏱️ Process timed out after ${timeout / 1000} seconds but continues running.`, }, { type: 'text', text: `PID: ${child.pid}`, }, { type: 'text', text: `💡 Process is still running. Use swift_package_stop with PID ${child.pid} to terminate when needed.`, }, { type: 'text', text: output || '(no output so far)' }, ]; if (errorOutput) { content.push({ type: 'text', text: `Errors:\n${errorOutput}` }); } resolve({ content }); } }, timeout); // Wait for process to exit (only if it happens before timeout) child.on('exit', (code, signal) => { if (!resolved) { resolved = true; if (timeoutHandle) clearTimeout(timeoutHandle); if (code === 0) { resolve({ content: [ { type: 'text', text: '✅ Swift executable completed successfully.' }, { type: 'text', text: '💡 Process finished cleanly. Check output for results.', }, { type: 'text', text: output || '(no output)' }, ], }); } else { const exitReason = signal ? `killed by signal ${signal}` : `exited with code ${code}`; const content: Array<{ type: 'text'; text: string }> = [ { type: 'text', text: `❌ Swift executable ${exitReason}.` }, { type: 'text', text: output || '(no output)' }, ]; if (errorOutput) { content.push({ type: 'text', text: `Errors:\n${errorOutput}` }); } resolve({ content }); } } }); }); } } catch (error) { const message = error instanceof Error ? error.message : String(error); log('error', `Swift run failed: ${message}`); // Note: No need to delete from activeProcesses since child.pid won't exist if spawn failed return createErrorResponse('Failed to execute swift run', message, 'SystemError'); } }, ); }
- src/tools/run-swift-package.ts:12-16 (helper)Global Map used by the handler and related tools (swift_package_list, swift_package_stop) to track and manage running Swift package processes by PID.// Store active processes so we can manage them - keyed by PID for uniqueness const activeProcesses = new Map< number, { process: ChildProcess; startedAt: Date; packagePath: string; executableName?: string } >();