@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
283 lines • 15.3 kB
JavaScript
/**
* 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