jaxon-optimizely-dxp-mcp
Version: 
AI-powered automation for Optimizely DXP - deploy, monitor, and manage environments through natural conversations
1,143 lines (1,001 loc) β’ 54.9 kB
JavaScript
#!/usr/bin/env node
/**
 * Jaxon Digital Optimizely DXP MCP Server
 * Built with official @modelcontextprotocol for full Claude compatibility
 * 
 * Built by Jaxon Digital - Optimizely Gold Partner
 * https://www.jaxondigital.com
 */
// Load environment variables from .env file if it exists
const fs = require('fs');
const path = require('path');
const envPath = path.join(process.cwd(), '.env');
if (fs.existsSync(envPath)) {
  const envContent = fs.readFileSync(envPath, 'utf8');
  envContent.split('\n').forEach(line => {
    if (line && !line.startsWith('#')) {
      const [key, ...valueParts] = line.split('=');
      if (key && valueParts.length > 0) {
        process.env[key.trim()] = valueParts.join('=').trim();
      }
    }
  });
}
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
const { 
    ListToolsRequestSchema,
    CallToolRequestSchema 
} = require('@modelcontextprotocol/sdk/types.js');
const { z } = require('zod');
const { zodToJsonSchema } = require('zod-to-json-schema');
// Import existing modules
const libPath = path.join(__dirname, 'lib');
const Config = require(path.join(libPath, 'config'));
const ErrorHandler = require(path.join(libPath, 'error-handler'));
const ResponseBuilder = require(path.join(libPath, 'response-builder'));
const OutputLogger = require(path.join(libPath, 'output-logger'));
const { 
    DatabaseTools, 
    DeploymentTools, 
    StorageTools, 
    PackageTools, 
    LoggingTools,
    ContentTools,
    DeploymentHelperTools 
} = require(path.join(libPath, 'tools'));
const ProjectTools = require(path.join(libPath, 'tools', 'project-tools'));
const MonitoringTools = require(path.join(libPath, 'tools', 'monitoring-tools'));
const ConnectionTestTools = require(path.join(libPath, 'tools', 'connection-test-tools'));
const SetupWizard = require(path.join(libPath, 'tools', 'setup-wizard'));
const SimpleTools = require(path.join(libPath, 'tools', 'simple-tools'));
const DatabaseSimpleTools = require(path.join(libPath, 'tools', 'database-simple-tools'));
const ProjectSwitchTool = require(path.join(libPath, 'tools', 'project-switch-tool'));
const VersionChecker = require(path.join(libPath, 'version-check'));
const { getTelemetry } = require(path.join(libPath, 'telemetry'));
// Initialize telemetry
const telemetry = getTelemetry();
// Check for updates on startup (async, non-blocking)
(async () => {
    const updateInfo = await VersionChecker.checkForUpdates();
    const notification = VersionChecker.formatUpdateNotification(updateInfo);
    if (notification) {
        OutputLogger.info(notification);
    }
})();
// Helper function to normalize environment names
function normalizeEnvironmentName(env) {
    if (!env) return env;
    
    const envUpper = env.toUpperCase();
    
    // Map common abbreviations to full names
    const abbreviations = {
        'INT': 'Integration',
        'INTE': 'Integration',
        'INTEGRATION': 'Integration',
        'PREP': 'Preproduction',
        'PRE': 'Preproduction',
        'PREPRODUCTION': 'Preproduction',
        'PROD': 'Production',
        'PRODUCTION': 'Production'
    };
    
    return abbreviations[envUpper] || env;
}
// Custom Zod transformer for environment names
const environmentSchema = z.string().transform(normalizeEnvironmentName).pipe(
    z.enum(['Integration', 'Preproduction', 'Production'])
);
// Define Zod schemas for each tool
const schemas = {
    // Simple Commands - Dead Simple with Smart Defaults
    deploy: z.object({
        target: z.string().optional().describe('Target environment: prod, staging, integration (default: prod)'),
        source: z.string().optional().describe('Source environment (auto-detected if not specified)'),
        project: z.string().optional().describe('Project name (uses default if not specified)'),
        dryRun: z.boolean().optional().describe('Show what would be deployed without executing')
    }),
    
    status: z.object({
        project: z.string().optional().describe('Project name (uses default if not specified)'),
        environment: z.string().optional().describe('Filter to specific environment')
    }),
    
    rollback: z.object({
        environment: z.string().optional().describe('Environment to rollback (default: production)'),
        project: z.string().optional().describe('Project name (uses default if not specified)')
    }),
    
    quick: z.object({
        project: z.string().optional().describe('Project name (uses default if not specified)')
    }),
    
    // Database Simple Commands - Natural language for database operations
    backup: z.object({
        environment: z.string().optional().describe('Environment to backup: prod, staging, integration (default: prod)'),
        project: z.string().optional().describe('Project name (uses default if not specified)'),
        databaseName: z.string().optional().describe('Database name (default: epicms)'),
        dryRun: z.boolean().optional().describe('Preview what will happen without executing'),
        autoDownload: z.boolean().optional().describe('Automatically download backup when complete'),
        downloadPath: z.string().optional().describe('Path to save downloaded backup (default: ./backups)'),
        forceNew: z.boolean().optional().describe('Force creation of new backup even if recent one exists')
    }),
    
    backup_status: z.object({
        exportId: z.string().optional().describe('Export ID to check (uses latest if not specified)'),
        project: z.string().optional().describe('Project name (uses default if not specified)'),
        latest: z.boolean().optional().describe('Check status of latest backup')
    }),
    
    list_backups: z.object({
        project: z.string().optional().describe('Project name (uses default if not specified)'),
        limit: z.number().optional().describe('Number of recent backups to show (default: 5)')
    }),
    
    check_download_capabilities: z.object({
        downloadPath: z.string().optional().describe('Path to check for download capability (default: ./backups)')
    }),
    
    // Project switching
    switch_project: z.object({
        projectName: z.string().describe('Name of the project to switch to')
    }),
    
    current_project: z.object({}),
    // Connection testing
    test_connection: z.object({
        projectId: z.string().optional(),
        projectName: z.string().optional(),
        apiKey: z.string().optional(),
        apiSecret: z.string().optional()
    }),
    
    health_check: z.object({
        projectId: z.string().optional(),
        projectName: z.string().optional(),
        apiKey: z.string().optional(),
        apiSecret: z.string().optional()
    }),
    
    setup_wizard: z.object({
        skipChecks: z.boolean().optional(),
        autoFix: z.boolean().optional()
    }),
    
    // Version information
    get_version: z.object({}),
    
    // Project management
    get_api_key_info: z.object({
        projectId: z.string().optional(),
        projectName: z.string().optional(),
        apiKey: z.string().optional(),
        apiSecret: z.string().optional()
    }),
    
    list_api_keys: z.object({}),
    
    get_support: z.object({}),
    
    list_monitors: z.object({}),
    
    update_monitoring_interval: z.object({
        deploymentId: z.string().optional(),
        interval: z.number().min(10).max(600)
    }),
    
    stop_monitoring: z.object({
        deploymentId: z.string().optional(),
        all: z.boolean().optional()
    }),
    
    get_monitoring_stats: z.object({}),
    
    
    disable_telemetry: z.object({}),
    
    enable_telemetry: z.object({}),
    
    get_rate_limit_status: z.object({
        projectName: z.string().optional(),
        projectId: z.string().optional()
    }),
    
    get_cache_status: z.object({
        projectName: z.string().optional(),
        projectId: z.string().optional(),
        action: z.enum(['status', 'clear']).optional().default('status')
    }),
    
    // Database operations
    export_database: z.object({
        environment: environmentSchema,
        databaseName: z.enum(['epicms', 'epicommerce']),
        projectName: z.string().optional(),
        projectId: z.string().optional(),
        apiKey: z.string().optional(),
        apiSecret: z.string().optional()
    }),
    
    check_export_status: z.object({
        exportId: z.string(),
        projectName: z.string().optional(),
        projectId: z.string().optional(),
        environment: environmentSchema,
        databaseName: z.enum(['epicms', 'epicommerce']),
        apiKey: z.string().optional(),
        apiSecret: z.string().optional()
    }),
    
    // Deployment operations
    list_deployments: z.object({
        limit: z.number().min(1).max(100).optional().default(20),
        offset: z.number().min(0).optional(),
        projectName: z.string().optional(),
        projectId: z.string().optional(),
        apiKey: z.string().optional(),
        apiSecret: z.string().optional()
    }),
    
    start_deployment: z.object({
        sourceEnvironment: environmentSchema,
        targetEnvironment: environmentSchema,
        deploymentType: z.enum(['code', 'content', 'all']).optional(),
        sourceApps: z.array(z.string()).optional(),
        includeBlob: z.boolean().optional(),
        includeDatabase: z.boolean().optional(),
        directDeploy: z.boolean().optional().default(false),
        useMaintenancePage: z.boolean().optional().default(false),
        projectName: z.string().optional(),
        projectId: z.string().optional(),
        apiKey: z.string().optional(),
        apiSecret: z.string().optional()
    }),
    
    get_deployment_status: z.object({
        deploymentId: z.string(),
        projectName: z.string().optional(),
        projectId: z.string().optional(),
        apiKey: z.string().optional(),
        apiSecret: z.string().optional()
    }),
    
    complete_deployment: z.object({
        deploymentId: z.string(),
        projectName: z.string().optional(),
        projectId: z.string().optional(),
        apiKey: z.string().optional(),
        apiSecret: z.string().optional()
    }),
    
    reset_deployment: z.object({
        deploymentId: z.string(),
        projectName: z.string().optional(),
        projectId: z.string().optional(),
        apiKey: z.string().optional(),
        apiSecret: z.string().optional()
    }),
    
    // Storage operations
    list_storage_containers: z.object({
        environment: environmentSchema,
        projectName: z.string().optional(),
        projectId: z.string().optional(),
        apiKey: z.string().optional(),
        apiSecret: z.string().optional()
    }),
    
    generate_storage_sas_link: z.object({
        environment: environmentSchema,
        containerName: z.string(),
        projectName: z.string().optional(),
        projectId: z.string().optional(),
        permissions: z.enum(['Read', 'Write', 'Delete', 'List']).optional().default('Read'),
        expiryHours: z.number().optional().default(24),
        apiKey: z.string().optional(),
        apiSecret: z.string().optional()
    }),
    
    // Package operations
    upload_deployment_package: z.object({
        environment: environmentSchema,
        packagePath: z.string(),
        projectName: z.string().optional(),
        projectId: z.string().optional(),
        apiKey: z.string().optional(),
        apiSecret: z.string().optional()
    }),
    
    deploy_package_and_start: z.object({
        sourceEnvironment: environmentSchema,
        targetEnvironment: environmentSchema,
        packagePath: z.string(),
        projectName: z.string().optional(),
        projectId: z.string().optional(),
        directDeploy: z.boolean().optional().default(true),
        apiKey: z.string().optional(),
        apiSecret: z.string().optional()
    }),
    // Azure DevOps Integration
    deploy_azure_artifact: z.object({
        artifactUrl: z.string().describe('Azure DevOps build artifact URL (e.g. https://dev.azure.com/org/project/_apis/build/builds/12345/artifacts)'),
        targetEnvironment: environmentSchema.describe('Target environment for deployment'),
        azureDevOpsPat: z.string().optional().describe('Azure DevOps Personal Access Token (can also use AZURE_DEVOPS_PAT env var)'),
        artifactName: z.string().optional().default('drop').describe('Name of the artifact to download (default: drop)'),
        cleanupAfterDeploy: z.boolean().optional().default(true).describe('Clean up downloaded artifact after deployment'),
        projectName: z.string().optional(),
        projectId: z.string().optional(),
        directDeploy: z.boolean().optional().default(true),
        useMaintenancePage: z.boolean().optional().default(false),
        zeroDowntimeMode: z.boolean().optional().default(false),
        warmUpUrl: z.string().optional(),
        waitForCompletion: z.boolean().optional().default(false),
        waitTimeoutMinutes: z.number().optional().default(30),
        apiKey: z.string().optional(),
        apiSecret: z.string().optional()
    }),
    deploy_package_enhanced: z.object({
        packagePath: z.string().optional().describe('Local package file path (for traditional deployment)'),
        artifactUrl: z.string().optional().describe('Azure DevOps artifact URL (for CI/CD integration)'),
        targetEnvironment: environmentSchema.describe('Target environment for deployment'),
        azureDevOpsPat: z.string().optional().describe('Azure DevOps PAT (required for artifact URLs)'),
        artifactName: z.string().optional().default('drop').describe('Artifact name to download'),
        cleanupAfterDeploy: z.boolean().optional().default(true).describe('Clean up temporary files'),
        projectName: z.string().optional(),
        projectId: z.string().optional(),
        directDeploy: z.boolean().optional().default(true),
        useMaintenancePage: z.boolean().optional().default(false),
        zeroDowntimeMode: z.boolean().optional().default(false),
        warmUpUrl: z.string().optional(),
        waitForCompletion: z.boolean().optional().default(false),
        waitTimeoutMinutes: z.number().optional().default(30),
        apiKey: z.string().optional(),
        apiSecret: z.string().optional()
    }),
    
    // Logging operations
    get_edge_logs: z.object({
        environment: environmentSchema.optional(),
        projectName: z.string().optional(),
        projectId: z.string().optional(),
        hours: z.number().optional().default(1),
        apiKey: z.string().optional(),
        apiSecret: z.string().optional()
    }),
    
    // Content operations
    copy_content: z.object({
        sourceEnvironment: environmentSchema,
        targetEnvironment: environmentSchema,
        projectName: z.string().optional(),
        projectId: z.string().optional(),
        apiKey: z.string().optional(),
        apiSecret: z.string().optional()
    }),
    
    // Deployment helper operations
    analyze_package: z.object({
        packagePath: z.string()
    }),
    
    prepare_deployment_package: z.object({
        sourcePath: z.string(),
        outputPath: z.string().optional(),
        excludePatterns: z.array(z.string()).optional()
    }),
    
    generate_sas_upload_url: z.object({
        environment: environmentSchema,
        projectName: z.string().optional(),
        projectId: z.string().optional(),
        apiKey: z.string().optional(),
        apiSecret: z.string().optional()
    }),
    
    split_package: z.object({
        packagePath: z.string(),
        chunkSizeMB: z.number().optional().default(50)
    })
};
// Special handler for project info - now delegated to ProjectTools
function handleProjectInfo(args) {
    return ProjectTools.getProjectInfo(args);
}
// Legacy project info handler (for reference)
function handleProjectInfoLegacy() {
    const projectId = process.env.OPTIMIZELY_PROJECT_ID;
    const projectName = process.env.OPTIMIZELY_PROJECT_NAME;
    const hasApiKey = !!process.env.OPTIMIZELY_API_KEY;
    const hasApiSecret = !!process.env.OPTIMIZELY_API_SECRET;
    const isConfigured = projectId && hasApiKey && hasApiSecret;
    
    let infoText = `π **Optimizely DXP Project Information**\n\n`;
    
    if (isConfigured) {
        if (projectName) {
            infoText += `β
 **Active Project: ${projectName}**\n\n`;
        } else {
            infoText += `β
 **Project is configured and ready!**\n\n`;
        }
        
        infoText += `**Project Details:**\n`;
        
        if (projectName) {
            infoText += `β’ Name: **${projectName}**\n`;
        }
        
        infoText += `β’ Project ID: \`${projectId}\`\n` +
                   `β’ API Key: β
 Configured\n` +
                   `β’ API Secret: β
 Configured\n`;
    } else {
        infoText += `β οΈ **Configuration Required**\n\n` +
                   `**Current Status:**\n` +
                   `β’ Project ID: ${projectId ? `\`${projectId}\`` : 'β Not configured'}\n` +
                   `β’ API Key: ${hasApiKey ? 'β
 Configured' : 'β Not configured'}\n` +
                   `β’ API Secret: ${hasApiSecret ? 'β
 Configured' : 'β Not configured'}\n\n`;
        
        if (!projectId || !hasApiKey || !hasApiSecret) {
            infoText += `**To configure, you have two options:**\n\n` +
                       `**Option 1: Pass credentials with each tool call**\n` +
                       `When using any tool, provide:\n` +
                       `β’ projectId: "your-project-id"\n` +
                       `β’ apiKey: "your-api-key"\n` +
                       `β’ apiSecret: "your-api-secret"\n\n` +
                       `**Option 2: Configure environment variables (recommended)**\n` +
                       `Edit your MCP client config and add:\n\n` +
                       `\`\`\`json\n` +
                       `"env": {\n` +
                       `  "OPTIMIZELY_PROJECT_ID": "your-project-id",\n` +
                       `  "OPTIMIZELY_API_KEY": "your-api-key",\n` +
                       `  "OPTIMIZELY_API_SECRET": "your-api-secret"\n` +
                       `}\n` +
                       `\`\`\`\n\n` +
                       `Then restart your MCP client.\n`;
        }
    }
    
    infoText += `\nBuilt by Jaxon Digital - Optimizely Gold Partner`;
    
    return {
        result: {
            content: [{
                type: 'text',
                text: infoText
            }]
        }
    };
}
/**
 * Handle analytics request
 */
function handleDisableTelemetry(args) {
    try {
        // Disable telemetry for this session
        telemetry.enabled = false;
        
        // Also set environment variable for future sessions in this process
        process.env.OPTIMIZELY_MCP_TELEMETRY = 'false';
        
        return {
            result: {
                content: [{
                    type: 'text',
                    text: `π **Telemetry Disabled**\n\n` +
                          `β
 Anonymous telemetry has been disabled for this session.\n\n` +
                          `**What this means:**\n` +
                          `β’ No usage data will be collected\n` +
                          `β’ No performance metrics will be tracked\n` +
                          `β’ No error reports will be sent\n\n` +
                          `**To make this permanent across all sessions:**\n\n` +
                          `**Option 1:** Add to your Claude Desktop config:\n` +
                          `\`"OPTIMIZELY_MCP_TELEMETRY": "false"\`\n\n` +
                          `**Option 2:** Set environment variable:\n` +
                          `\`export OPTIMIZELY_MCP_TELEMETRY=false\`\n\n` +
                          `**To re-enable:** Use the \`enable_telemetry\` tool.\n\n` +
                          `Thank you for using Jaxon Digital's Optimizely DXP MCP Server! π`
                }]
            }
        };
    } catch (error) {
        const errorMessage = ErrorHandler.formatError(error, { tool: 'disable_telemetry' });
        return {
            result: {
                content: [{
                    type: 'text',
                    text: errorMessage
                }]
            }
        };
    }
}
function handleEnableTelemetry(args) {
    try {
        // Enable telemetry for this session
        telemetry.enabled = true;
        
        // Remove the disable flag from environment
        delete process.env.OPTIMIZELY_MCP_TELEMETRY;
        
        return {
            result: {
                content: [{
                    type: 'text',
                    text: `π **Telemetry Enabled**\n\n` +
                          `β
 Anonymous telemetry has been re-enabled for this session.\n\n` +
                          `**What we collect (anonymously):**\n` +
                          `β’ Tool usage patterns (which tools are used most)\n` +
                          `β’ Performance metrics (operation times)\n` +
                          `β’ Error categories (no sensitive data)\n\n` +
                          `**Privacy guaranteed:**\n` +
                          `β’ No personal information\n` +
                          `β’ No project names or IDs\n` +
                          `β’ No API keys or secrets\n` +
                          `β’ No file contents or paths\n\n` +
                          `**To disable again:** Use the \`disable_telemetry\` tool.\n\n` +
                          `Thank you for helping us improve this tool! π`
                }]
            }
        };
    } catch (error) {
        const errorMessage = ErrorHandler.formatError(error, { tool: 'enable_telemetry' });
        return {
            result: {
                content: [{
                    type: 'text',
                    text: errorMessage
                }]
            }
        };
    }
}
/**
 * Handle rate limit status request
 */
function handleGetRateLimitStatus(args) {
    try {
        const PowerShellHelper = require(path.join(libPath, 'powershell-helper'));
        const rateLimiter = PowerShellHelper.getRateLimiter();
        
        // Get project credentials for the status check
        let projectId = args.projectId || process.env.OPTIMIZELY_PROJECT_ID;
        
        // If project name provided, try to resolve it
        if (args.projectName && !projectId) {
            const ProjectTools = require(path.join(libPath, 'tools', 'project-tools'));
            const projectCreds = ProjectTools.getProjectCredentials(args.projectName);
            if (projectCreds) {
                projectId = projectCreds.projectId;
            }
        }
        
        if (!projectId) {
            const defaultCreds = ProjectTools.getProjectCredentials();
            projectId = defaultCreds.projectId;
        }
        
        if (!projectId) {
            return {
                error: `No project ID found. Please provide a projectId parameter or configure environment variables.\n\nπ§ Need help? Contact us at support@jaxondigital.com`
            };
        }
        
        const status = rateLimiter.getStatus(projectId);
        const suggestedWait = rateLimiter.getSuggestedWaitTime(projectId);
        
        let statusText = `β‘ **Rate Limit Status**\n\n`;
        statusText += `**Project:** \`${projectId}\`\n\n`;
        
        statusText += `π **Usage Quotas**\n`;
        statusText += `β’ Requests per minute: ${status.requestsLastMinute}/${status.maxRequestsPerMinute}\n`;
        statusText += `β’ Requests per hour: ${status.requestsLastHour}/${status.maxRequestsPerHour}\n`;
        
        const minutePercent = ((status.requestsLastMinute / status.maxRequestsPerMinute) * 100).toFixed(1);
        const hourPercent = ((status.requestsLastHour / status.maxRequestsPerHour) * 100).toFixed(1);
        statusText += `β’ Minute usage: ${minutePercent}%\n`;
        statusText += `β’ Hour usage: ${hourPercent}%\n\n`;
        
        if (status.isThrottled) {
            const waitTime = Math.ceil((status.throttleRetryAfter - Date.now()) / 1000);
            statusText += `π¨ **Currently Throttled**\n`;
            statusText += `β’ Status: API returned 429 (Too Many Requests)\n`;
            statusText += `β’ Wait time: ${waitTime} seconds\n`;
            statusText += `β’ Retry after: ${new Date(status.throttleRetryAfter).toISOString()}\n\n`;
        } else if (status.backoffUntil) {
            const waitTime = Math.ceil((status.backoffUntil - Date.now()) / 1000);
            statusText += `β³ **Backing Off**\n`;
            statusText += `β’ Reason: Consecutive failures\n`;
            statusText += `β’ Wait time: ${waitTime} seconds\n`;
            statusText += `β’ Retry after: ${new Date(status.backoffUntil).toISOString()}\n\n`;
        } else if (suggestedWait > 0) {
            statusText += `β οΈ  **Usage Warning**\n`;
            statusText += `β’ Status: Approaching rate limits\n`;
            statusText += `β’ Suggested wait: ${Math.ceil(suggestedWait / 1000)} seconds\n`;
            statusText += `β’ Recommendation: Space out requests\n\n`;
        } else {
            statusText += `β
 **Status: Good**\n`;
            statusText += `β’ No rate limiting active\n`;
            statusText += `β’ Requests can proceed normally\n\n`;
        }
        
        if (status.consecutiveFailures > 0) {
            statusText += `β οΈ  **Error History**\n`;
            statusText += `β’ Consecutive failures: ${status.consecutiveFailures}\n`;
            statusText += `β’ This triggers exponential backoff\n\n`;
        }
        
        if (status.lastRequest > 0) {
            const lastRequestAge = ((Date.now() - status.lastRequest) / 1000).toFixed(1);
            statusText += `π
 **Last Request**\n`;
            statusText += `β’ ${lastRequestAge} seconds ago\n`;
            statusText += `β’ Time: ${new Date(status.lastRequest).toISOString()}\n\n`;
        }
        
        statusText += `π§ **Rate Limiting Info**\n`;
        statusText += `β’ Rate limiting helps prevent API abuse\n`;
        statusText += `β’ Limits are per-project and reset automatically\n`;
        statusText += `β’ Failed requests don't count against quotas\n`;
        statusText += `β’ The system uses exponential backoff for failed requests\n\n`;
        
        statusText += `π‘ **Tips**\n`;
        statusText += `β’ Space out requests when approaching limits\n`;
        statusText += `β’ Use batch operations when possible\n`;
        statusText += `β’ Check this status if requests are being throttled\n\n`;
        
        statusText += `π§ Need help? Contact us at support@jaxondigital.com`;
        
        return {
            result: {
                content: [{
                    type: 'text',
                    text: statusText
                }]
            }
        };
        
    } catch (error) {
        OutputLogger.error('Rate limit status error:', error);
        return {
            error: `Failed to get rate limit status: ${error.message}\n\nπ§ Need help? Contact us at support@jaxondigital.com`
        };
    }
}
/**
 * Handle cache status request
 */
function handleGetCacheStatus(args) {
    try {
        const PowerShellHelper = require(path.join(libPath, 'powershell-helper'));
        
        // Get project credentials for the status check
        let projectId = args.projectId || process.env.OPTIMIZELY_PROJECT_ID;
        
        // If project name provided, try to resolve it
        if (args.projectName && !projectId) {
            const ProjectTools = require(path.join(libPath, 'tools', 'project-tools'));
            const projectCreds = ProjectTools.getProjectCredentials(args.projectName);
            if (projectCreds) {
                projectId = projectCreds.projectId;
            }
        }
        
        if (!projectId && args.action === 'clear') {
            const defaultCreds = ProjectTools.getProjectCredentials();
            projectId = defaultCreds.projectId;
        }
        
        // Handle clear action
        if (args.action === 'clear') {
            if (!projectId) {
                return {
                    error: `No project ID found for cache clearing. Please provide a projectId parameter.\n\nπ§ Need help? Contact us at support@jaxondigital.com`
                };
            }
            
            PowerShellHelper.clearCache(projectId);
            
            return {
                result: {
                    content: [{
                        type: 'text',
                        text: `β
 **Cache Cleared**\n\n` +
                              `**Project:** \`${projectId}\`\n\n` +
                              `All cached entries for this project have been removed.\n\n` +
                              `π§ Need help? Contact us at support@jaxondigital.com`
                    }]
                }
            };
        }
        
        // Get cache statistics
        const stats = PowerShellHelper.getCacheStats();
        
        let statusText = `πΎ **Cache Status**\n\n`;
        
        statusText += `π **Performance Metrics**\n`;
        statusText += `β’ Hit Rate: ${stats.hitRate} (${stats.hits} hits, ${stats.misses} misses)\n`;
        statusText += `β’ Total Entries: ${stats.entries}/${stats.maxEntries || 1000}\n`;
        statusText += `β’ Cache Size: ${stats.sizeMB} MB / ${stats.maxSizeMB} MB\n`;
        statusText += `β’ Operations: ${stats.sets} sets, ${stats.deletes} deletes\n\n`;
        
        const efficiency = stats.hits + stats.misses > 0 ? ((stats.hits / (stats.hits + stats.misses)) * 100) : 0;
        
        if (efficiency >= 70) {
            statusText += `β
 **Cache Performance: Excellent**\n`;
            statusText += `β’ High hit rate indicates good caching efficiency\n`;
            statusText += `β’ Frequently accessed data is being cached effectively\n\n`;
        } else if (efficiency >= 40) {
            statusText += `β οΈ  **Cache Performance: Good**\n`;
            statusText += `β’ Moderate hit rate - caching is helping performance\n`;
            statusText += `β’ Consider using operations that benefit from caching more frequently\n\n`;
        } else if (stats.hits + stats.misses > 10) {
            statusText += `π **Cache Performance: Low**\n`;
            statusText += `β’ Low hit rate - cache may need tuning\n`;
            statusText += `β’ Operations may not be benefiting from caching\n\n`;
        } else {
            statusText += `π **Cache Performance: Starting**\n`;
            statusText += `β’ Not enough data to determine efficiency\n`;
            statusText += `β’ Performance will improve with usage\n\n`;
        }
        
        if (stats.entries > 0) {
            statusText += `π§ **Cache Details**\n`;
            statusText += `β’ Cached operations include: list_deployments, get_deployment_status, list_storage_containers\n`;
            statusText += `β’ Cache automatically expires based on data type\n`;
            statusText += `β’ Write operations automatically invalidate related cache entries\n`;
            statusText += `β’ Cache is persistent across sessions\n\n`;
        }
        
        statusText += `π‘ **How Caching Helps**\n`;
        statusText += `β’ Reduces API calls to Optimizely DXP\n`;
        statusText += `β’ Improves response times for repeated operations\n`;
        statusText += `β’ Respects rate limits by serving cached results\n`;
        statusText += `β’ Automatically invalidates when data changes\n\n`;
        
        statusText += `π **Cache Management**\n`;
        statusText += `β’ Use \`get_cache_status\` with \`action: "clear"\` to clear project cache\n`;
        statusText += `β’ Cache automatically cleans expired entries\n`;
        statusText += `β’ Size and entry limits prevent unlimited growth\n\n`;
        
        if (projectId) {
            statusText += `**Current Project:** \`${projectId}\`\n\n`;
        }
        
        statusText += `π§ Need help? Contact us at support@jaxondigital.com`;
        
        return {
            result: {
                content: [{
                    type: 'text',
                    text: statusText
                }]
            }
        };
        
    } catch (error) {
        OutputLogger.error('Cache status error:', error);
        return {
            error: `Failed to get cache status: ${error.message}\n\nπ§ Need help? Contact us at support@jaxondigital.com`
        };
    }
}
// Handle get_version command
async function handleGetVersion(args) {
    try {
        const packageJson = require('./package.json');
        const currentVersion = packageJson.version;
        
        let versionText = `π¦ **Jaxon Optimizely DXP MCP Server**\n\n`;
        versionText += `**Current Version**: v${currentVersion}\n`;
        versionText += `**Released**: ${packageJson.publishedAt || 'Unknown'}\n\n`;
        
        // Check for updates (with error handling)
        try {
            const versionChecker = require('./lib/version-check');
            const updateInfo = await versionChecker.checkForUpdates();
            
            if (updateInfo && updateInfo.updateAvailable) {
                versionText += `β οΈ **Update Available**: v${updateInfo.latestVersion}\n`;
                versionText += `π
 Released: ${updateInfo.publishedAt || 'Recently'}\n\n`;
                versionText += `**To Update**:\n`;
                versionText += `\`\`\`bash\n`;
                versionText += `npm install -g jaxon-optimizely-dxp-mcp@latest\n`;
                versionText += `\`\`\`\n\n`;
                versionText += `Then restart Claude Desktop or your MCP client.\n`;
            } else if (updateInfo) {
                versionText += `β
 **You are on the latest version!**\n`;
            } else {
                // updateInfo is null, likely due to network issues
                versionText += `βΉοΈ **Update check unavailable** (offline or timeout)\n`;
            }
        } catch (updateError) {
            // If update check fails, just show current version
            OutputLogger.error('Version check error:', updateError);
            versionText += `βΉοΈ **Update check failed** - showing current version only\n`;
        }
        
        versionText += `\n**System Information**:\n`;
        versionText += `β’ Node.js: ${process.version}\n`;
        versionText += `β’ Platform: ${process.platform}\n`;
        versionText += `β’ Architecture: ${process.arch}\n`;
        
        return {
            result: {
                content: [{
                    type: 'text',
                    text: ResponseBuilder.addFooter(versionText)
                }]
            }
        };
    } catch (error) {
        OutputLogger.error('Error in handleGetVersion:', error);
        const errorMessage = ErrorHandler.formatError(error, { tool: 'get_version', args });
        return {
            result: {
                content: [{
                    type: 'text',
                    text: errorMessage
                }]
            }
        };
    }
}
// Command handler map
const commandHandlers = {
    // Simple Commands - Dead Simple with Smart Defaults
    'deploy': (args) => SimpleTools.handleDeploy(args),
    'status': (args) => SimpleTools.handleStatus(args),
    'rollback': (args) => SimpleTools.handleRollback(args),
    'quick': (args) => SimpleTools.handleQuick(args),
    
    // Database Simple Commands - Natural language
    'backup': (args) => DatabaseSimpleTools.handleBackup(args),
    'backup_status': (args) => DatabaseSimpleTools.handleBackupStatus(args),
    'list_backups': (args) => DatabaseSimpleTools.handleListBackups(args),
    'check_download_capabilities': (args) => DatabaseSimpleTools.handleCheckCapabilities(args),
    
    // Project Switching
    'switch_project': (args) => ProjectSwitchTool.handleSwitchProject(args),
    'current_project': (args) => ProjectSwitchTool.handleGetCurrentProject(args),
    
    // Setup & Connection Tools
    'test_connection': (args) => ConnectionTestTools.testConnection(args),
    'health_check': (args) => ConnectionTestTools.healthCheck(args),
    'setup_wizard': (args) => SetupWizard.runSetupWizard(args),
    'get_version': handleGetVersion,
    'get_api_key_info': handleProjectInfo,
    'list_api_keys': (args) => ProjectTools.listProjects(args),
    'get_support': (args) => ProjectTools.handleGetSupport(args),
    'list_monitors': (args) => MonitoringTools.listMonitors(args),
    'update_monitoring_interval': (args) => MonitoringTools.updateMonitoringInterval(args),
    'stop_monitoring': (args) => MonitoringTools.stopMonitoring(args),
    'get_monitoring_stats': (args) => MonitoringTools.getMonitoringStats(args),
    'disable_telemetry': handleDisableTelemetry,
    'enable_telemetry': handleEnableTelemetry,
    'get_rate_limit_status': handleGetRateLimitStatus,
    'get_cache_status': handleGetCacheStatus,
    'export_database': (args) => DatabaseTools.handleExportDatabase(args),
    'check_export_status': (args) => DatabaseTools.handleCheckExportStatus(args),
    'list_deployments': (args) => DeploymentTools.handleListDeployments(args),
    'start_deployment': (args) => DeploymentTools.handleStartDeployment(args),
    'get_deployment_status': (args) => DeploymentTools.handleGetDeploymentStatus(args),
    'complete_deployment': (args) => DeploymentTools.handleCompleteDeployment(args),
    'reset_deployment': (args) => DeploymentTools.handleResetDeployment(args),
    'list_storage_containers': (args) => StorageTools.handleListStorageContainers(args),
    'generate_storage_sas_link': (args) => StorageTools.handleGenerateStorageSasLink(args),
    'upload_deployment_package': (args) => PackageTools.handleUploadDeploymentPackage(args),
    'deploy_package_and_start': (args) => PackageTools.handleDeployPackageAndStart(args),
    'deploy_azure_artifact': (args) => PackageTools.handleDeployAzureArtifact(args),
    'deploy_package_enhanced': (args) => PackageTools.handleDeployPackageEnhanced(args),
    'get_edge_logs': (args) => LoggingTools.handleGetEdgeLogs(args),
    'copy_content': (args) => ContentTools.handleCopyContent(args),
    'analyze_package': (args) => DeploymentHelperTools.handleAnalyzePackage(args),
    'prepare_deployment_package': (args) => DeploymentHelperTools.handlePrepareDeploymentPackage(args),
    'generate_sas_upload_url': (args) => DeploymentHelperTools.handleGenerateSasUploadUrl(args),
    'split_package': (args) => DeploymentHelperTools.handleSplitPackage(args)
};
// Tool definitions
const toolDefinitions = Object.keys(schemas).map(name => {
    const descriptions = {
        // Simple Commands - Dead Simple with Smart Defaults
        'deploy': 'π Deploy to any environment with smart defaults (e.g. "deploy to prod")',
        'status': 'π Intelligent status overview showing what matters right now',
        'rollback': 'π¨ Emergency rollback - one-click safety when things go wrong',
        'quick': 'β‘ Ultra-fast status check - just the essentials',
        
        // Database Simple Commands
        'backup': 'πΎ Create database backup (defaults to production for safety)',
        'backup_status': 'π Check database backup status (defaults to latest backup)',
        'list_backups': 'π List recent database backups with status',
        'check_download_capabilities': 'π§ Check if auto-download is supported in your environment',
        
        // Project Switching
        'switch_project': 'π Switch to a different project (persists for session)',
        'current_project': 'π Show currently active project',
        
        // Setup & Connection Tools
        'test_connection': 'π Test your MCP setup and validate configuration (run this first!)',
        'health_check': 'Quick health check of MCP status (minimal output)',
        'setup_wizard': 'π§ Interactive setup wizard for first-time configuration',
        'get_version': 'Check the current MCP server version and available updates',
        'get_api_key_info': 'Get current Optimizely API key configuration details or register a new API key',
        'list_api_keys': 'List all configured Optimizely API keys',
        'get_support': 'Get comprehensive support information and contact details',
        'list_monitors': 'List active deployment monitors and monitoring statistics',
        'update_monitoring_interval': 'Update the monitoring frequency for active deployment monitors',
        'stop_monitoring': 'Stop monitoring for specific deployments or all active monitors',
        'get_monitoring_stats': 'Get detailed monitoring system statistics and performance metrics',
        'disable_telemetry': 'Disable anonymous telemetry data collection for this session',
        'enable_telemetry': 'Re-enable anonymous telemetry data collection for this session',
        'get_rate_limit_status': 'View current rate limiting status and usage quotas',
        'get_cache_status': 'View cache performance statistics or clear cache entries',
        'export_database': 'Export database from an Optimizely DXP environment (uses configured project)',
        'check_export_status': 'Check the status of a database export',
        'list_deployments': 'List all deployments for the configured project',
        'start_deployment': 'Start deployment between environments. Smart defaults: Upward (IntβPre, PreβProd) deploys CODE; Downward (ProdβPre/Int) copies CONTENT. Override with deploymentType: "code", "content", or "all". Commerce: set sourceApps: ["cms", "commerce"]',
        'get_deployment_status': 'Get the status of a deployment',
        'complete_deployment': 'Complete a deployment that is in Verification state',
        'reset_deployment': 'Reset/rollback a deployment',
        'list_storage_containers': 'List storage containers for an environment (uses configured project)',
        'generate_storage_sas_link': 'Generate SAS link for storage container',
        'upload_deployment_package': 'Upload a deployment package',
        'deploy_package_and_start': 'Deploy a package and start deployment',
        'deploy_azure_artifact': 'π Deploy directly from Azure DevOps build artifacts - perfect for CI/CD integration',
        'deploy_package_enhanced': 'π¦ Enhanced package deployment supporting both local files and Azure DevOps artifacts',
        'get_edge_logs': 'Get edge/CDN logs for entire project (BETA - requires enablement by Optimizely support)',
        'copy_content': 'Copy content between environments (uses configured project)',
        'analyze_package': 'Analyze deployment package size and provide upload recommendations',
        'prepare_deployment_package': 'Prepare optimized deployment package from source directory',
        'generate_sas_upload_url': 'Generate SAS URL for direct package upload (best for large files)',
        'split_package': 'Split large package into smaller chunks for easier upload'
    };
    
    return {
        name,
        description: descriptions[name],
        inputSchema: schemas[name]
    };
});
// Create server instance
const server = new Server(
    {
        name: Config.PROJECT.NAME,
        version: require('./package.json').version
    },
    {
        capabilities: {
            tools: {}
        }
    }
);
// Handle tools/list request
server.setRequestHandler(ListToolsRequestSchema, async () => {
    return {
        tools: toolDefinitions.map(tool => ({
            name: tool.name,
            description: tool.description,
            inputSchema: zodToJsonSchema(tool.inputSchema)
        }))
    };
});
// Handle tools/call request
server.setRequestHandler(CallToolRequestSchema, async (request) => {
    const { name: toolName, arguments: args } = request.params;
    
    // Validate input with Zod schema
    const schema = schemas[toolName];
    if (!schema) {
        throw new Error(`Unknown tool: ${toolName}`);
    }
    
    let validatedArgs;
    try {
        validatedArgs = schema.parse(args);
    } catch (error) {
        return {
            content: [{ 
                type: 'text', 
                text: `β Invalid arguments: ${error.message}\n\nπ§ Need help? Contact us at support@jaxondigital.com` 
            }],
            isError: true
        };
    }
    
    // Auto-register project when credentials are provided inline (BEFORE credential injection)
    // This ensures API keys are saved even when used with get_api_key_info
    if (validatedArgs.projectName && validatedArgs.projectId && 
        validatedArgs.apiKey && validatedArgs.apiSecret) {
        // Check if this is a new project or update
        const existingProjects = ProjectTools.getConfiguredProjects();
        const isNewProject = !existingProjects.find(p => 
            p.id === validatedArgs.projectId || 
            p.name === validatedArgs.projectName
        );
        
        // Add or update the API key configuration
        ProjectTools.addConfiguration({
            name: validatedArgs.projectName,
            id: validatedArgs.projectId,
            apiKey: validatedArgs.apiKey,
            apiSecret: validatedArgs.apiSecret,
            environments: ['Integration', 'Preproduction', 'Production'],
            isDefault: false
        });
        
        // Log registration for debugging (to stderr)
        if (isNewProject) {
            // console.error(`Registered new project: ${validatedArgs.projectName}`);
        } else {
            // console.error(`Updated project: ${validatedArgs.projectName}`);
        }
    }
    
    // Handle project switching and credential injection
    // First check if a project name was provided (for easier switching)
    if (validatedArgs.projectName && !validatedArgs.projectId) {
        const projectCreds = ProjectTools.getProjectCredentials(validatedArgs.projectName);
        if (projectCreds) {
            validatedArgs.projectId = projectCreds.projectId;
            validatedArgs.apiKey = projectCreds.apiKey;
            validatedArgs.apiSecret = projectCreds.apiSecret;
            // Remember this project for subsequent calls in the session
            ProjectTools.setLastUsedProject(validatedArgs.projectName);
        }
    }
    
    // Inject environment credentials if not provided and no inline credentials
    if (!validatedArgs.projectId && !validatedArgs.apiKey && !validatedArgs.apiSecret) {
        const defaultCreds = ProjectTools.getProjectCredentials();
        validatedArgs.projectId = defaultCreds.projectId || process.env.OPTIMIZELY_PROJECT_ID;
        validatedArgs.apiKey = defaultCreds.apiKey || process.env.OPTIMIZELY_API_KEY;
        validatedArgs.apiSecret = defaultCreds.apiSecret || process.env.OPTIMIZELY_API_SECRET;
    }
    
    // If still missing apiKey or apiSecret, try to get from configured projects
    if (!validatedArgs.apiKey || !validatedArgs.apiSecret) {
        const projectCreds = ProjectTools.getProjectCredentials(validatedArgs.projectId);
        if (projectCreds) {
            validatedArgs.apiKey = validatedArgs.apiKey || projectCreds.apiKey;
            validatedArgs.apiSecret = validatedArgs.apiSecret || projectCreds.apiSecret;
        }
    }
    
    // Log which project is being used (to stderr to avoid polluting stdout)
    if (validatedArgs.projectId && toolName !== 'get_api_key_info') {
        // console.error(`Using project: ${validatedArgs.projectId}`);
    }
    
    // Check for missing credentials (except for project management tools)
    if (toolName !== 'get_api_key_info' && toolName !== 'list_api_keys') {
        const missingCreds = [];
        const hasProjectName = !!validatedArgs.projectName;
        if (!validatedArgs.projectId) missingCreds.push('Project ID');
        if (!validatedArgs.apiKey) missingCreds.push('API Key');
        if (!validatedArgs.apiSecret) missingCreds.push('API Secret');
        
        // Only show missing credentials error if we're actually missing required fields
        if (missingCreds.length > 0) {
            // Add project name suggestion if other credentials are missing
            if (!hasProjectName) {
                missingCreds.unshift('Project Name (strongly recommended for easy reference)');
            }
            return {
                content: [{
                    type: 'text',
                    text: `β **Missing Required Credentials**\n\n` +
                          `The following credentials are required but not provided:\n` +
                          missingCreds.map(c => `β’ ${c}`).join('\n') + `\n\n` +
                          `**How to fix this:**\n\n` +
                          `**Option 1:** Pass ALL credentials as parameters to this tool:\n` +
                          `β’ projectName: "Your Project Name" (e.g., "Production", "Staging", "ClientA")\n` +
                          `β’ projectId: "your-uuid"\n` +
                          `β’ apiKey: "your-key"\n` +
                          `β’ apiSecret: "your-secret"\n\n` +
                          `**Why Project Name is Important:**\n` +
                          `Once you provide a project name, the project is auto-registered and you can reference it by name in future commands!\n\n` +
                          `**Option 2:** Configure environment variables in Claude Desktop:\n` +
                          `Run the \`get_api_key_info\` tool for detailed setup instructions.\n\n` +
                          `π‘ **Tip:** Use \`list_api_keys\` to see all registered API key configurations.`
                }],
                isError: true
            };
        }
    }
    
    // Execute tool using handler map
    const startTime = Date.now();
    
    try {
        const handler = commandHandlers[toolName];
        if (!handler) {
            throw new Error(`Tool ${toolName} not implemented`);
        }
        
        // Track tool usage
        telemetry.trackToolUsage(toolName, {
            environment: validatedArgs.environment,
            hasCredentials: !!(validatedArgs.apiKey && validatedArgs.projectId)