UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

283 lines 15.3 kB
/** * Export Data Tool - Individual Module * @description Exports project data in various formats (JSON, CSV, etc.) * @since 2025-08-04 * @author Tool Modularization Team * * Migration Status: COMPLETED * Original Method: OptimizelyMCPTools.exportData * Complexity: MEDIUM * Dependencies: storage.query, logger, errorMapper, analyzeData, listEntities, ExportManager */ /** * Creates the Export Data tool with injected dependencies * @param deps - Injected dependencies (storage, logger, errorMapper, etc.) * @returns Tool definition with handler */ export function createExportDataTool(deps) { return { name: 'export_data', requiresCache: true, category: 'operations', description: 'Exports project data in various formats (JSON, CSV, YAML)', handler: async (args) => { try { deps.logger.debug({ format: args.format, hasQuery: !!args.query, hasEntityType: !!args.entity_type, projectId: args.project_id, customFields: args.custom_fields?.length || 0 }, 'OptimizelyMCPTools.exportData: Starting data export'); // Import ExportManager const { ExportManager } = await import('../../export/ExportManager.js'); // Validate export options const validation = ExportManager.validateOptions({ format: args.format, filename: args.filename, include_metadata: args.include_metadata, pretty_print: args.pretty_print, custom_fields: args.custom_fields, exclude_fields: args.exclude_fields, output_directory: args.output_directory }); if (!validation.valid) { return { result: 'error', message: `Invalid export options: ${validation.errors.join(', ')}`, error_type: 'ValidationError' }; } let data = []; let sourceQuery = ''; let paginationInfo = undefined; // Get data based on the input type if (args.query || args.structured_query) { // Check if the agent is confused - trying to use analytics query for simple entity export const queryLower = (args.query || '').toLowerCase(); const isSimpleEntityQuery = queryLower.match(/^(show\s+(me\s+)?all|list|get)\s+(flags?|experiments?|audiences?|events?|pages?|campaigns?)\s*(in|from)?\s*(this\s+)?project/i); if (isSimpleEntityQuery && !args.options?.user_consent_required) { // Guide the agent to use entity_type instead const entityMatch = queryLower.match(/(flags?|experiments?|audiences?|events?|pages?|campaigns?)/i); const suggestedEntity = entityMatch ? entityMatch[1].replace(/s$/, '') : 'flag'; return { result: 'error', message: `For simple entity exports, use entity_type instead of query. Try: {"entity_type": "${suggestedEntity}", "project_id": "${args.project_id || 'PROJECT_ID'}", "format": "${args.format}", "filename": "${args.filename || 'export'}"}`, error_type: 'ParameterGuidance', suggestion: { entity_type: suggestedEntity, project_id: args.project_id, format: args.format, filename: args.filename } }; } // Use analyze_data to get the data sourceQuery = args.query || JSON.stringify(args.structured_query); const analyticsResult = await deps.analyzeData({ query: args.query, structured_query: args.structured_query, project_id: args.project_id, options: { bypass_pagination: true, // Get all data for export limit: args.options?.limit || 10000 } }); if (analyticsResult.result === 'error') { return analyticsResult; } data = analyticsResult.data || analyticsResult.entities || []; paginationInfo = analyticsResult.pagination; } else if (args.entity_type) { // Use list_entities to get the data sourceQuery = `list_entities(${args.entity_type})`; deps.logger.debug({ entityType: args.entity_type, projectId: args.project_id, filters: { bypass_pagination: true, user_consent_required: true, simplified: false, _internal: true, limit: args.options?.limit || 10000 } }, 'exportData: About to call listEntities with these parameters'); const entitiesResult = await deps.listEntities(args.entity_type, args.project_id, { bypass_pagination: true, user_consent_required: true, simplified: false, _internal: true, limit: args.options?.limit || 10000 }).catch(async (error) => { deps.logger.error({ entityType: args.entity_type, projectId: args.project_id, errorMessage: error.message, errorStack: error.stack, errorType: error.constructor.name }, 'exportData: listEntities call failed with error'); // Check if this is a platform mismatch error if (error.message?.includes('not supported') && args.project_id) { // Try to provide helpful context about what IS supported try { const project = await deps.storage.get('SELECT name, is_flags_enabled FROM projects WHERE id = ?', [args.project_id]); if (project) { const supportedEntities = project.is_flags_enabled ? ['flag', 'environment', 'feature', 'audience', 'event', 'attribute'] : ['experiment', 'campaign', 'page', 'audience', 'event', 'attribute', 'extension']; return { result: 'error', message: `Entity type '${args.entity_type}' is not supported for ${project.is_flags_enabled ? 'Feature Experimentation' : 'Web Experimentation'} project '${project.name}'. Supported entities: ${supportedEntities.join(', ')}`, error_type: 'PlatformMismatch', project_info: { name: project.name, type: project.is_flags_enabled ? 'Feature Experimentation' : 'Web Experimentation', supported_entities: supportedEntities } }; } } catch (e) { // Fall through to original error } } throw error; }); deps.logger.debug({ entityType: args.entity_type, projectId: args.project_id, resultStructure: { result: entitiesResult.result, hasEntities: !!entitiesResult.entities, entitiesLength: entitiesResult.entities?.length, hasData: !!entitiesResult.data, dataLength: entitiesResult.data?.length, hasPagination: !!entitiesResult.pagination, keys: Object.keys(entitiesResult) } }, 'exportData: listEntities returned this result structure'); if (entitiesResult.result === 'error') { deps.logger.error({ entityType: args.entity_type, projectId: args.project_id, errorResult: entitiesResult }, 'exportData: listEntities returned error result'); return entitiesResult; } data = entitiesResult.entities || entitiesResult.data || entitiesResult[args.entity_type + 's'] || entitiesResult[args.entity_type] || []; paginationInfo = entitiesResult.pagination; deps.logger.debug({ entityType: args.entity_type, projectId: args.project_id, extractedDataLength: data.length, dataIsArray: Array.isArray(data), paginationInfo }, 'exportData: Data extracted from listEntities result'); } else { return { result: 'error', message: 'Must specify either query/structured_query or entity_type', error_type: 'InvalidParameters' }; } deps.logger.debug({ dataIsArray: Array.isArray(data), dataLength: data?.length, dataType: typeof data, firstFewItems: Array.isArray(data) ? data.slice(0, 2) : 'not array', entityType: args.entity_type, projectId: args.project_id }, 'exportData: Final data check before validation'); if (!Array.isArray(data) || data.length === 0) { deps.logger.error({ dataIsArray: Array.isArray(data), dataLength: data?.length, dataType: typeof data, data: data, entityType: args.entity_type, projectId: args.project_id }, 'exportData: No data to export - returning error'); // Provide helpful guidance based on what was requested let helpMessage = 'No data to export'; if (args.entity_type) { helpMessage = `No ${args.entity_type}s found${args.project_id ? ' in project ' + args.project_id : ''}. Verify the project ID and that the entity type exists.`; } else if (args.query) { helpMessage = 'The query returned no results. Check your query syntax or try a broader search.'; } return { result: 'error', message: helpMessage, error_type: 'NoDataError', suggestions: [ 'Verify the project_id is correct', 'Check if the entity type is supported for this project type', 'Try listing entities first to see what data is available' ] }; } // Create export manager const exportManager = new ExportManager(args.output_directory); // Prepare export options const exportOptions = { format: args.format, filename: args.filename, include_metadata: args.include_metadata ?? true, pretty_print: args.pretty_print ?? true, custom_fields: args.custom_fields, exclude_fields: args.exclude_fields, output_directory: args.output_directory }; // Prepare metadata const exportMetadata = { source_query: sourceQuery, pagination_info: paginationInfo }; // Export the data const exportResult = await exportManager.export(data, exportOptions, exportMetadata); if (!exportResult.success) { return { result: 'error', message: exportResult.error || 'Export failed', error_type: 'ExportError' }; } // Get format suggestions for next time const formatSuggestions = ExportManager.getFormatSuggestions(data, data.length); return { result: 'success', export_result: { file_path: exportResult.file_path, total_records: exportResult.total_records, format: exportResult.format, file_size_bytes: exportResult.file_size_bytes, export_time_ms: exportResult.export_time_ms }, recommendations: { format_used: args.format, alternative_formats: formatSuggestions, next_export_suggestions: formatSuggestions.alternatives }, metadata: { source_query: sourceQuery, data_source: args.query ? 'analytics_query' : 'entity_list', total_exported: data.length, fields_exported: args.custom_fields || 'all', excluded_fields: args.exclude_fields || 'none' } }; } catch (error) { deps.logger.error({ error: error.message, stack: error.stack, args }, 'OptimizelyMCPTools.exportData: Failed to export data'); throw deps.errorMapper.toMCPError(error, 'Failed to export data'); } } }; } //# sourceMappingURL=ExportData.js.map