@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
219 lines • 12.1 kB
JavaScript
/**
* Analyze Data Tool - Individual Module
* @description Delegates to IntelligentQueryEngine for data analysis
* @since 2025-08-04
* @author Tool Modularization Team
*
* Migration Status: COMPLETED
* Original Method: OptimizelyMCPTools.analyzeData
* Complexity: HIGH
* Dependencies: logger, errorMapper, storage, various analytics imports
*/
/**
* Creates the Analyze Data tool with injected dependencies
* @param deps - Injected dependencies (storage, logger, errorMapper, etc.)
* @returns Tool definition with handler
*/
export function createAnalyzeDataTool(deps) {
return {
name: 'analyze_data',
requiresCache: true,
category: 'analytics',
description: 'Perform advanced analytics queries using Micro-Kernel (ZERO-JOIN ARCHITECTURE)',
handler: async (args) => {
try {
// Import the new Template Query Translator (DETERMINISTIC ARCHITECTURE)
const { TemplateQueryTranslator } = await import('../../analytics/TemplateQueryTranslator.js');
// Create database connection
const db = deps.storage.getDatabase();
if (!db) {
throw new Error('Database connection unavailable. The analytics engine requires database access.');
}
// Import PaginationConfigManager
const { PaginationConfigManager } = await import('../../config/PaginationConfig.js');
const paginationConfig = new PaginationConfigManager();
// Only structured_query is supported now
if (!args.structured_query) {
return {
result: 'error',
message: 'structured_query parameter is required. Natural language queries are no longer supported.',
error_type: 'InvalidQueryFormat',
help: {
example: {
structured_query: {
from: 'flags',
select: ['flag_key', 'flag_name', 'enabled']
}
},
documentation: 'Use structured_query instead of natural language query parameter'
}
};
}
// Handle recent changes queries with special processing
if (args.structured_query?.from === 'change_history_flat' && deps.handleRecentChangesQuery) {
return deps.handleRecentChangesQuery(args);
}
// ✨ TEMPLATE QUERY TRANSLATOR - Handle structured queries deterministically
deps.logger.debug({}, 'Template query: Using deterministic template-based query system');
const translator = new TemplateQueryTranslator();
try {
// Use intelligent pagination extraction
const { extractPagination } = await import('../../analytics/PaginationParameterExtractor.js');
const paginationInfo = extractPagination(args, {
page: 1,
pageSize: paginationConfig.getPageSize('analytics')
});
// Merge pagination into structured query
const queryWithPagination = {
...args.structured_query,
page: paginationInfo.page,
page_size: paginationInfo.pageSize
};
deps.logger.debug({}, `Pagination: Detected format="${paginationInfo.detectedFormat}", page=${paginationInfo.page}, pageSize=${paginationInfo.pageSize}`);
// Translate template to SQL
const sqlResult = await translator.translateToSQL(queryWithPagination, db);
deps.logger.debug({}, `Template query: Generated SQL: ${sqlResult.sql}`);
// Execute COUNT query first to get total records
const queryStartTime = Date.now();
deps.logger.debug({}, `Count query: ${sqlResult.countSql}`);
const countResult = db.prepare(sqlResult.countSql).get(...sqlResult.params);
const totalCount = countResult?.total_count || 0;
// Execute main query with LIMIT/OFFSET
deps.logger.debug({ params: sqlResult.params }, 'Before query: About to execute with params');
const data = db.prepare(sqlResult.sql).all(...sqlResult.params);
const totalExecutionTime = Date.now() - queryStartTime;
deps.logger.debug({}, `After query: Raw data type: ${typeof data}, isArray: ${Array.isArray(data)}`);
const rowCount = Array.isArray(data) ? data.length : 0;
deps.logger.debug({}, `Template query: Returned ${rowCount} rows (of ${totalCount} total) in ${totalExecutionTime}ms`);
// Debug: Log first few rows of data
if (rowCount > 0) {
deps.logger.debug({ firstRow: data[0] }, 'Template query: First row sample');
}
else {
deps.logger.debug({ data: data }, 'Template query: No rows returned');
}
// Data is already paginated by SQL LIMIT/OFFSET
// Check if user wants to bypass pagination (accept multiple formats)
const opts = args.options || {};
const bypassPagination = opts.bypass_pagination ||
opts.user_consent_required ||
opts.bypass_consent ||
opts.get_all_data ||
false;
let finalData, pagination;
if (bypassPagination) {
// User wants all data - need to re-query without LIMIT
deps.logger.debug({}, 'Bypass pagination: User requested all data, re-querying without LIMIT');
// Generate SQL without pagination
const unpaginatedQuery = {
...args.structured_query,
no_limit: true // Tell translator to skip LIMIT clause
};
// Re-run query without LIMIT
const allDataSql = await translator.translateToSQL(unpaginatedQuery, db);
const allData = db.prepare(allDataSql.sql).all(...allDataSql.params);
finalData = allData;
const actualRowCount = Array.isArray(allData) ? allData.length : 0;
deps.logger.debug({}, `Bypass pagination: Retrieved ALL ${actualRowCount} rows`);
const consentRequired = totalCount > 50;
pagination = {
consent_required: false, // Already consented
has_more: false,
bypass_applied: true,
total_count: actualRowCount,
total_pages: 1,
current_page: 1,
page_size: actualRowCount,
next_page: null,
previous_page: null
};
}
else {
// Data is already paginated by SQL - just use it
const pageSize = sqlResult.limit;
const page = sqlResult.page;
finalData = data; // Already limited by SQL
const totalPages = Math.ceil(totalCount / pageSize);
const hasMore = (page * pageSize) < totalCount;
// Only require consent when the requested page size exceeds 50
const consentRequired = pageSize > 50 && !bypassPagination;
pagination = {
consent_required: consentRequired,
has_more: hasMore,
total_count: totalCount,
current_page: page,
page_size: pageSize,
total_pages: totalPages,
next_page: hasMore ? page + 1 : null,
previous_page: page > 1 ? page - 1 : null
};
if (consentRequired && rowCount > 0) {
pagination.consent_message = `This query returns ${pageSize} rows per page (${totalCount} total). Add options.bypass_pagination=true to get all results.`;
}
}
// Generate insights if we have data
let insights = null;
if (finalData && finalData.length > 0) {
// const { generateInsights } = await import('../../analytics/InsightGenerator.js');
const generateInsights = async (data, query, options) => {
// Placeholder implementation for extracted version
return {
summary: `Analyzed ${data.length} records`,
key_findings: [],
recommendations: []
};
};
try {
insights = await generateInsights(finalData, args.structured_query, { totalCount, executionTime: totalExecutionTime });
}
catch (insightError) {
deps.logger.warn(`Failed to generate insights: ${insightError.message}`);
}
}
return {
result: 'success',
data: finalData,
pagination,
metadata: {
query_type: 'structured',
source_table: sqlResult.sourceTable,
fields: sqlResult.fields,
conditions: sqlResult.conditions?.length || 0,
execution_time_ms: totalExecutionTime,
row_count: Array.isArray(finalData) ? finalData.length : 0,
total_available: totalCount
},
insights,
_debug: {
sql: sqlResult.sql.substring(0, 200) + '...',
params_count: sqlResult.params.length
}
};
}
catch (translationError) {
deps.logger.error({
error: translationError.message,
stack: translationError.stack
}, 'Template query translation failed');
return {
result: 'error',
message: translationError.message,
error_type: 'QueryTranslationError',
details: {
structured_query: args.structured_query,
error_details: translationError.details || {}
}
};
}
}
catch (error) {
deps.logger.error({
error: error.message,
stack: error.stack
}, 'OptimizelyMCPTools.analyzeData: Failed to analyze data');
throw deps.errorMapper.toMCPError(error, 'Failed to analyze data');
}
}
};
}
//# sourceMappingURL=AnalyzeData.js.map