UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

852 lines (741 loc) • 30 kB
/** * OptimizelyDocumentationHandler.cjs * * Handler that mimics OpenAPIReferenceHandler.ts functionality for * Optimizely SDK documentation queries. Provides structured parameter * interface for MCP server integration. */ const OptimizelySDKParser = require('../parsers/OptimizelySDKParser.cjs'); const path = require('path'); class OptimizelyDocumentationHandler { constructor(documentationPath = null) { this.parser = new OptimizelySDKParser(); this.documentationPath = documentationPath || this._getDefaultDocumentationPath(); this._isInitialized = false; } /** * Initialize the handler with documentation */ async initialize() { try { this.parser.loadDocumentation(this.documentationPath); this._isInitialized = true; return true; } catch (error) { throw new Error(`Failed to initialize documentation handler: ${error.message}`); } } /** * Main handler method that mirrors OpenAPIReferenceHandler structure * Processes structured queries with specific parameters */ async handleQuery(params) { if (!this._isInitialized) { await this.initialize(); } const { entity_type, information_type, sdk_name = null, product_type = null, // Add support for product_type method_name = null, concept_name = null, search_query = null, options = {} } = params; // Use product_type as sdk_name if sdk_name is not provided, with flexible mapping const rawSdkName = sdk_name || product_type; const effectiveSdkName = this._normalizeSdkName(rawSdkName); // Validate required parameters if (!entity_type || !information_type) { throw new Error('entity_type and information_type are required parameters'); } try { switch (entity_type) { case 'sdk': return await this._handleSDKQuery(information_type, effectiveSdkName, options); case 'platform': return await this._handlePlatformQuery(information_type, effectiveSdkName, options); case 'method': return await this._handleMethodQuery(information_type, effectiveSdkName, method_name, options); case 'concept': return await this._handleConceptQuery(information_type, concept_name, options); case 'overview': return await this._handleOverviewQuery(information_type, options); case 'search': return await this._handleSearchQuery(information_type, search_query, options); default: throw new Error(`Unsupported entity_type: ${entity_type}`); } } catch (error) { return this._formatErrorResponse(error, params); } } /** * Handle SDK-specific queries */ async _handleSDKQuery(informationType, sdkName, options) { if (!sdkName) { throw new Error('sdk_name is required for SDK queries'); } // Check if they're using a platform name as SDK - provide comprehensive guidance if (sdkName === 'web_experimentation' || sdkName === 'optimizely_agent') { throw new Error(`"${sdkName}" is a platform, not an SDK. šŸ“š HERE'S HOW TO ACCESS DOCUMENTATION: For SDKs (JavaScript, Python, Java, Swift, Android, PHP), use: { "entity_type": "sdk", "sdk_name": "javascript", // or: python, java, swift, android, php "information_type": "methods" // or: info, installation, configuration, setup, examples } For Platforms, use: { "entity_type": "platform", "platform": "web_experimentation", // or: optimizely_agent "information_type": "all" // or: info, api, methods, setup, deployment, examples } šŸŽÆ Quick Guide: - SDKs = Programming languages (JavaScript, Python, etc.) - web_experimentation = Visual Editor, A/B Testing, Page-based experiments - optimizely_agent = Microservice/Container deployment, REST API proxy šŸ’” Not sure which to use? Try searching: { "entity_type": "search", "search_query": "your question here", "information_type": "general" }`); } switch (informationType) { case 'info': return this._formatResponse('sdk_info', this.parser.getSDKInfo(sdkName)); case 'methods': const methods = this.parser.getAllMethods().filter(m => m.sdk === sdkName); return this._formatResponse('sdk_methods', methods); case 'installation': const sdkInfo = this.parser.getSDKInfo(sdkName); return this._formatResponse('installation_guide', { sdk: sdkName, installation: sdkInfo?.installation || (sdkName === 'optimizely_agent' ? sdkInfo?.deployment : null), deployment: sdkInfo?.deployment, package: sdkInfo?.package, version: sdkInfo?.version, platform: sdkInfo?.platform }); case 'examples': const sdkWithExamples = this.parser.getSDKInfo(sdkName); return this._formatResponse('sdk_examples', { sdk: sdkName, examples: sdkWithExamples?.examples, initialization: sdkWithExamples?.initialization }); case 'configuration': const sdkWithConfig = this.parser.getSDKInfo(sdkName); return this._formatResponse('sdk_configuration', { sdk: sdkName, initialization: sdkWithConfig?.initialization, configuration: sdkWithConfig?.configuration, installation: sdkWithConfig?.installation, package: sdkWithConfig?.package, version: sdkWithConfig?.version, platforms: sdkWithConfig?.platforms }); case 'setup': // Setup combines installation and configuration/initialization const sdkWithSetup = this.parser.getSDKInfo(sdkName); return this._formatResponse('sdk_setup', { sdk: sdkName, installation: sdkWithSetup?.installation, initialization: sdkWithSetup?.initialization, configuration: sdkWithSetup?.configuration, package: sdkWithSetup?.package, version: sdkWithSetup?.version, platforms: sdkWithSetup?.platforms, api: sdkWithSetup?.api // Include basic API info for quick reference }); case 'all': // Return comprehensive SDK information const allSdkInfo = this.parser.getSDKInfo(sdkName); const allMethods = this.parser.getAllMethods().filter(m => m.sdk === sdkName); return this._formatResponse('sdk_all', { sdk: sdkName, info: allSdkInfo, methods: allMethods, // Include all available data description: allSdkInfo?.description, architecture: allSdkInfo?.architecture, deployment: allSdkInfo?.deployment, installation: allSdkInfo?.installation, initialization: allSdkInfo?.initialization, configuration: allSdkInfo?.configuration, api_endpoints: allSdkInfo?.api_endpoints, admin_endpoints: allSdkInfo?.admin_endpoints, webhook_endpoints: allSdkInfo?.webhook_endpoints, examples: allSdkInfo?.examples, package: allSdkInfo?.package, version: allSdkInfo?.version, platforms: allSdkInfo?.platforms }); default: throw new Error(`Unsupported information_type for SDK: ${informationType}. Supported types: info, methods, installation, examples, configuration, setup, all`); } } /** * Handle platform-specific queries (Web Experimentation, Agent) */ async _handlePlatformQuery(informationType, platformName, options) { if (!platformName) { throw new Error('platform name is required for platform queries'); } // For ANY platform error, provide comprehensive guidance if (!['web_experimentation', 'optimizely_agent'].includes(platformName)) { throw new Error(`Platform "${platformName}" is not supported. šŸ“š HERE'S HOW TO ACCESS DOCUMENTATION: For SDKs (JavaScript, Python, Java, Swift, Android, PHP), use: { "entity_type": "sdk", "sdk_name": "javascript", // or: python, java, swift, android, php "information_type": "methods" // or: info, installation, configuration, setup, examples } For Platforms, use: { "entity_type": "platform", "platform": "web_experimentation", // or: optimizely_agent "information_type": "all" // or: info, api, methods, setup, deployment, examples } šŸŽÆ Quick Guide: - SDKs = Programming languages (JavaScript, Python, etc.) - web_experimentation = Visual Editor, A/B Testing, Page-based experiments - optimizely_agent = Microservice/Container deployment, REST API proxy šŸ’” Not sure which to use? Try searching: { "entity_type": "search", "search_query": "your question here", "information_type": "general" }`); } switch (informationType) { case 'info': return this._formatResponse('platform_info', this.parser.getSDKInfo(platformName)); case 'api': case 'methods': const methods = this.parser.getAllMethods().filter(m => m.sdk === platformName); return this._formatResponse('platform_api', methods); case 'setup': case 'installation': const platformInfo = this.parser.getSDKInfo(platformName); return this._formatResponse('platform_setup', { platform: platformName, setup: platformInfo?.installation || platformInfo?.integration, configuration: platformInfo?.configuration, deployment: platformInfo?.deployment }); case 'deployment': const deploymentInfo = this.parser.getSDKInfo(platformName); return this._formatResponse('platform_deployment', { platform: platformName, deployment: deploymentInfo?.deployment, architecture: deploymentInfo?.architecture, docker: deploymentInfo?.docker, kubernetes: deploymentInfo?.kubernetes }); case 'examples': const platformWithExamples = this.parser.getSDKInfo(platformName); return this._formatResponse('platform_examples', { platform: platformName, examples: platformWithExamples?.examples, implementation_patterns: platformWithExamples?.implementation_patterns }); case 'all': // Return comprehensive platform information const allPlatformInfo = this.parser.getSDKInfo(platformName); const allMethods = this.parser.getAllMethods().filter(m => m.sdk === platformName); return this._formatResponse('platform_all', { platform: platformName, info: allPlatformInfo, methods: allMethods, setup: allPlatformInfo?.installation || allPlatformInfo?.integration, configuration: allPlatformInfo?.configuration, deployment: allPlatformInfo?.deployment, examples: allPlatformInfo?.examples, implementation_patterns: allPlatformInfo?.implementation_patterns, // Include all available data for Agent description: allPlatformInfo?.description, architecture: allPlatformInfo?.architecture, api_endpoints: allPlatformInfo?.api_endpoints, admin_endpoints: allPlatformInfo?.admin_endpoints, webhook_endpoints: allPlatformInfo?.webhook_endpoints }); default: throw new Error(`Unsupported information_type for platform: ${informationType}. Supported types: info, api, methods, setup, installation, deployment, examples, all`); } } /** * Handle method-specific queries */ async _handleMethodQuery(informationType, sdkName, methodName, options) { if (!sdkName || !methodName) { throw new Error('sdk_name and method_name are required for method queries'); } switch (informationType) { case 'details': const details = this.parser.getMethodDetails(sdkName, methodName); if (!details) { throw new Error(`Method "${methodName}" not found`); } return this._formatResponse('method_details', details); case 'signature': const methodDetails = this.parser.getMethodDetails(sdkName, methodName); return this._formatResponse('method_signature', { sdk: sdkName, method: methodName, signature: methodDetails?.signature, parameters: methodDetails?.parameters, returns: methodDetails?.returns }); case 'examples': const examples = this.parser.getMethodExamples(sdkName, methodName); return this._formatResponse('method_examples', { sdk: sdkName, method: methodName, examples: examples }); case 'related': const methodInfo = this.parser.getMethodDetails(sdkName, methodName); return this._formatResponse('related_methods', { sdk: sdkName, method: methodName, related: methodInfo?.related_methods || [] }); default: throw new Error(`Unsupported information_type for method: ${informationType}`); } } /** * Handle concept-specific queries */ async _handleConceptQuery(informationType, conceptName, options) { if (!conceptName) { throw new Error('concept_name is required for concept queries'); } switch (informationType) { case 'details': const concept = this.parser.getConceptDetails(conceptName); return this._formatResponse('concept_details', { name: conceptName, ...concept }); case 'configuration': const conceptWithConfig = this.parser.getConceptDetails(conceptName); return this._formatResponse('concept_configuration', { concept: conceptName, configuration: conceptWithConfig?.configuration, description: conceptWithConfig?.description }); case 'examples': const conceptWithExamples = this.parser.getConceptDetails(conceptName); return this._formatResponse('concept_examples', { concept: conceptName, examples: conceptWithExamples?.examples }); default: throw new Error(`Unsupported information_type for concept: ${informationType}`); } } /** * Handle overview queries */ async _handleOverviewQuery(informationType, options) { switch (informationType) { case 'summary': const summary = this.parser.getAPISummary(); return this._formatResponse('documentation_summary', summary); case 'all_sdks': const summary2 = this.parser.getAPISummary(); return this._formatResponse('all_sdks', { sdks: summary2.sdks, total: summary2.sdks.length }); case 'all_methods': const allMethods = this.parser.getAllMethods(); return this._formatResponse('all_methods', allMethods); case 'all_concepts': const summary3 = this.parser.getAPISummary(); return this._formatResponse('all_concepts', summary3.concepts); case 'all_platforms': // Return information about all platform types const platforms = ['web_experimentation', 'optimizely_agent']; const platformsInfo = platforms.map(platform => { const info = this.parser.getSDKInfo(platform); return { name: platform, description: info?.description || 'No description available', type: info?.architecture?.type || 'Platform', deployment: info?.architecture?.deployment || [], hasData: !!info }; }); return this._formatResponse('all_platforms', { platforms: platformsInfo, total: platformsInfo.length }); default: throw new Error(`Unsupported information_type for overview: ${informationType}. Supported types: summary, all_sdks, all_methods, all_concepts, all_platforms`); } } /** * Handle search queries */ async _handleSearchQuery(informationType, searchQuery, options) { if (!searchQuery) { throw new Error('search_query is required for search queries'); } switch (informationType) { case 'general': const results = this.parser.findProperty(searchQuery, options); // If no results, throw error that will trigger helpful suggestions if (results.length === 0) { throw new Error('No results found'); } return this._formatResponse('search_results', { query: searchQuery, results: results, total: results.length }); case 'methods_only': const methodResults = this.parser.findProperty(searchQuery, options) .filter(r => r.type === 'method'); if (methodResults.length === 0) { throw new Error('No results found'); } return this._formatResponse('method_search_results', { query: searchQuery, results: methodResults, total: methodResults.length }); case 'concepts_only': const conceptResults = this.parser.findProperty(searchQuery, options) .filter(r => r.type === 'concept'); if (conceptResults.length === 0) { throw new Error('No results found'); } return this._formatResponse('concept_search_results', { query: searchQuery, results: conceptResults, total: conceptResults.length }); default: throw new Error(`Unsupported information_type for search: ${informationType}`); } } /** * Normalize SDK name to handle flexible naming with intelligent parsing */ _normalizeSdkName(rawName) { if (!rawName) return null; // Split camelCase and clean the input const cleanName = this._splitCamelCase(rawName).toLowerCase(); const words = cleanName.split(/[\s_\-\.]+/).filter(w => w.length > 0); // Direct mappings (highest priority) const directMappings = { // JavaScript variations 'javascript': 'javascript_sdk_v6', 'js': 'javascript_sdk_v6', 'javascript_sdk': 'javascript_sdk_v6', 'node': 'javascript_sdk_v6', 'nodejs': 'javascript_sdk_v6', 'typescript': 'javascript_sdk_v6', 'ts': 'javascript_sdk_v6', // React/Next.js (maps to JavaScript since React SDK uses JS) 'react': 'javascript_sdk_v6', 'reactjs': 'javascript_sdk_v6', 'react_sdk': 'javascript_sdk_v6', 'nextjs': 'javascript_sdk_v6', 'next': 'javascript_sdk_v6', 'next.js': 'javascript_sdk_v6', // Web Experimentation variations 'web': 'web_experimentation', 'web_exp': 'web_experimentation', 'browser': 'web_experimentation', 'client': 'web_experimentation', // Mobile variations 'ios': 'swift', 'swift_sdk': 'swift', 'android_sdk': 'android', 'kotlin': 'android', // Server variations 'java_sdk': 'java', 'python_sdk': 'python', 'php_sdk': 'php', 'py': 'python', // Agent/Container variations 'agent': 'optimizely_agent', 'microservice': 'optimizely_agent', 'server': 'optimizely_agent', 'container': 'optimizely_agent', 'docker': 'optimizely_agent', 'kubernetes': 'optimizely_agent', 'k8s': 'optimizely_agent', 'pod': 'optimizely_agent', 'helm': 'optimizely_agent', 'deployment': 'optimizely_agent' }; // Check direct mappings first const directMatch = directMappings[cleanName]; if (directMatch) return directMatch; // Smart word-based matching const sdkTypes = { javascript: ['javascript', 'js', 'node', 'typescript', 'ts', 'react', 'next'], web_experimentation: ['web', 'browser', 'client', 'experimentation'], swift: ['ios', 'swift'], android: ['android', 'kotlin'], java: ['java'], python: ['python', 'py'], php: ['php'], optimizely_agent: ['agent', 'microservice', 'server', 'container', 'docker', 'kubernetes', 'k8s', 'pod', 'helm', 'deployment'] }; // Look for SDK type matches in words for (const [sdkType, keywords] of Object.entries(sdkTypes)) { if (keywords.some(keyword => words.includes(keyword))) { // Add version suffix for javascript return sdkType === 'javascript' ? 'javascript_sdk_v6' : sdkType; } } // Handle version flexibility - if contains version numbers, ignore them const versionlessWords = words.filter(word => !word.match(/^v?\d+$/)); if (versionlessWords.length !== words.length) { // Try again without version numbers const versionlessName = versionlessWords.join('_'); const versionlessMatch = directMappings[versionlessName]; if (versionlessMatch) return versionlessMatch; // Try smart matching again without versions for (const [sdkType, keywords] of Object.entries(sdkTypes)) { if (keywords.some(keyword => versionlessWords.includes(keyword))) { return sdkType === 'javascript' ? 'javascript_sdk_v6' : sdkType; } } } // Return original if no mapping found return rawName; } /** * Split camelCase strings into space-separated words */ _splitCamelCase(str) { return str .replace(/([a-z])([A-Z])/g, '$1 $2') .replace(/([A-Z])([A-Z][a-z])/g, '$1 $2') .replace(/([0-9])([A-Za-z])/g, '$1 $2') .replace(/([A-Za-z])([0-9])/g, '$1 $2'); } /** * Format successful response in MCP-compatible structure */ _formatResponse(responseType, data) { return { success: true, response_type: responseType, data: data, timestamp: new Date().toISOString(), handler: 'OptimizelyDocumentationHandler' }; } /** * Format error response in MCP-compatible structure */ _formatErrorResponse(error, originalParams) { // Provide helpful suggestions based on the error let suggestions = []; let helpfulMessage = error.message; // Check if it's a method not found error const sdkName = originalParams.sdk_name || originalParams.product_type; if (error.message.includes('sdk_name and method_name are required') || (originalParams.entity_type === 'method' && !this.parser.getMethodDetails(sdkName, originalParams.method_name))) { // For Web Experimentation, suggest the correct method names if (sdkName === 'web_experimentation') { suggestions = [ 'Try using the full method name like "window.optimizely.get" or "window.optimizely.push"', 'For push commands, use format like "push:event", "push:page", etc.', 'Available Web Experimentation methods:', ' - window.optimizely.get - Retrieve data from Optimizely', ' - window.optimizely.push - Execute commands', ' - push:event - Track custom events', ' - push:page - Activate pages', ' - push:user - Set user attributes', ' - push:decision - Make decisions', '', 'Example queries:', ' - method_name: "window.optimizely.get"', ' - method_name: "push:event"' ]; helpfulMessage = `Method "${originalParams.method_name}" not found in Web Experimentation API.`; } else { // Get available methods for the SDK const methods = this.parser.getAllMethods().filter(m => m.sdk === sdkName); if (methods.length > 0) { suggestions = [ `Available methods for ${sdkName}:`, ...methods.slice(0, 10).map(m => ` - ${m.method}`), methods.length > 10 ? ` ... and ${methods.length - 10} more` : '' ]; } } } // Check if it's a search with no results if (originalParams.entity_type === 'search' && error.message === 'No results found') { suggestions = [ 'Try using fewer or more specific search terms', 'Search tips:', ' - Use single keywords like "event", "user", "variation"', ' - For Web Experimentation, try: "push", "window", "state", "visitor"', ' - For Feature Experimentation, try: "decide", "track", "context"', '', 'You can also browse all methods using:', ' entity_type: "overview", information_type: "all_methods"' ]; helpfulMessage = `No results found for "${originalParams.search_query}".`; } return { success: false, error: { message: helpfulMessage, type: error.constructor.name, original_params: originalParams, suggestions: suggestions.length > 0 ? suggestions : undefined }, timestamp: new Date().toISOString(), handler: 'OptimizelyDocumentationHandler' }; } /** * Get default documentation file path */ _getDefaultDocumentationPath() { return path.join(__dirname, '..', 'resources', 'full-api-docs-web-fx.json'); } /** * Get available entity types and information types */ getAvailableQueries() { return { entity_types: { sdk: { description: 'Query SDK-specific information', required_params: ['sdk_name'], information_types: ['info', 'methods', 'installation', 'examples', 'configuration', 'setup'] }, method: { description: 'Query method-specific information', required_params: ['sdk_name', 'method_name'], information_types: ['details', 'signature', 'examples', 'related'] }, concept: { description: 'Query shared concept information', required_params: ['concept_name'], information_types: ['details', 'configuration', 'examples'] }, overview: { description: 'Query overview information', required_params: [], information_types: ['summary', 'all_sdks', 'all_methods', 'all_concepts'] }, search: { description: 'Search documentation', required_params: ['search_query'], information_types: ['general', 'methods_only', 'concepts_only'] } }, examples: [ { entity_type: 'sdk', information_type: 'info', sdk_name: 'javascript' }, { entity_type: 'method', information_type: 'details', sdk_name: 'javascript', method_name: 'createUserContext' }, { entity_type: 'concept', information_type: 'details', concept_name: 'event_batching' }, { entity_type: 'overview', information_type: 'summary' }, { entity_type: 'search', information_type: 'general', search_query: 'user context' } ] }; } } // CLI interface for testing if (require.main === module) { const args = process.argv.slice(2); if (args.length === 0) { console.log(` Usage: node OptimizelyDocumentationHandler.cjs [OPTIONS] Test the structured query interface: EXAMPLES: node OptimizelyDocumentationHandler.cjs --test-overview node OptimizelyDocumentationHandler.cjs --test-sdk javascript node OptimizelyDocumentationHandler.cjs --test-search "user context" node OptimizelyDocumentationHandler.cjs --available-queries `); process.exit(0); } const handler = new OptimizelyDocumentationHandler(); async function runTest() { try { const command = args[0]; let result; switch (command) { case '--test-overview': result = await handler.handleQuery({ entity_type: 'overview', information_type: 'summary' }); break; case '--test-sdk': if (!args[1]) { console.error('SDK name required for --test-sdk'); process.exit(1); } result = await handler.handleQuery({ entity_type: 'sdk', information_type: 'info', sdk_name: args[1] }); break; case '--test-search': if (!args[1]) { console.error('Search query required for --test-search'); process.exit(1); } result = await handler.handleQuery({ entity_type: 'search', information_type: 'general', search_query: args[1] }); break; case '--available-queries': result = handler.getAvailableQueries(); break; default: console.error(`Unknown command: ${command}`); process.exit(1); } console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error('Error:', error.message); process.exit(1); } } runTest(); } module.exports = OptimizelyDocumentationHandler;