Skip to main content
Glama

xcresult_export_attachment

Export specific attachments from Xcode test results files, converting App UI hierarchies to JSON format for analysis.

Instructions

Export a specific attachment by index - can convert App UI hierarchy attachments to JSON

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
xcresult_pathYesAbsolute path to the .xcresult file
test_idYesTest ID or index number that contains the attachment
attachment_indexYesIndex number of the attachment to export (1-based, from xcresult-list-attachments)
convert_to_jsonNoIf true and attachment is an App UI hierarchy, convert to JSON format

Implementation Reference

  • Main handler implementation: Exports specific test attachments from XCResult files using xcresulttool. Handles validation, attachment extraction, type detection, special UI hierarchy processing, and JSON conversion.
    /**
     * Export a specific attachment by index
     */
    public static async xcresultExportAttachment(
      xcresultPath: string,
      testId: string,
      attachmentIndex: number,
      convertToJson: boolean = false
    ): Promise<McpResult> {
      // Validate xcresult path
      if (!existsSync(xcresultPath)) {
        throw new McpError(
          ErrorCode.InvalidParams,
          `XCResult file not found: ${xcresultPath}`
        );
      }
    
      if (!xcresultPath.endsWith('.xcresult')) {
        throw new McpError(
          ErrorCode.InvalidParams,
          `Path must be an .xcresult file: ${xcresultPath}`
        );
      }
    
      // Check if xcresult is readable
      if (!XCResultParser.isXCResultReadable(xcresultPath)) {
        throw new McpError(
          ErrorCode.InternalError,
          `XCResult file is not readable or incomplete: ${xcresultPath}`
        );
      }
    
      if (!testId || testId.trim() === '') {
        throw new McpError(
          ErrorCode.InvalidParams,
          'Test ID or index is required'
        );
      }
    
      if (attachmentIndex < 1) {
        throw new McpError(
          ErrorCode.InvalidParams,
          'Attachment index must be 1 or greater'
        );
      }
    
      try {
        const parser = new XCResultParser(xcresultPath);
        
        // First find the test node to get the actual test identifier
        const testNode = await parser.findTestNode(testId);
        if (!testNode) {
          throw new McpError(
            ErrorCode.InvalidParams,
            `Test '${testId}' not found. Run xcresult_browse "${xcresultPath}" to see all available tests`
          );
        }
    
        if (!testNode.nodeIdentifier) {
          throw new McpError(
            ErrorCode.InvalidParams,
            `Test '${testId}' does not have a valid identifier for attachment retrieval`
          );
        }
    
        // Get test attachments
        const attachments = await parser.getTestAttachments(testNode.nodeIdentifier);
        
        if (attachments.length === 0) {
          throw new McpError(
            ErrorCode.InvalidParams,
            `No attachments found for test '${testNode.name}'.`
          );
        }
    
        if (attachmentIndex > attachments.length) {
          throw new McpError(
            ErrorCode.InvalidParams,
            `Invalid attachment index ${attachmentIndex}. Test has ${attachments.length} attachments.`
          );
        }
    
        const attachment = attachments[attachmentIndex - 1];
        if (!attachment) {
          throw new McpError(
            ErrorCode.InternalError,
            `Attachment at index ${attachmentIndex} not found`
          );
        }
    
        const attachmentId = attachment.payloadId || attachment.payload_uuid || attachment.payloadUUID;
        if (!attachmentId) {
          throw new McpError(
            ErrorCode.InternalError,
            'Attachment does not have a valid ID for export'
          );
        }
    
        const filename = attachment.filename || attachment.name || `attachment_${attachmentIndex}`;
        
        // Determine type from identifier or filename first
        let type = attachment.uniform_type_identifier || attachment.uniformTypeIdentifier || '';
        if (!type || type === 'unknown') {
          // Infer type from filename extension or special patterns
          const ext = filename.toLowerCase().split('.').pop();
          if (ext === 'jpeg' || ext === 'jpg') type = 'public.jpeg';
          else if (ext === 'png') type = 'public.png';
          else if (ext === 'mp4') type = 'public.mpeg-4';
          else if (ext === 'mov') type = 'com.apple.quicktime-movie';
          else if (ext === 'txt') type = 'public.plain-text';
          else if (filename.toLowerCase().includes('app ui hierarchy')) type = 'ui-hierarchy';
          else if (filename.toLowerCase().includes('ui snapshot')) type = 'ui-snapshot';
          else if (filename.toLowerCase().includes('synthesized event')) type = 'synthesized-event';
          else type = 'unknown';
        }
    
        const exportedPath = await parser.exportAttachment(attachmentId, filename);
        
        // Handle UI hierarchy files specially  
        if (type === 'ui-hierarchy') {
          if (convertToJson) {
            const hierarchyJson = await this.convertUIHierarchyToJSON(exportedPath);
            return { 
              content: [{ 
                type: 'text', 
                text: JSON.stringify(hierarchyJson)
              }] 
            };
          }
          
          // Return the raw UI hierarchy content (it's already AI-friendly)
          const { readFile } = await import('fs/promises');
          const hierarchyContent = await readFile(exportedPath, 'utf-8');
          return { 
            content: [{ 
              type: 'text', 
              text: `UI Hierarchy for: ${filename}\nType: ${type}\n\n${hierarchyContent}`
            }] 
          };
        }
    
        return { 
          content: [{ 
            type: 'text', 
            text: `Attachment exported to: ${exportedPath}\nFilename: ${filename}\nType: ${type}`
          }] 
        };
    
      } catch (error) {
        if (error instanceof McpError) {
          throw error;
        }
    
        const errorMessage = error instanceof Error ? error.message : String(error);
        
        if (errorMessage.includes('xcresulttool')) {
          throw new McpError(
            ErrorCode.InternalError,
            `XCResult parsing failed. Make sure Xcode Command Line Tools are installed: ${errorMessage}`
          );
        }
        
        throw new McpError(
          ErrorCode.InternalError,
          `Failed to export attachment: ${errorMessage}`
        );
      }
    }
  • Input schema definition for the xcresult_export_attachment tool, defining parameters and validation rules
      name: 'xcresult_export_attachment',
      description: 'Export a specific attachment by index - can convert App UI hierarchy attachments to JSON',
      inputSchema: {
        type: 'object',
        properties: {
          xcresult_path: {
            type: 'string',
            description: 'Absolute path to the .xcresult file',
          },
          test_id: {
            type: 'string',
            description: 'Test ID or index number that contains the attachment',
          },
          attachment_index: {
            type: 'number',
            description: 'Index number of the attachment to export (1-based, from xcresult-list-attachments)',
          },
          convert_to_json: {
            type: 'boolean',
            description: 'If true and attachment is an App UI hierarchy, convert to JSON format',
          },
        },
        required: ['xcresult_path', 'test_id', 'attachment_index'],
      },
    },
  • MCP server registration: Dispatches xcresult_export_attachment tool calls to XCResultTools.xcresultExportAttachment handler in CallToolRequestSchema
    case 'xcresult_export_attachment':
      if (!args.xcresult_path) {
        throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`);
      }
      if (!args.test_id) {
        throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: test_id`);
      }
      if (args.attachment_index === undefined) {
        throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: attachment_index`);
      }
      return await XCResultTools.xcresultExportAttachment(
        args.xcresult_path as string,
        args.test_id as string,
        args.attachment_index as number,
        args.convert_to_json as boolean | undefined
      );
  • Fallback schema definition used by MCP library getTools() when CLI is unavailable
    name: 'xcresult_export_attachment',
    description: 'Export a specific attachment by index - can convert App UI hierarchy attachments to JSON',
    inputSchema: {
      type: 'object',
      properties: {
        xcresult_path: {
          type: 'string',
          description: 'Absolute path to the .xcresult file',
        },
        test_id: {
          type: 'string',
          description: 'Test ID or index number that contains the attachment',
        },
        attachment_index: {
          type: 'number',
          description: 'Index number of the attachment to export (1-based, from xcresult-list-attachments)',
        },
        convert_to_json: {
          type: 'boolean',
          description: 'If true and attachment is an App UI hierarchy, convert to JSON format',
        },
      },
      required: ['xcresult_path', 'test_id', 'attachment_index'],
    },

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/lapfelix/XcodeMCP'

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