Skip to main content
Glama

Optimizely DXP MCP Server

by JaxonDigital
deployment-formatters.js30.9 kB
/** * Deployment Formatters * Handles formatting of deployment responses * Part of Jaxon Digital Optimizely DXP MCP Server */ const { Config, ResponseBuilder } = require('../../index'); class DeploymentFormatters { /** * Format date/time in user's local timezone with timezone name */ static formatLocalDateTime(dateInput) { if (!dateInput) return 'N/A'; const date = new Date(dateInput); if (isNaN(date.getTime())) return 'Invalid date'; // Get the user's timezone const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; // Format date and time portions separately const dateOptions = { year: 'numeric', month: 'short', day: 'numeric' }; const timeOptions = { hour: '2-digit', minute: '2-digit', timeZoneName: 'short' }; const datePart = date.toLocaleDateString('en-US', dateOptions); const timePart = date.toLocaleTimeString('en-US', timeOptions); // Format as: Aug 6, 2025 (12:26 PM CDT) return `${datePart} (${timePart})`; } /** * Format duration between two dates */ static formatDuration(startDate, endDate) { const start = new Date(startDate); const end = new Date(endDate); const duration = end - start; if (duration < 0) return 'Invalid duration'; const minutes = Math.floor(duration / 60000); const seconds = Math.floor((duration % 60000) / 1000); if (minutes > 60) { const hours = Math.floor(minutes / 60); const mins = minutes % 60; return `${hours}h ${mins}m`; } return minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`; } /** * Format a list of deployments */ static formatDeploymentList(deployments, projectId = null, limit = null, projectName = null) { const { FORMATTING: { STATUS_ICONS } } = Config; // Get project info from configured projects if not provided if (!projectId || !projectName) { try { const ProjectTools = require('../project-tools'); const projects = ProjectTools.getConfiguredProjects(); if (projects && projects.length > 0) { const defaultProject = projects.find(p => p.isDefault) || projects[0]; projectId = projectId || defaultProject.id; projectName = projectName || defaultProject.name; } } catch (error) { // Fall back to environment variables if ProjectTools fails projectName = projectName || process.env.OPTIMIZELY_PROJECT_NAME; projectId = projectId || process.env.OPTIMIZELY_PROJECT_ID; } } let response = `${STATUS_ICONS.DEPLOY} **Recent Deployments`; if (projectName) { response += ` - ${projectName}**\n\n`; } else if (projectId) { response += ` - ${projectId}**\n\n`; } else { response += `**\n\n`; } if (Array.isArray(deployments) && deployments.length > 0) { // Sort by start time (most recent first) const sorted = deployments.sort((a, b) => { const dateA = new Date(a.startTime || a.created || 0); const dateB = new Date(b.startTime || b.created || 0); return dateB - dateA; }); // Apply limit if specified, otherwise show up to 5 for performance const displayLimit = limit || 5; const recentDeployments = sorted.slice(0, displayLimit); recentDeployments.forEach((deployment, index) => { const status = deployment.status || 'Unknown'; let statusIcon = STATUS_ICONS.IN_PROGRESS; if (status.toLowerCase().includes('success') || status.toLowerCase().includes('completed')) { statusIcon = STATUS_ICONS.SUCCESS; } else if (status.toLowerCase().includes('fail')) { statusIcon = STATUS_ICONS.ERROR; } else if (status.toLowerCase().includes('reset')) { statusIcon = STATUS_ICONS.WARNING; } else if (status.toLowerCase().includes('verification')) { statusIcon = STATUS_ICONS.VERIFICATION; } response += `${statusIcon} **Deployment #${deployment.id}**\n`; // Get environment names - check direct fields first, then parameters // For package uploads, there's no source environment const sourceEnv = deployment.startEnvironment || deployment.parameters?.sourceEnvironment || null; const targetEnv = deployment.endEnvironment || deployment.parameters?.targetEnvironment || 'Unknown'; const isPackageUpload = deployment.parameters?.packages && deployment.parameters?.packages.length > 0; // Format deployment path - handle package uploads differently if (isPackageUpload && !sourceEnv) { const packageName = deployment.parameters.packages[0]; response += `• Package: ${packageName} → ${targetEnv}\n`; } else if (!sourceEnv || sourceEnv === 'Unknown') { response += `• To: ${targetEnv} (Package Upload)\n`; } else { response += `• From: ${sourceEnv} → To: ${targetEnv}\n`; } response += `• Status: **${status}**`; // Add progress for in-progress deployments if (deployment.percentComplete !== undefined && deployment.percentComplete < 100) { response += ` (${deployment.percentComplete}%)`; } response += '\n'; if (deployment.startTime) { response += `• Started: ${this.formatLocalDateTime(deployment.startTime)}`; // Add duration for completed deployments if (deployment.endTime) { const duration = this.formatDuration(deployment.startTime, deployment.endTime); response += ` (Duration: ${duration})`; } response += '\n'; } // Show deployment type if available and limit is small if (displayLimit <= 5 && deployment.parameters) { if (deployment.parameters.sourceApps && deployment.parameters.sourceApps.length > 0) { response += `• Apps: ${deployment.parameters.sourceApps.join(', ')}`; // Add flags for blob/db const flags = []; if (deployment.parameters.includeBlob) flags.push('Blob'); if (deployment.parameters.includeDb) flags.push('DB'); if (flags.length > 0) { response += ` (+${flags.join(', ')})`; } response += '\n'; } } // Show errors/warnings if present if (deployment.deploymentErrors && deployment.deploymentErrors.length > 0) { response += `• **❌ ${deployment.deploymentErrors.length} Error(s)**\n`; } if (deployment.deploymentWarnings && deployment.deploymentWarnings.length > 0) { response += `• **⚠️ ${deployment.deploymentWarnings.length} Warning(s)**\n`; } // Show preview URL for deployments awaiting verification if (status.toLowerCase().includes('verification')) { const previewUrl = this.getPreviewUrl(targetEnv, projectId); if (previewUrl) { if (targetEnv === 'Production') { response += `• **Preview URL (Slot)**: ${previewUrl}\n`; } else { response += `• **Preview URL**: ${previewUrl}\n`; } } else if (targetEnv === 'Production') { response += `• **Verification URL**: The slot URL will be provided in the DXP portal\n`; response += `• **Expected Format**: https://[your-site-name]-slot.dxcloud.episerver.net/\n`; response += `• **Note**: Check the DXP portal deployment details for the exact URL\n`; } } response += '\n'; }); if (deployments.length > displayLimit) { response += `_Showing ${displayLimit} most recent deployments out of ${deployments.length} total_\n`; } } else { response += 'No deployments found.\n'; } return ResponseBuilder.addFooter(response); } /** * Format a single deployment */ static formatSingleDeployment(deployment, projectName = null) { const { FORMATTING: { STATUS_ICONS } } = Config; // Get project info from configured projects if not provided let projectId = null; if (!projectName) { try { const ProjectTools = require('../project-tools'); const projects = ProjectTools.getConfiguredProjects(); if (projects && projects.length > 0) { const defaultProject = projects.find(p => p.isDefault) || projects[0]; projectId = defaultProject.id; projectName = projectName || defaultProject.name; } } catch (error) { // Fall back to environment variables if ProjectTools fails projectId = process.env.OPTIMIZELY_PROJECT_ID; projectName = projectName || process.env.OPTIMIZELY_PROJECT_NAME; } } let response = `${STATUS_ICONS.DEPLOY} **Deployment Details`; if (projectName) { response += ` - ${projectName}**\n\n`; } else { response += `**\n\n`; } const status = deployment.status || 'Unknown'; let statusIcon = STATUS_ICONS.IN_PROGRESS; if (status.toLowerCase().includes('success') || status.toLowerCase().includes('completed')) { statusIcon = STATUS_ICONS.SUCCESS; } else if (status.toLowerCase().includes('fail')) { statusIcon = STATUS_ICONS.ERROR; } else if (status.toLowerCase().includes('reset')) { statusIcon = STATUS_ICONS.WARNING; } else if (status.toLowerCase().includes('verification')) { statusIcon = STATUS_ICONS.VERIFICATION; } response += `${statusIcon} **Deployment #${deployment.id}**\n\n`; // Get environment names - check direct fields first, then parameters const sourceEnv = deployment.startEnvironment || deployment.parameters?.sourceEnvironment || null; const targetEnv = deployment.endEnvironment || deployment.parameters?.targetEnvironment || 'Unknown'; const isPackageUpload = deployment.parameters?.packages && deployment.parameters?.packages.length > 0; response += `**Status**: ${status}\n`; if (isPackageUpload && !sourceEnv) { response += `**Package**: ${deployment.parameters.packages[0]}\n`; response += `**To**: ${targetEnv}\n`; response += `**Type**: Package Upload\n`; } else if (!sourceEnv || sourceEnv === 'Unknown') { response += `**To**: ${targetEnv}\n`; response += `**Type**: Package Upload\n`; } else { response += `**From**: ${sourceEnv}\n`; response += `**To**: ${targetEnv}\n`; } // Show progress if available if (deployment.percentComplete !== undefined) { response += `**Progress**: ${deployment.percentComplete}%\n`; } // Always show preview URL for deployments awaiting verification if (status.toLowerCase().includes('verification')) { const previewUrl = this.getPreviewUrl(targetEnv, projectId); if (previewUrl) { if (targetEnv === 'Production') { response += `\n**🔗 Preview URL (Slot)**: ${previewUrl}\n`; response += `_Review your changes in the deployment slot before completing_\n`; } else { response += `\n**🔗 Preview URL**: ${previewUrl}\n`; response += `_Review your changes at the preview URL above_\n`; } } else if (targetEnv === 'Production') { response += `\n**🔗 Verification Slot URL**\n`; response += `• The verification URL will be available in the DXP portal\n`; response += `• Expected format: https://[your-site-name]-slot.dxcloud.episerver.net/\n`; response += `• Navigate to the deployment in DXP portal to get the exact URL\n`; } } // Timing information response += '\n**📅 Timeline**:\n'; if (deployment.startTime) { response += `• Started: ${this.formatLocalDateTime(deployment.startTime)}\n`; } if (deployment.endTime) { response += `• Ended: ${this.formatLocalDateTime(deployment.endTime)}\n`; // Calculate duration if both times are available if (deployment.startTime) { const duration = this.formatDuration(deployment.startTime, deployment.endTime); response += `• Duration: ${duration}\n`; } } // Deployment configuration details if (deployment.parameters) { response += '\n**⚙️ Configuration**:\n'; if (deployment.parameters.sourceApps && deployment.parameters.sourceApps.length > 0) { response += `• Apps: ${deployment.parameters.sourceApps.join(', ')}\n`; } if (deployment.parameters.includeBlob !== undefined) { response += `• Include Blob: ${deployment.parameters.includeBlob ? 'Yes' : 'No'}\n`; } if (deployment.parameters.includeDb !== undefined) { response += `• Include Database: ${deployment.parameters.includeDb ? 'Yes' : 'No'}\n`; } if (deployment.parameters.maintenancePage !== undefined) { response += `• Maintenance Page: ${deployment.parameters.maintenancePage ? 'Enabled' : 'Disabled'}\n`; } if (deployment.parameters.zeroDowntimeMode && deployment.parameters.zeroDowntimeMode !== 'NotApplicable') { response += `• Zero Downtime Mode: ${deployment.parameters.zeroDowntimeMode}\n`; } // Reset parameters if this was a rollback if (deployment.parameters.resetParameters) { response += '\n**🔄 Reset Configuration**:\n'; const reset = deployment.parameters.resetParameters; if (reset.resetWithDbRollback !== undefined) { response += `• Database Rollback: ${reset.resetWithDbRollback ? 'Yes' : 'No'}\n`; } if (reset.validateBeforeSwap !== undefined) { response += `• Validate Before Swap: ${reset.validateBeforeSwap ? 'Yes' : 'No'}\n`; } if (reset.complete !== undefined) { response += `• Auto-Complete: ${reset.complete ? 'Yes' : 'No'}\n`; } } } // Validation links if available if (deployment.validationLinks && deployment.validationLinks.length > 0) { response += '\n**🔗 Validation Links**:\n'; deployment.validationLinks.forEach(link => { response += `• ${link}\n`; }); } // Warnings if (deployment.deploymentWarnings && deployment.deploymentWarnings.length > 0) { response += '\n**⚠️ Warnings**:\n'; deployment.deploymentWarnings.forEach(warning => { response += `• ${warning}\n`; }); } // Errors if (deployment.deploymentErrors && deployment.deploymentErrors.length > 0) { response += '\n**❌ Errors**:\n'; deployment.deploymentErrors.forEach(err => { response += `• ${err}\n`; }); } // Validation messages (legacy field) if (deployment.validationMessages && deployment.validationMessages.length > 0) { response += '\n**Validation Messages**:\n'; deployment.validationMessages.forEach(msg => { response += `• ${msg}\n`; }); } // Add action hint for verification state if (status.toLowerCase().includes('verification')) { response += '\n**Next Actions**:\n'; response += '• Review changes at the preview URL\n'; response += '• Use `complete_deployment` to finalize\n'; response += '• Use `reset_deployment` to rollback\n'; } return ResponseBuilder.addFooter(response); } /** * Format multiple deployments with optional limit */ static formatMultipleDeployments(deployments, limit) { const { FORMATTING: { STATUS_ICONS } } = Config; // Get project info from configured projects let projectId = null; let projectName = null; try { const ProjectTools = require('../project-tools'); const projects = ProjectTools.getConfiguredProjects(); if (projects && projects.length > 0) { const defaultProject = projects.find(p => p.isDefault) || projects[0]; projectId = defaultProject.id; projectName = defaultProject.name; } } catch (error) { // Fall back to environment variables if ProjectTools fails projectId = process.env.OPTIMIZELY_PROJECT_ID; } // Ensure deployments is an array const deploymentArray = Array.isArray(deployments) ? deployments : [deployments]; // Sort by start time (most recent first) const sorted = deploymentArray.sort((a, b) => { const dateA = new Date(a.startTime || a.created || 0); const dateB = new Date(b.startTime || b.created || 0); return dateB - dateA; }); // Apply limit if specified const toShow = limit ? sorted.slice(0, limit) : sorted; let response = `${STATUS_ICONS.DEPLOY} **Deployment Status**\n\n`; toShow.forEach((deployment) => { const status = deployment.status || 'Unknown'; let statusIcon = STATUS_ICONS.IN_PROGRESS; if (status.toLowerCase().includes('success') || status.toLowerCase().includes('completed')) { statusIcon = STATUS_ICONS.SUCCESS; } else if (status.toLowerCase().includes('fail')) { statusIcon = STATUS_ICONS.ERROR; } else if (status.toLowerCase().includes('reset')) { statusIcon = STATUS_ICONS.WARNING; } else if (status.toLowerCase().includes('verification')) { statusIcon = STATUS_ICONS.VERIFICATION; } response += `${statusIcon} **Deployment #${deployment.id}**\n`; response += `• Status: **${status}**\n`; const sourceEnv = deployment.startEnvironment || deployment.parameters?.sourceEnvironment || null; const targetEnv = deployment.endEnvironment || deployment.parameters?.targetEnvironment || 'N/A'; if (!sourceEnv || sourceEnv === 'Unknown' || sourceEnv === 'N/A') { response += `• To: ${targetEnv} (Package Upload)\n`; } else { response += `• From: ${sourceEnv} → To: ${targetEnv}\n`; } // Always show preview URL for deployments awaiting verification if (status.toLowerCase().includes('verification')) { const previewUrl = this.getPreviewUrl(deployment.endEnvironment, projectId); if (previewUrl) { response += `• **Preview URL**: ${previewUrl}\n`; } } if (deployment.startTime) { response += `• Started: ${this.formatLocalDateTime(deployment.startTime)}\n`; } response += '\n'; }); if (limit && deploymentArray.length > limit) { response += `_Showing ${limit} most recent deployments out of ${deploymentArray.length} total_\n`; } return ResponseBuilder.addFooter(response); } /** * Format deployment started response */ static formatDeploymentStarted(deployment, args) { const { FORMATTING: { STATUS_ICONS } } = Config; // Get project info from args, configured projects, or environment variables let projectId = args.projectId; let projectName = args.projectName; if (!projectId || !projectName) { try { const ProjectTools = require('../project-tools'); const projects = ProjectTools.getConfiguredProjects(); if (projects && projects.length > 0) { const defaultProject = projects.find(p => p.isDefault) || projects[0]; projectId = projectId || defaultProject.id; projectName = projectName || defaultProject.name; } } catch (error) { // Fall back to environment variables if ProjectTools fails projectId = projectId || process.env.OPTIMIZELY_PROJECT_ID; projectName = projectName || process.env.OPTIMIZELY_PROJECT_NAME; } } let response = `${STATUS_ICONS.SUCCESS} **Deployment Started`; if (projectName) { response += ` - ${projectName}**\n\n`; } else { response += `**\n\n`; } response += `**Deployment ID**: ${deployment.id}\n`; response += `**From**: ${args.sourceEnvironment}\n`; response += `**To**: ${args.targetEnvironment}\n`; // Show what's being deployed let deploymentType = args.deploymentType; if (!deploymentType) { // Apply smart defaults const isUpward = this.isUpwardDeployment(args.sourceEnvironment, args.targetEnvironment); deploymentType = isUpward ? 'code' : 'content'; } response += `**Type**: ${deploymentType.charAt(0).toUpperCase() + deploymentType.slice(1)}`; if (deploymentType === 'code' && args.sourceApps) { response += ` (${args.sourceApps.join(', ')})`; } response += '\n'; if (deployment.status) { response += `**Status**: ${deployment.status}\n`; } // Always show preview URL for deployments that will need verification const needsVerification = args.targetEnvironment === 'Production' && !args.directDeploy; if (needsVerification) { const previewUrl = this.getPreviewUrl(args.targetEnvironment, projectId); if (previewUrl) { response += `\n**🔗 Preview URL (Slot)**: ${previewUrl}\n`; response += `_Your deployment will be available for preview at this slot URL once it enters verification state_\n`; } else { response += `\n**🔗 Verification URL Information**:\n`; response += `• When the deployment reaches verification state, the slot URL will be available in the DXP portal\n`; response += `• Expected format: https://[your-site-name]-slot.dxcloud.episerver.net/\n`; response += `• You'll need to check the DXP portal deployment details for the exact URL\n`; } } response += '\n**Next Steps**:\n'; response += `• Use \`get_deployment_status\` with deployment ID **${deployment.id}** to check progress\n`; if (needsVerification) { response += '• Once in Verification state, review your changes at the preview URL\n'; response += '• Use `complete_deployment` to finalize or `reset_deployment` to rollback\n'; } return ResponseBuilder.addFooter(response); } /** * Format deployment completed response */ static formatDeploymentCompleted(deployment, projectName = null) { const { FORMATTING: { STATUS_ICONS } } = Config; projectName = projectName || process.env.OPTIMIZELY_PROJECT_NAME; let response = `${STATUS_ICONS.SUCCESS} **Deployment Completed Successfully`; if (projectName) { response += ` - ${projectName}**\n\n`; } else { response += `**\n\n`; } response += `**Deployment ID**: ${deployment.id}\n`; if (deployment.status) { response += `**Final Status**: ${deployment.status}\n`; } if (deployment.completionTime) { response += `**Completed At**: ${this.formatLocalDateTime(deployment.completionTime)}\n`; } // Check if we have environment information to provide accurate messaging const targetEnv = deployment.endEnvironment || deployment.targetEnvironment; if (targetEnv === 'Production' || targetEnv?.toLowerCase() === 'production') { response += '\n✅ **Deployment Completed**\n\n'; response += 'The deployment has been successfully promoted from the verification slot to production.\n'; response += 'Your changes are now live on the production environment.'; } else if (targetEnv) { response += `\n✅ The deployment to **${targetEnv}** has been completed successfully.`; } else { // Generic message when we don't know the environment response += '\n✅ The deployment has been completed successfully.\n\n'; response += '**Note**: If this was a Production deployment, changes are now live after being promoted from the verification slot.'; } return ResponseBuilder.addFooter(response); } /** * Format deployment reset response */ static formatDeploymentReset(deployment, includeDbRollback, projectName = null) { const { FORMATTING: { STATUS_ICONS } } = Config; projectName = projectName || process.env.OPTIMIZELY_PROJECT_NAME; let response = `${STATUS_ICONS.WARNING} **Deployment Reset Initiated`; if (projectName) { response += ` - ${projectName}**\n\n`; } else { response += `**\n\n`; } response += `**Deployment ID**: ${deployment.id}\n`; if (deployment.status) { response += `**Status**: ${deployment.status || 'Resetting'}\n`; } // Get deployment details if available if (deployment.parameters) { const source = deployment.parameters.sourceEnvironment; const target = deployment.parameters.targetEnvironment; if (source && target) { response += `**Original Deployment**: ${source} → ${target}\n`; } } response += '\n🔄 **Reset in Progress**\n'; response += 'The deployment is being rolled back. This typically takes 2-5 minutes.\n'; if (includeDbRollback) { response += '\n⚠️ **Database Rollback**: Database changes are also being reverted.\n'; } response += '\n📊 **I\'ll monitor the reset progress and notify you when it\'s complete.**'; response += '\n\nYou can continue working while the reset completes. I\'ll update you with:'; response += '\n• Reset completion status'; response += '\n• Environment restoration confirmation'; response += '\n• Any errors or issues encountered'; return ResponseBuilder.addFooter(response); } /** * Helper to check if deployment is upward (code flow) */ static isUpwardDeployment(source, target) { const envOrder = ['Integration', 'Preproduction', 'Production']; const sourceIndex = envOrder.indexOf(source); const targetIndex = envOrder.indexOf(target); return targetIndex > sourceIndex; } /** * Get preview URL for an environment */ static getPreviewUrl(environment, projectId) { if (!projectId) return null; // Remove any @ prefix from project ID if present const cleanProjectId = projectId.replace(/^@/, ''); // For Production deployments in verification, we cannot generate the slot URL // The actual URL needs to come from the DXP API response if (environment === 'Production') { // The slot URL format is: https://{short-name}-slot.dxcloud.episerver.net/ // But we cannot determine the short name from the project ID alone // This should be retrieved from the deployment API response return null; } const envMap = { 'Integration': 'integration', 'Preproduction': 'preproduction' }; const envSlug = envMap[environment]; if (!envSlug) return null; // For Int/Pre environments, use standard environment URL return `https://${cleanProjectId}.${envSlug}.dxp.optimizely.com/`; } } module.exports = DeploymentFormatters;

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/JaxonDigital/optimizely-dxp-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server