Skip to main content
Glama

ios_take_screenshot

Capture screenshots from iOS simulators by specifying device UDID and save path for development testing and debugging workflows.

Instructions

Take a screenshot of an iOS simulator

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
udidYesSimulator UDID
pathYesAbsolute path to save screenshot

Implementation Reference

  • Handler function that validates inputs, performs security and path checks, executes xcrun simctl to take screenshot, verifies file creation, and returns structured result.
    handler: async (args: any) => {
      checkMacOS();
    
      const validation = IosSimulatorScreenshotSchema.safeParse(args);
      if (!validation.success) {
        throw new Error(`Invalid request: ${validation.error.message}`);
      }
    
      const { udid, path: screenshotPath } = validation.data;
    
      // Validate UDID format
      if (!validateUDID(udid)) {
        throw new Error(`Invalid simulator UDID format. UDID must be in format XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX: ${udid}`);
      }
    
      // Validate screenshot path - must be absolute and not contain dangerous patterns
      if (!path.isAbsolute(screenshotPath)) {
        throw new Error(`Screenshot path must be absolute. Path must start with /: ${screenshotPath}`);
      }
    
      // Security check - prevent path traversal and access to sensitive directories
      const normalizedPath = path.normalize(screenshotPath);
      const dangerousPaths = ['/etc', '/usr', '/System', '/private', '/var'];
      if (dangerousPaths.some(dangerous => normalizedPath.startsWith(dangerous))) {
        throw new Error(`Access to this path is not allowed. Path access denied for security reasons: ${normalizedPath}`);
      }
    
      // Ensure file has image extension
      const allowedExtensions = ['.png', '.jpg', '.jpeg'];
      const extension = path.extname(screenshotPath).toLowerCase();
      if (!allowedExtensions.includes(extension)) {
        throw new Error(`Screenshot file must have image extension. Allowed extensions: ${allowedExtensions.join(', ')}. Got: ${extension}`);
      }
    
      // Create directory if it doesn't exist
      const directory = path.dirname(screenshotPath);
      try {
        await fs.mkdir(directory, { recursive: true });
      } catch (mkdirError) {
        throw new Error(`Failed to create screenshot directory: ${mkdirError}`);
      }
    
      const result = await processExecutor.execute('xcrun', ['simctl', 'io', udid, 'screenshot', screenshotPath]);
    
      if (result.exitCode !== 0) {
        throw new Error(`Failed to capture screenshot: ${result.stderr}`);
      }
    
      // Verify screenshot was created
      let fileStats;
      try {
        fileStats = await fs.stat(screenshotPath);
      } catch {
        throw new Error(`Screenshot file was not created. Expected file: ${screenshotPath}`);
      }
    
      return {
        success: true,
        data: {
          udid,
          screenshotPath,
          fileSize: fileStats.size,
          status: 'captured',
          message: 'Screenshot captured successfully',
          output: result.stdout,
        },
      };
    }
  • Zod schema used for input validation in the handler, defining udid and path parameters.
     * Zod validation schema for ios_simulator_screenshot tool.
     *
     * @type {z.ZodObject}
     * @property {string} udid - Simulator UDID
     * @property {string} path - Output file path for screenshot
     */
    const IosSimulatorScreenshotSchema = z.object({
      udid: z.string().min(1),
      path: z.string().min(1),
    });
  • Registration of the tool in the createIOSTools function, including name, description, inputSchema (JSON schema for MCP), and reference to handler.
    tools.set('ios_take_screenshot', {
      name: 'ios_take_screenshot',
      description: 'Take a screenshot of an iOS simulator',
      inputSchema: {
        type: 'object',
        properties: {
          udid: { type: 'string', minLength: 1, description: 'Simulator UDID' },
          path: { type: 'string', minLength: 1, description: 'Absolute path to save screenshot' }
        },
        required: ['udid', 'path']
      },
      handler: async (args: any) => {
        checkMacOS();
    
        const validation = IosSimulatorScreenshotSchema.safeParse(args);
        if (!validation.success) {
          throw new Error(`Invalid request: ${validation.error.message}`);
        }
    
        const { udid, path: screenshotPath } = validation.data;
    
        // Validate UDID format
        if (!validateUDID(udid)) {
          throw new Error(`Invalid simulator UDID format. UDID must be in format XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX: ${udid}`);
        }
    
        // Validate screenshot path - must be absolute and not contain dangerous patterns
        if (!path.isAbsolute(screenshotPath)) {
          throw new Error(`Screenshot path must be absolute. Path must start with /: ${screenshotPath}`);
        }
    
        // Security check - prevent path traversal and access to sensitive directories
        const normalizedPath = path.normalize(screenshotPath);
        const dangerousPaths = ['/etc', '/usr', '/System', '/private', '/var'];
        if (dangerousPaths.some(dangerous => normalizedPath.startsWith(dangerous))) {
          throw new Error(`Access to this path is not allowed. Path access denied for security reasons: ${normalizedPath}`);
        }
    
        // Ensure file has image extension
        const allowedExtensions = ['.png', '.jpg', '.jpeg'];
        const extension = path.extname(screenshotPath).toLowerCase();
        if (!allowedExtensions.includes(extension)) {
          throw new Error(`Screenshot file must have image extension. Allowed extensions: ${allowedExtensions.join(', ')}. Got: ${extension}`);
        }
    
        // Create directory if it doesn't exist
        const directory = path.dirname(screenshotPath);
        try {
          await fs.mkdir(directory, { recursive: true });
        } catch (mkdirError) {
          throw new Error(`Failed to create screenshot directory: ${mkdirError}`);
        }
    
        const result = await processExecutor.execute('xcrun', ['simctl', 'io', udid, 'screenshot', screenshotPath]);
    
        if (result.exitCode !== 0) {
          throw new Error(`Failed to capture screenshot: ${result.stderr}`);
        }
    
        // Verify screenshot was created
        let fileStats;
        try {
          fileStats = await fs.stat(screenshotPath);
        } catch {
          throw new Error(`Screenshot file was not created. Expected file: ${screenshotPath}`);
        }
    
        return {
          success: true,
          data: {
            udid,
            screenshotPath,
            fileSize: fileStats.size,
            status: 'captured',
            message: 'Screenshot captured successfully',
            output: result.stdout,
          },
        };
      }
    });
  • Helper function to validate UDID format, used in the handler.
    const validateUDID = (udid: string): boolean => {
      const uuidPattern = /^[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}$/i;
      return uuidPattern.test(udid);
    };
  • Helper function to ensure running on macOS, called at start of handler.
    const checkMacOS = (): void => {
      if (process.platform !== 'darwin') {
        throw new Error(`iOS development tools only work on macOS. Current platform: ${process.platform}`);
      }
    };

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/cristianoaredes/mcp-mobile-server'

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