@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
852 lines (741 loc) ⢠30 kB
JavaScript
/**
* 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;