UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

493 lines 20.6 kB
/** * Universal Response Wrapper for Optimizely MCP Tools * @description Provides consistent response formatting with success indicators, * metadata, and performance tracking across all MCP tools while preserving * existing business logic response structures. * * DESIGN PRINCIPLES: * - Additive, not transformative: Wraps existing responses without breaking them * - Universal compatibility: Works with all 26 tool response patterns * - Neural-first: Places critical metadata in high-attention zones * - Backward compatible: Preserves existing response structures * - Progressive enhancement: Layers additional metadata as needed * * @pattern Decorator Pattern - Enhances responses without modifying core logic * @version 1.0.0 */ export const SuccessStopType = { COMPREHENSIVE_DATA_PROVIDED: 'COMPREHENSIVE_DATA_PROVIDED', MULTI_PROJECT_COMPLETE_DATASET: 'MULTI_PROJECT_COMPLETE_DATASET', TEMPLATE_GUIDANCE_PROVIDED: 'TEMPLATE_GUIDANCE_PROVIDED', PAGINATION_COMPLETE: 'PAGINATION_COMPLETE', THRESHOLD_EXCEEDED: 'THRESHOLD_EXCEEDED' }; export class ResponseWrapper { /** * Detects if a success hard stop should be triggered * @param data - The response data * @param options - Wrapping configuration options * @returns Success stop configuration or null */ static detectSuccessStop(data, options) { if (!options.enable_success_stops) { return null; } const { tool_name, project_context } = options; // Large dataset criteria (50+ results) const resultCount = this.extractResultCount(data); if (resultCount && resultCount >= 50) { return { type: SuccessStopType.COMPREHENSIVE_DATA_PROVIDED, reason: 'LARGE_RESULT_SET_PROVIDED', message: `Complete ${tool_name} results retrieved (${resultCount} items). You have all available data. Process these results instead of making additional queries.` }; } // Multi-project complete query if (project_context?.is_multi_project && (project_context?.projects_queried || 0) > 1) { return { type: SuccessStopType.MULTI_PROJECT_COMPLETE_DATASET, reason: 'MULTI_PROJECT_COMPLETE_DATASET', message: `Complete multi-project query (${project_context.projects_queried} projects). You have comprehensive data across all configured projects. Process these results instead of making additional queries.` }; } // Template mode guidance provided if (data && typeof data === 'object' && data.template_mode?.level === 'warning') { return { type: SuccessStopType.TEMPLATE_GUIDANCE_PROVIDED, reason: 'TEMPLATE_MODE_GUIDANCE_READY', message: `Template mode guidance provided. Follow the template instructions instead of making additional queries.` }; } // Tool-specific thresholds switch (tool_name) { case 'list_experiments': if ((resultCount && resultCount >= 25) || project_context?.query_mode === 'all_projects') { return { type: SuccessStopType.COMPREHENSIVE_DATA_PROVIDED, reason: 'MODE_all_projects_COMPLETE', message: `STOP querying list_experiments; mode=all_projects is complete. You have comprehensive experiment data. Analyze these results instead of making additional queries.` }; } break; case 'list_flags': if ((resultCount && resultCount >= 75) || project_context?.query_mode === 'all_projects') { return { type: SuccessStopType.COMPREHENSIVE_DATA_PROVIDED, reason: 'FLAG_DATASET_COMPLETE', message: `Complete flag dataset retrieved. You have comprehensive flag data. Analyze these results instead of making additional queries.` }; } break; case 'search_all': if (resultCount && resultCount >= 30) { return { type: SuccessStopType.COMPREHENSIVE_DATA_PROVIDED, reason: 'SEARCH_RESULTS_COMPREHENSIVE', message: `Comprehensive search results retrieved. You have extensive search data. Analyze these results instead of making additional queries.` }; } break; } return null; } /** * Creates a success hard stop response with business data at root level * @param successStop - Success stop configuration * @param data - Original data * @param toolName - Tool name * @returns Success hard stop response with data spread at root level */ static createSuccessHardStop(successStop, data, toolName) { const baseResponse = { // ===== HIGH-ATTENTION ZONE (First 200 chars) ===== hard_stop: 'STOP_YOU_HAVE_ALL_DATA', required_action: 'PROCESS_DATA', forbidden_actions: ['retry_query', 'make_additional_calls', 'request_more_details'], critical_rule: 'STOP querying. Process the comprehensive data provided.', // ===== OPERATION METADATA ===== success: { code: 200, type: successStop.type, message: successStop.message, reason: successStop.reason, comprehensive: true }, immediate_guidance: { critical_rule: 'STOP querying list_experiments; mode=all_projects is complete.', detected_situation: `${successStop.reason} - all available data retrieved`, next_step: 'Analyze results and present organized information to user' } }; // Spread business data at root level alongside hard stop fields if (data && typeof data === 'object' && !Array.isArray(data)) { // Build result with specific field ordering const result = {}; const businessData = data; // 1. First, add all hard stop fields Object.assign(result, baseResponse); // 2. Then add business metadata fields (before experiments array) if ('total' in businessData) result.total = businessData.total; if ('summary' in businessData) result.summary = businessData.summary; if ('filters_applied' in businessData) result.filters_applied = businessData.filters_applied; if ('_metadata' in businessData) result._metadata = businessData._metadata; // 3. Then add the main data array (experiments, flags, etc.) if ('experiments' in businessData) result.experiments = businessData.experiments; if ('flags' in businessData) result.flags = businessData.flags; if ('audiences' in businessData) result.audiences = businessData.audiences; if ('events' in businessData) result.events = businessData.events; // 4. Finally, add any remaining fields Object.keys(businessData).forEach(key => { if (!(key in result)) { result[key] = businessData[key]; } }); return result; } else { // For non-object data, still put it in data field return { ...baseResponse, data }; } } /** * Wraps any tool response with consistent success indicators and metadata * @param data - The original business logic response * @param options - Wrapping configuration options * @returns Enhanced response with metadata at top, data preserved */ static wrap(data, options) { const endTime = Date.now(); const startTime = options.start_time || endTime; // Check for success hard stop conditions first const successStop = this.detectSuccessStop(data, options); if (successStop) { return this.createSuccessHardStop(successStop, data, options.tool_name); } // Determine result count from common patterns const resultCount = this.extractResultCount(data); // Determine query mode for MODE pattern tools const queryMode = this.determineQueryMode(data, options.project_context); // Check for hard stop requirements const hasHardStop = this.checkForHardStop(data); // Determine status const status = this.determineStatus(data, hasHardStop); const wrapped = { // High-attention metadata (first 200 chars) status, tool: options.tool_name, ...(resultCount !== undefined && { result_count: resultCount }), // Mode-specific metadata ...(queryMode && { query_mode: queryMode }), ...(options.project_context?.projects_queried && { projects_queried: options.project_context.projects_queried }), ...(options.operation_type && { operation_type: options.operation_type }), // Performance metrics timing: { duration_ms: endTime - startTime, started_at: new Date(startTime).toISOString(), completed_at: new Date(endTime).toISOString() }, // Preserved business data data, // Wrapper metadata _wrapper_metadata: { wrapper_version: '1.0.0', enhanced_response: true, original_structure_preserved: true, applied_patterns: this.getAppliedPatterns(options) } }; return wrapped; } /** * Enhanced wrapper for tools using the MODE pattern * Provides neural-first metadata for all_projects vs single_project operations */ static wrapWithModePattern(data, toolName, projectContext, startTime) { return this.wrap(data, { tool_name: toolName, operation_type: 'list', start_time: startTime, project_context: projectContext, enhance_for_mode_pattern: true, enable_success_stops: true // Enable success stops for MODE pattern tools }); } /** * Enhanced wrapper with explicit success stop enablement * Use this when you specifically want to prevent repetitive tool calls */ static wrapWithSuccessStops(data, toolName, projectContext, startTime) { return this.wrap(data, { tool_name: toolName, operation_type: 'list', start_time: startTime, project_context: projectContext, enable_success_stops: true }); } /** * Lightweight wrapper for simple success/failure indication * Minimal overhead for system tools that don't need full enhancement */ static wrapSimple(data, toolName, operationType) { return { status: 'success', tool: toolName, ...(operationType && { operation_type: operationType }), timing: { duration_ms: 0, started_at: new Date().toISOString(), completed_at: new Date().toISOString() }, data }; } /** * Wraps response with AI metadata for guiding AI agents * Preserves root structure for backward compatibility * @param data - The original response * @param options - Wrapper options including AI metadata * @returns Response with _ai_meta added */ static wrapWithAIMeta(data, options) { const endTime = Date.now(); const startTime = options.start_time || endTime; // Build comprehensive AI metadata const aiMeta = { // Core metadata - always included cursor_notice: "Ignore Cursor's conversation history panel", tool_status: this.determineToolStatus(data), source: "optimizely-mcp-server", timestamp: new Date().toISOString(), // Merge in any custom AI metadata ...options.aiMeta }; // Add automatic guidance based on data patterns const autoGuidance = this.generateAutoGuidance(data, options); if (autoGuidance) { Object.assign(aiMeta, autoGuidance); } // For backward compatibility, preserve root structure if (options.preserve_root_structure !== false) { // Spread original data at root level, add _ai_meta return { _ai_meta: aiMeta, ...data }; } // New structure with data nested (opt-in) return { _ai_meta: aiMeta, data: data }; } /** * Determines tool status from response data */ static determineToolStatus(data) { if (!data || typeof data !== 'object') { return 'SUCCESS'; } // Check for errors if (data._isError || data.error || data.status === 'error') { return 'ERROR'; } // Check for warnings if (data.template_mode || data._usage_guidance || data.status === 'warning') { return 'WARNING'; } return 'SUCCESS'; } /** * Generates automatic AI guidance based on response patterns */ static generateAutoGuidance(data, options) { const guidance = {}; // Check for large result sets const resultCount = this.extractResultCount(data); if (resultCount && resultCount >= 50) { guidance.operation_hints = { complexity: 'simple', data_comprehensive: true, no_further_calls_needed: true }; guidance.avoid_common_mistakes = [ "Don't make additional calls - you have all data", "Focus on analyzing the results provided" ]; } // Check for template mode if (data && typeof data === 'object' && data.template_mode) { guidance.template_mode = { required: true, reason: data.template_mode.message || "Complex entity requires template", instruction: "Use get_entity_templates first, then manage_entity_lifecycle with mode='template'", template_tool: "get_entity_templates" }; guidance.recommended_next_tool = { tool: "get_entity_templates", reason: "Template required for this entity type", parameters: { entity_type: data.template_mode.entity_type } }; } // Check for multi-project queries if (options.project_context?.is_multi_project) { if (!guidance.operation_hints) { guidance.operation_hints = { complexity: 'simple', data_comprehensive: true }; } else { guidance.operation_hints.data_comprehensive = true; } guidance.success_indicators = { operation_completed: true, data_comprehensive: true }; } return Object.keys(guidance).length > 0 ? guidance : null; } // ===== PRIVATE HELPER METHODS ===== static extractResultCount(data) { // Handle array responses if (Array.isArray(data)) { return data.length; } // Handle object responses with common count patterns if (typeof data === 'object' && data !== null) { // Pattern 1: { total: number, [entityName]: array } if (typeof data.total === 'number') { return data.total; } // Pattern 2: { [entityName]: array } - count the array const arrayFields = Object.values(data).filter(Array.isArray); if (arrayFields.length === 1) { return arrayFields[0].length; } // Pattern 3: Single entity response if (!Array.isArray(data) && Object.keys(data).length > 0) { return 1; } } return undefined; } static determineQueryMode(data, projectContext) { // Check for explicit metadata (from MODE pattern tools) if (data && typeof data === 'object' && data._metadata?.mode) { return data._metadata.mode; } // Infer from project context if (projectContext?.is_multi_project || (projectContext?.projects_queried && projectContext.projects_queried > 1)) { return 'all_projects'; } if (projectContext?.project_id && projectContext.project_id !== 'ALL') { return 'single_project'; } // Check data structure for multi-project indicators if (data && typeof data === 'object' && data.summary?.by_project) { const projectCount = Object.keys(data.summary.by_project).length; return projectCount > 1 ? 'all_projects' : 'single_project'; } return undefined; } static checkForHardStop(data) { if (typeof data === 'object' && data !== null) { return data.status === 'HARD_STOP_REQUIRED' || data._isError === true || data.error !== undefined; } return false; } static determineStatus(data, hasHardStop) { if (hasHardStop) { return 'hard_stop_required'; } if (typeof data === 'object' && data !== null) { // Check explicit success indicators if (data.success === false) { return 'error'; } if (data.success === true) { return 'success'; } // Check for warning conditions if (data._usage_guidance || data.template_mode) { return 'warning'; } } return 'success'; } static getAppliedPatterns(options) { const patterns = ['response_wrapper']; if (options.enhance_for_mode_pattern) { patterns.push('mode_pattern_enhancement'); } if (options.preserve_hard_stops) { patterns.push('hard_stop_preservation'); } if (options.project_context) { patterns.push('project_context_inference'); } return patterns; } } /** * Decorator function for automatically wrapping tool methods * Usage: @withResponseWrapper('tool_name', options) */ export function withResponseWrapper(toolName, options = {}) { return function (target, propertyKey, descriptor) { const originalMethod = descriptor.value; descriptor.value = async function (...args) { const startTime = Date.now(); try { const result = await originalMethod.apply(this, args); return ResponseWrapper.wrap(result, { tool_name: toolName, start_time: startTime, ...options }); } catch (error) { // For errors, still wrap them for consistency return ResponseWrapper.wrap({ error: error.message, _isError: true }, { tool_name: toolName, start_time: startTime, ...options }); } }; return descriptor; }; } // ===== MIGRATION UTILITIES ===== /** * Utility for gradually migrating existing tools to use response wrapper * Allows selective enhancement without breaking existing functionality */ export class ResponseWrapperMigration { static enabledTools = new Set(); static enableForTool(toolName) { this.enabledTools.add(toolName); } static isEnabledForTool(toolName) { return this.enabledTools.has(toolName); } static wrapIfEnabled(data, toolName, options) { if (this.isEnabledForTool(toolName)) { return ResponseWrapper.wrap(data, options); } return data; } } //# sourceMappingURL=ResponseWrapper.js.map