Skip to main content
Glama
deployment-list.ts16.8 kB
/** * Deployment List Operations * Handles listing and status checking for deployments * Part of Jaxon Digital Optimizely DXP MCP Server */ import { ResponseBuilder, ErrorHandler } from '../../index'; import DeploymentFormatters from './deployment-formatters'; import DXPRestClient from '../../dxp-rest-client'; /** * List deployments arguments */ interface ListDeploymentsArgs { apiKey?: string; apiSecret?: string; projectId?: string; projectName?: string; limit?: number; offset?: number; activeOnly?: boolean; status?: string; environmentSlot?: string; format?: 'concise' | 'detailed'; isSelfHosted?: boolean; connectionString?: string; apiUrl?: string; } /** * Get deployment status arguments */ interface GetDeploymentStatusArgs { apiKey?: string; apiSecret?: string; projectId?: string; projectName?: string; deploymentId?: string; limit?: number; waitBeforeCheck?: number; monitor?: boolean; isNewDeployment?: boolean; apiUrl?: string; } /** * Deployment API response */ interface DeploymentResponse { Status?: string; PercentComplete?: number; [key: string]: any; } /** * Structured result with data and message */ // interface StructuredResult { // data: any; // message: string; // } class DeploymentListOperations { /** * List all deployments */ static async handleListDeployments(args: ListDeploymentsArgs): Promise<any> { // Check if this is a self-hosted project if (args.isSelfHosted || args.connectionString) { return ResponseBuilder.invalidParams('Deployment listing is not available for self-hosted projects. Self-hosted projects can only download existing backups and blobs.'); } if (!args.apiKey || !args.apiSecret || !args.projectId) { return ResponseBuilder.invalidParams('Missing required parameters'); } try { const result = await this.listDeployments(args); // DXP-66: Check if result is structured response with data and message if (result && typeof result === 'object' && 'data' in result && 'message' in result) { return ResponseBuilder.successWithStructuredData(result.data, result.message); } // Fallback for legacy string responses return ResponseBuilder.success(result); } catch (error: any) { console.error('List deployments error:', error); return ResponseBuilder.internalError('Failed to list deployments', error.message); } } static async listDeployments(args: ListDeploymentsArgs): Promise<any> { const { apiKey, apiSecret, projectId, limit, activeOnly = false, status, environmentSlot, format = 'detailed' } = args; // DXP-101: Use REST API instead of PowerShell (3-10x faster, no PowerShell dependency) try { // Get deployments directly from REST API const deployments: DeploymentResponse | DeploymentResponse[] = await DXPRestClient.getDeployments( projectId!, apiKey!, apiSecret!, null, // No specific deployment ID - get all { apiUrl: args.apiUrl } // Support custom API URLs for Optimizely internal team ); // Ensure we have an array let deploymentList: DeploymentResponse[] = Array.isArray(deployments) ? deployments : [deployments]; // DXP-103: Filter to active deployments only if requested if (activeOnly) { const activeStatuses = ['InProgress', 'AwaitingVerification', 'Resetting', 'Completing']; deploymentList = deploymentList.filter(dep => dep.Status && activeStatuses.includes(dep.Status) ); } // DXP-76-1: Filter by specific status if (status) { deploymentList = deploymentList.filter(dep => dep.Status === status); } // DXP-76-1: Filter by environment slot if (environmentSlot) { deploymentList = deploymentList.filter(dep => (dep as any).parameters?.environmentSlot === environmentSlot || (dep as any).TargetEnvironment === environmentSlot ); } // DXP-76-1: Format response based on format parameter if (format === 'concise') { // Concise format - return minimal fields for token efficiency const conciseList = deploymentList.map(dep => ({ id: (dep as any).Id || (dep as any).id, status: dep.Status, environment: (dep as any).parameters?.environmentSlot || (dep as any).TargetEnvironment, startTime: (dep as any).StartTime || (dep as any).Created, percentComplete: dep.PercentComplete || 0 })); // Apply limit if specified const limited = limit ? conciseList.slice(0, limit) : conciseList; return { data: { deployments: limited, count: limited.length }, message: ResponseBuilder.addFooter(`Found ${limited.length} deployment(s)`) }; } // Detailed format - use existing formatter if (deploymentList.length > 0) { return DeploymentFormatters.formatDeploymentList(deploymentList as any, projectId!, limit, args.projectName); } return ResponseBuilder.addFooter(activeOnly ? 'No active deployments found' : 'No deployments found'); } catch (error: any) { // Handle REST API errors const errorDetails = { operation: 'List Deployments', projectId, projectName: args.projectName, apiKey }; // Check if this is an access denied error if (error.statusCode === 401 || error.statusCode === 403) { return ErrorHandler.formatError({ type: 'ACCESS_DENIED', message: 'Access denied to deployment API', statusCode: error.statusCode } as any, errorDetails); } // Generic error handling return ErrorHandler.formatError({ type: 'API_ERROR', message: error.message, statusCode: error.statusCode } as any, errorDetails); } } /** * Get deployment status */ static async handleGetDeploymentStatus(args: GetDeploymentStatusArgs): Promise<any> { if (!args.apiKey || !args.apiSecret || !args.projectId) { return ResponseBuilder.invalidParams('Missing required parameters'); } const { waitBeforeCheck = 0, monitor = false } = args; // Implement transparent wait-then-check pattern like database exports if (waitBeforeCheck > 0) { const { StructuredLogger } = require('../../structured-logger'); const logger = new StructuredLogger({ context: { tool: 'wait_for_deployment', deployment_id: args.deploymentId } }); const waitMinutes = Math.floor(waitBeforeCheck / 60); const waitSeconds = waitBeforeCheck % 60; const waitDisplay = waitMinutes > 0 ? `${waitMinutes} minute${waitMinutes > 1 ? 's' : ''}${waitSeconds > 0 ? ` ${waitSeconds} second${waitSeconds > 1 ? 's' : ''}` : ''}` : `${waitSeconds} second${waitSeconds > 1 ? 's' : ''}`; logger.info('Waiting before checking deployment status', { wait_seconds: waitBeforeCheck, deployment_id: args.deploymentId }); console.log(`⏳ Waiting ${waitDisplay} before checking deployment status...`); await new Promise(resolve => setTimeout(resolve, waitBeforeCheck * 1000)); logger.info('Wait complete, checking deployment status', { deployment_id: args.deploymentId }); console.log(`✅ Wait complete. Checking deployment status now...`); } try { const result = await this.getDeploymentStatus(args); // Check if result is already a structured response with data and message if (result && typeof result === 'object' && 'data' in result && 'message' in result) { // Add monitoring instructions if monitor mode is enabled if (monitor && result.data && result.data.status) { const monitoringInstructions = this.generateMonitoringInstructions( args.deploymentId!, result.data.status, result.data.percentComplete || 0, args ); result.message = result.message + '\n\n' + monitoringInstructions; } return ResponseBuilder.successWithStructuredData(result.data, result.message); } // Fallback for legacy string responses return ResponseBuilder.success(result); } catch (error: any) { console.error('Get deployment status error:', error); return ResponseBuilder.internalError('Failed to get deployment status', error.message); } } static async getDeploymentStatus(args: GetDeploymentStatusArgs): Promise<any> { const { apiKey, apiSecret, projectId, deploymentId, limit } = args; // Check if this is a newly created deployment (within last 30 seconds) // If the caller indicates this is a new deployment, add an initial delay if (args.isNewDeployment) { // Wait 3 seconds before first check to allow deployment to register await new Promise(resolve => setTimeout(resolve, 3000)); } // DXP-101: Use REST API instead of PowerShell (3-10x faster, no PowerShell dependency) try { // Get deployment(s) directly from REST API const result: DeploymentResponse | DeploymentResponse[] = await DXPRestClient.getDeployments( projectId!, apiKey!, apiSecret!, deploymentId || null, // Pass deployment ID if checking specific deployment, null for all { apiUrl: args.apiUrl } // Support custom API URLs ); // Format response based on whether we got single or multiple deployments if (Array.isArray(result)) { return DeploymentFormatters.formatMultipleDeployments(result as any, limit); } else if (result) { return DeploymentFormatters.formatSingleDeployment(result as any, args.projectName); } return { data: { deployments: [] }, message: ResponseBuilder.addFooter('No deployment data available') }; } catch (error: any) { // Handle REST API errors const errorDetails = { operation: 'Get Deployment Status', projectId, projectName: args.projectName, deploymentId, apiKey }; // Check if this is an access denied error if (error.statusCode === 401 || error.statusCode === 403) { return ErrorHandler.formatError({ type: 'ACCESS_DENIED', message: 'Access denied to deployment API', statusCode: error.statusCode } as any, errorDetails); } // Generic error handling return ErrorHandler.formatError({ type: 'API_ERROR', message: error.message, statusCode: error.statusCode } as any, errorDetails); } } /** * Generate transparent monitoring instructions for AI * Similar to database export monitoring pattern */ static generateMonitoringInstructions( deploymentId: string, status: string, percentComplete: number, args: GetDeploymentStatusArgs ): string { let instructions = ''; // Determine if deployment is still in progress // DXP-71: Added 'Completing' as transitional state const inProgressStatuses = ['InProgress', 'Resetting', 'Completing']; const awaitingVerificationStatuses = ['AwaitingVerification']; const completeStatuses = ['Succeeded', 'Failed', 'Reset']; if (completeStatuses.includes(status)) { // Deployment is complete - no more monitoring needed instructions += `---\n\n`; instructions += `✅ **Deployment Complete**\n\n`; instructions += `Final Status: **${status}**\n`; instructions += `Progress: **${percentComplete}%**\n\n`; if (status === 'AwaitingVerification') { instructions += `🔍 **Next Steps:**\n`; instructions += `• Review the deployment at verification URLs\n`; instructions += `• Use \`complete_deployment\` when ready to finalize\n`; instructions += `• Use \`reset_deployment\` to rollback if issues found\n`; } else if (status === 'Succeeded') { instructions += `🎉 Deployment completed successfully! No further action needed.\n`; } else if (status === 'Failed') { instructions += `⚠️ Deployment failed. Review logs and consider using \`reset_deployment\` to rollback.\n`; } instructions += `\n**AI: Stop monitoring - deployment is complete.**`; } else if (awaitingVerificationStatuses.includes(status)) { // Deployment awaiting verification instructions += `---\n\n`; instructions += `🔍 **Deployment Awaiting Verification**\n\n`; instructions += `The deployment has completed and is ready for review.\n\n`; instructions += `**Next Steps:**\n`; instructions += `1. Review the deployment at verification URLs\n`; instructions += `2. Use \`complete_deployment\` to finalize if everything looks good\n`; instructions += `3. Use \`reset_deployment\` to rollback if issues are found\n\n`; instructions += `**AI: Stop monitoring - deployment awaits user verification.**`; } else if (inProgressStatuses.includes(status)) { // Deployment still in progress - provide monitoring instructions instructions += `---\n\n`; instructions += `📊 **MONITORING INSTRUCTIONS FOR AI**\n\n`; instructions += `Deployment still in progress.\n`; instructions += `Deployment ID: ${deploymentId}\n`; instructions += `Current Status: ${status}\n`; instructions += `Progress: ${percentComplete}%\n\n`; // Use 30-second interval for deployments (faster than exports) const monitorInterval = 30; // 30 seconds instructions += `**To continue monitoring, use get_deployment_status with:**\n`; instructions += `• deploymentId: "${deploymentId}"\n`; instructions += `• waitBeforeCheck: ${monitorInterval} (waits ${monitorInterval} seconds then checks)\n`; instructions += `• monitor: true (enables monitoring prompts)\n`; instructions += `• projectName: "${args.projectName || ''}"\n`; instructions += `\n`; instructions += `**RECOMMENDED MONITORING PATTERN:**\n`; instructions += `1. Call get_deployment_status with:\n`; instructions += ` - deploymentId="${deploymentId}"\n`; instructions += ` - waitBeforeCheck=${monitorInterval}\n`; instructions += ` - monitor=true\n`; instructions += `2. If still InProgress, repeat with same interval\n`; instructions += `3. When AwaitingVerification, prompt user to verify\n`; instructions += `4. When Succeeded/Failed, stop monitoring\n\n`; instructions += `**Note:** Tool will wait ${monitorInterval} seconds then check automatically.\n`; instructions += `Deployments can take 30-90 minutes depending on complexity and size. Be patient and don't raise concerns unless status is Failed or stuck for several hours.`; } return instructions; } } export default DeploymentListOperations;

Implementation Reference

Latest Blog Posts

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