Skip to main content
Glama

swift_package_run

Execute an executable target from a Swift Package by specifying the package path, executable name, and optional arguments. Supports debug or release configurations, background execution, and custom timeouts.

Instructions

Runs an executable target from a Swift Package with swift run

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
argumentsNoArguments to pass to the executable
backgroundNoRun in background and return immediately (default: false)
configurationNoBuild configuration: 'debug' (default) or 'release'
executableNameNoName of executable to run (defaults to package name)
packagePathYesPath to the Swift package root (Required)
parseAsLibraryNoAdd -parse-as-library flag for @main support (default: false)
timeoutNoTimeout in seconds (default: 30, max: 300)

Implementation Reference

  • The main handler function `swift_package_runLogic` that executes the `swift run` command, supports foreground and background modes, handles timeouts, process tracking, and error cases.
    export async function swift_package_runLogic(
      params: SwiftPackageRunParams,
      executor: CommandExecutor,
    ): Promise<ToolResponse> {
      const resolvedPath = path.resolve(params.packagePath);
      const timeout = Math.min(params.timeout ?? 30, 300) * 1000; // Convert to ms, max 5 minutes
    
      // Detect test environment to prevent real spawn calls during testing
      const isTestEnvironment = process.env.VITEST === 'true' || process.env.NODE_ENV === 'test';
    
      const swiftArgs = ['run', '--package-path', resolvedPath];
    
      if (params.configuration && params.configuration.toLowerCase() === 'release') {
        swiftArgs.push('-c', 'release');
      } else if (params.configuration && params.configuration.toLowerCase() !== 'debug') {
        return createTextResponse("Invalid configuration. Use 'debug' or 'release'.", true);
      }
    
      if (params.parseAsLibrary) {
        swiftArgs.push('-Xswiftc', '-parse-as-library');
      }
    
      if (params.executableName) {
        swiftArgs.push(params.executableName);
      }
    
      // Add double dash before executable arguments
      if (params.arguments && params.arguments.length > 0) {
        swiftArgs.push('--');
        swiftArgs.push(...params.arguments);
      }
    
      log('info', `Running swift ${swiftArgs.join(' ')}`);
    
      try {
        if (params.background) {
          // Background mode: Use CommandExecutor but don't wait for completion
          if (isTestEnvironment) {
            // In test environment, return mock response without real process
            const mockPid = 12345;
            return {
              content: [
                createTextContent(
                  `πŸš€ Started executable in background (PID: ${mockPid})\n` +
                    `πŸ’‘ Process is running independently. Use swift_package_stop with PID ${mockPid} to terminate when needed.`,
                ),
              ],
            };
          } else {
            // Production: use CommandExecutor to start the process
            const command = ['swift', ...swiftArgs];
            // Filter out undefined values from process.env
            const cleanEnv = Object.fromEntries(
              Object.entries(process.env).filter(([, value]) => value !== undefined),
            ) as Record<string, string>;
            const result = await executor(
              command,
              'Swift Package Run (Background)',
              true,
              cleanEnv,
              true,
            );
    
            // Store the process in active processes system if available
            if (result.process?.pid) {
              addProcess(result.process.pid, {
                process: {
                  kill: (signal?: string) => {
                    // Adapt string signal to NodeJS.Signals
                    if (result.process) {
                      result.process.kill(signal as NodeJS.Signals);
                    }
                  },
                  on: (event: string, callback: () => void) => {
                    if (result.process) {
                      result.process.on(event, callback);
                    }
                  },
                  pid: result.process.pid,
                },
                startedAt: new Date(),
              });
    
              return {
                content: [
                  createTextContent(
                    `πŸš€ Started executable in background (PID: ${result.process.pid})\n` +
                      `πŸ’‘ Process is running independently. Use swift_package_stop with PID ${result.process.pid} to terminate when needed.`,
                  ),
                ],
              };
            } else {
              return {
                content: [
                  createTextContent(
                    `πŸš€ Started executable in background\n` +
                      `πŸ’‘ Process is running independently. PID not available for this execution.`,
                  ),
                ],
              };
            }
          }
        } else {
          // Foreground mode: use CommandExecutor but handle long-running processes
          const command = ['swift', ...swiftArgs];
    
          // Create a promise that will either complete with the command result or timeout
          const commandPromise = executor(command, 'Swift Package Run', true, undefined);
    
          const timeoutPromise = new Promise<{
            success: boolean;
            output: string;
            error: string;
            timedOut: boolean;
          }>((resolve) => {
            setTimeout(() => {
              resolve({
                success: false,
                output: '',
                error: `Process timed out after ${timeout / 1000} seconds`,
                timedOut: true,
              });
            }, timeout);
          });
    
          // Race between command completion and timeout
          const result = await Promise.race([commandPromise, timeoutPromise]);
    
          if ('timedOut' in result && result.timedOut) {
            // For timeout case, the process may still be running - provide timeout response
            if (isTestEnvironment) {
              // In test environment, return mock response
              const mockPid = 12345;
              return {
                content: [
                  createTextContent(
                    `⏱️ Process timed out after ${timeout / 1000} seconds but may continue running.`,
                  ),
                  createTextContent(`PID: ${mockPid} (mock)`),
                  createTextContent(
                    `πŸ’‘ Process may still be running. Use swift_package_stop with PID ${mockPid} to terminate when needed.`,
                  ),
                  createTextContent(result.output || '(no output so far)'),
                ],
              };
            } else {
              // Production: timeout occurred, but we don't start a new process
              return {
                content: [
                  createTextContent(`⏱️ Process timed out after ${timeout / 1000} seconds.`),
                  createTextContent(
                    `πŸ’‘ Process execution exceeded the timeout limit. Consider using background mode for long-running executables.`,
                  ),
                  createTextContent(result.output || '(no output so far)'),
                ],
              };
            }
          }
    
          if (result.success) {
            return {
              content: [
                createTextContent('βœ… Swift executable completed successfully.'),
                createTextContent('πŸ’‘ Process finished cleanly. Check output for results.'),
                createTextContent(result.output || '(no output)'),
              ],
            };
          } else {
            const content = [
              createTextContent('❌ Swift executable failed.'),
              createTextContent(result.output || '(no output)'),
            ];
            if (result.error) {
              content.push(createTextContent(`Errors:\n${result.error}`));
            }
            return { content };
          }
        }
      } catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        log('error', `Swift run failed: ${message}`);
        return createErrorResponse('Failed to execute swift run', message);
      }
    }
  • Zod schema defining input parameters for the `swift_package_run` tool, including package path, executable, arguments, config, timeout, background mode, and parse-as-library flag.
    const swiftPackageRunSchema = z.object({
      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: z
        .enum(['debug', 'release'])
        .optional()
        .describe("Build configuration: 'debug' (default) or 'release'"),
      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: z
        .boolean()
        .optional()
        .describe('Add -parse-as-library flag for @main support (default: false)'),
    });
  • Module export default object registering the tool with name, description, schema, and typed handler created via `createTypedTool`.
    export default {
      name: 'swift_package_run',
      description: 'Runs an executable target from a Swift Package with swift run',
      schema: swiftPackageRunSchema.shape, // MCP SDK compatibility
      handler: createTypedTool(
        swiftPackageRunSchema,
        swift_package_runLogic,
        getDefaultCommandExecutor,
      ),
    };
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/cameroncooke/XcodeBuildMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server