Skip to main content
Glama

start_deployment

Initiate deployments between Optimizely DXP environments with automated monitoring. Supports code, content, or full deployments with configurable options for files and databases.

Instructions

🚀 Start new deployment from source to target environment. ASYNC: 5-30min. Initiates deployment and auto-monitors progress with real-time updates. CODE deployments flow upward (Integration→Preproduction→Production). CONTENT deployments flow downward (Production→Preproduction→Integration). Returns deploymentId immediately. Set includeBlob=true for static files, includeDB=true for database sync. When status reaches "AwaitingVerification", use get_deployment_status() to get slot URL for testing, then complete_deployment() to finalize. Required: sourceEnvironment, targetEnvironment.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sourceEnvironmentYes
targetEnvironmentYes
deploymentTypeNo
sourceAppsNo
includeBlobNo
includeDatabaseNo
directDeployNo
useMaintenancePageNo
webhookUrlNoHTTP endpoint to receive deployment events (HTTPS required in production)
webhookHeadersNoCustom headers to include in webhook requests (e.g., { "Authorization": "Bearer token" })
projectNameNo
projectIdNo
apiKeyNo
apiSecretNo

Implementation Reference

  • Primary handler for 'start_deployment' tool. Performs permissions check, validates path/type, calls DXP REST API to start deployment, formats response, emits events, registers webhooks/monitoring.
    class DeploymentActionOperations { /** * Start a new deployment */ static async handleStartDeployment(args: StartDeploymentArgs): Promise<any> { // Check if this is a self-hosted project if (args.isSelfHosted || args.connectionString) { return ResponseBuilder.invalidParams('Deployments are 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.startDeployment(args); // Check if result is already a 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('Start deployment error:', error); return ResponseBuilder.internalError('Failed to start deployment', error.message); } } static async startDeployment(args: StartDeploymentArgs): Promise<any> { const { apiKey, apiSecret, projectId, projectName, sourceEnvironment, targetEnvironment, deploymentType, sourceApps, includeBlob, includeDatabase, directDeploy, useMaintenancePage, webhookUrl, webhookHeaders } = args; // DXP-67: Defensive check for useMaintenancePage to prevent accidental production downtime if (useMaintenancePage === true) { console.error('⚠️ WARNING: useMaintenancePage is set to TRUE'); console.error(` This will show a maintenance page during deployment to ${targetEnvironment}`); if (targetEnvironment === 'Production' || targetEnvironment === 'Preproduction') { console.error(' ⚠️ CAUTION: Deploying to Production/Preproduction with maintenance page!'); console.error(' This will cause site downtime for end users.'); } } console.error(`Starting deployment from ${sourceEnvironment} to ${targetEnvironment} for project ${projectId}`); // Check permissions for both environments first const projectConfig: ProjectConfig = { apiKey: apiKey!, apiSecret: apiSecret!, projectId: projectId!, id: projectId!, name: projectName || 'Project' }; const permissions: Permissions = await PermissionChecker.getOrCheckPermissionsSafe(projectConfig); // Check if user has access to both source and target const missingAccess: string[] = []; if (!permissions.accessible.includes(sourceEnvironment!)) { missingAccess.push(sourceEnvironment!); } if (!permissions.accessible.includes(targetEnvironment!)) { missingAccess.push(targetEnvironment!); } if (missingAccess.length > 0) { let response = `ℹ️ **Access Level Check**\n\n`; response += `Deployments require access to both source and target environments.\n\n`; response += `**Requested:** ${sourceEnvironment} → ${targetEnvironment}\n`; response += `**Your access level:** ${permissions.accessible.join(', ')} environment${permissions.accessible.length > 1 ? 's' : ''}\n`; response += `**Additional access needed:** ${missingAccess.join(', ')}\n\n`; // Suggest valid deployment paths based on what they have access to if (permissions.accessible.length >= 2) { response += `**Available Deployment Options:**\n\n`; // Check for valid deployment paths const hasInt = permissions.accessible.includes('Integration'); const hasPre = permissions.accessible.includes('Preproduction'); const hasProd = permissions.accessible.includes('Production'); if (hasInt && hasPre) { response += `• **Integration → Preproduction** (Code deployment)\n`; response += ` \`start_deployment sourceEnvironment: "Integration" targetEnvironment: "Preproduction"\`\n\n`; } if (hasPre && hasProd) { response += `• **Preproduction → Production** (Code deployment)\n`; response += ` \`start_deployment sourceEnvironment: "Preproduction" targetEnvironment: "Production"\`\n\n`; } // For content copy (if they have the environments but trying wrong direction) if (hasProd && hasPre) { response += `• **Production → Preproduction** (Content copy - use copy_content instead)\n`; response += ` \`copy_content sourceEnvironment: "Production" targetEnvironment: "Preproduction"\`\n\n`; } if (hasProd && hasInt) { response += `• **Production → Integration** (Content copy - use copy_content instead)\n`; response += ` \`copy_content sourceEnvironment: "Production" targetEnvironment: "Integration"\`\n\n`; } response += `\n💡 **Important:** Code deployments only work upward (Int→Pre→Prod).\n`; response += `For downward content sync, use the \`copy_content\` tool instead.`; } else if (permissions.accessible.length === 1) { response += `⚠️ You need access to at least 2 environments for deployments.\n`; response += `Your API key only has access to ${permissions.accessible[0]}.`; } // Return as structured response return { data: { error: 'insufficient_permissions', missingAccess: missingAccess, availableEnvironments: permissions.accessible }, message: response }; } // Check for any running deployments before starting a new one console.error('Checking for active deployments...'); try { const DeploymentListOperations = require('./deployment-list'); const listResult = await DeploymentListOperations.listDeployments({ apiKey, apiSecret, projectId, limit: 5, offset: 0 }); // DEBUG: Log the actual result to troubleshoot blocking issue console.error('DEBUG: listResult type:', typeof listResult); console.error('DEBUG: listResult (first 500 chars):', typeof listResult === 'string' ? listResult.substring(0, 500) : JSON.stringify(listResult).substring(0, 500)); // Check if there's a deployment in progress // CRITICAL FIX (v3.17.2): First check if this is an error response, not deployment data // Previously, error messages containing "InProgress" text would falsely trigger // the "deployment already in progress" check, preventing all new deployments if (listResult && typeof listResult === 'string') { // Skip check if this is an error message const isError = listResult.includes('❌') || listResult.includes('Error') || listResult.includes('Failed') || listResult.includes('Invalid') || listResult.includes('Forbidden') || listResult.includes('support@jaxondigital.com'); console.error('DEBUG: isError check result:', isError); if (isError) { // Log error but continue - we can't check deployment status but shouldn't block console.error('DEBUG: Detected error in deployment list, continuing with deployment...'); console.error('Warning: Could not check for active deployments due to error:', listResult.substring(0, 200)); } else { // Only check for in-progress deployments if this is actual deployment data console.error('DEBUG: Checking for in-progress deployments in actual deployment data...'); // FIXED: Don't use 🔄 emoji as it appears in all deployments (even completed ones) // Only look for actual status indicators of in-progress deployments const hasInProgress = listResult.includes('InProgress') || listResult.includes('Deploying') || listResult.includes('Status: **InProgress**') || listResult.includes('Status: **Deploying**'); console.error('DEBUG: hasInProgress result:', hasInProgress); console.error('DEBUG: Contains InProgress:', listResult.includes('InProgress')); console.error('DEBUG: Contains Deploying:', listResult.includes('Deploying')); console.error('DEBUG: Contains Status: **InProgress**:', listResult.includes('Status: **InProgress**')); console.error('DEBUG: Contains Status: **Deploying**:', listResult.includes('Status: **Deploying**')); if (hasInProgress) { console.error('DEBUG: Found in-progress deployment, blocking new deployment...'); // Extract details about the in-progress deployment if possible const lines = listResult.split('\n'); let inProgressDetails = ''; for (let i = 0; i < lines.length; i++) { if (lines[i].includes('InProgress') || lines[i].includes('🔄')) { // Try to get the deployment ID and environments const deploymentIdMatch = lines[i].match(/#([a-f0-9-]+)/); if (deploymentIdMatch) { inProgressDetails = `Deployment ${deploymentIdMatch[1]} `; } // Look for environment info in nearby lines for (let j = Math.max(0, i-2); j < Math.min(lines.length, i+3); j++) { if (lines[j].includes('→')) { const envMatch = lines[j].match(/(\w+)\s*→\s*(\w+)/); if (envMatch) { inProgressDetails += `(${envMatch[1]} → ${envMatch[2]})`; break; } } } break; } } return `⚠️ **Deployment Already In Progress**\n\n` + `Cannot start a new deployment while another is running.\n\n` + (inProgressDetails ? `**Active Deployment:** ${inProgressDetails}\n\n` : '') + `Please wait for the current deployment to complete or reset it before starting a new one.\n\n` + `**Options:**\n` + `• Use \`get_deployment_status\` to check progress\n` + `• Use \`reset_deployment\` if the deployment is stuck\n` + `• Wait for automatic completion (usually 10-30 minutes)`; } } } } catch (checkError: any) { // Don't fail the deployment if we can't check status console.error('Warning: Could not check for active deployments:', checkError.message); } // Validate deployment path const pathValidation: PathValidation = DeploymentValidator.validateDeploymentPath(sourceEnvironment!, targetEnvironment!); if (!pathValidation.valid) { // Check if this is a downward deployment that should use content copy if (sourceEnvironment === 'Production' || (sourceEnvironment === 'Preproduction' && targetEnvironment === 'Integration')) { let response = `ℹ️ **Invalid Deployment Direction**\n\n`; response += `You're trying to deploy from **${sourceEnvironment}** to **${targetEnvironment}**.\n\n`; response += `❌ **Code deployments can only go upward:**\n`; response += `• Integration → Preproduction\n`; response += `• Preproduction → Production\n\n`; response += `✅ **For downward content synchronization, use:**\n`; response += `\`copy_content sourceEnvironment: "${sourceEnvironment}" targetEnvironment: "${targetEnvironment}"\`\n\n`; response += `💡 **Why?** Code changes should flow through proper testing stages (Int→Pre→Prod),\n`; response += `while content can be copied from production back to lower environments for testing.`; // Return as structured response return { data: { error: 'invalid_deployment_direction', sourceEnvironment: sourceEnvironment, targetEnvironment: targetEnvironment, suggestion: 'use_copy_content' }, message: response }; } // For other invalid paths (like Int→Prod), show the standard error return ResponseBuilder.error( `❌ Invalid Deployment Path\n\n${pathValidation.error}\n\n💡 ${pathValidation.suggestion}` ); } // Show warnings if any if (pathValidation.warnings && pathValidation.warnings.length > 0) { let warningMsg = '⚠️ **Deployment Warnings:**\n\n'; pathValidation.warnings.forEach(warn => { warningMsg += `${warn.message}\n`; if (warn.suggestion) { warningMsg += ` 💡 ${warn.suggestion}\n`; } warningMsg += '\n'; }); console.error(warningMsg); } // Validate deployment parameters const paramValidation: ParamValidation = DeploymentValidator.validateDeploymentParams(args); if (!paramValidation.valid) { return ResponseBuilder.error( `❌ Invalid Parameters\n\n${paramValidation.errors!.join('\n')}` ); } // Use sanitized parameters const sanitizedArgs = paramValidation.sanitized; // Check deployment timing const timingCheck: TimingCheck = DeploymentValidator.validateDeploymentTiming({ targetEnvironment }); if (timingCheck.warnings && timingCheck.warnings.length > 0) { timingCheck.warnings.forEach(warn => { console.error(`Timing warning: ${warn.message}`); }); } // Determine if this is upward (code) or downward (content) deployment const isUpward = pathValidation.isUpward; // Apply smart defaults based on deployment direction let deployCode = false; let deployContent = false; if (sanitizedArgs.deploymentType) { // User specified deployment type if (sanitizedArgs.deploymentType === 'code') { deployCode = true; } else if (sanitizedArgs.deploymentType === 'content') { deployContent = true; } else if (sanitizedArgs.deploymentType === 'all') { deployCode = true; deployContent = true; } } else { // Apply smart defaults if (isUpward) { deployCode = true; // Code flows up console.error('Defaulting to CODE deployment (upward flow)'); } else { deployContent = true; // Content flows down console.error('Defaulting to CONTENT deployment (downward flow)'); } } // DXP-101: Build deployment parameters for REST API (replacing PowerShell) // Based on EpiCloud.psm1 Start-EpiDeployment SourceEnvironment parameter set const deploymentParams: DeploymentParams = { sourceEnvironment: sourceEnvironment!, // Lowercase per PowerShell source targetEnvironment: targetEnvironment! // Lowercase per PowerShell source }; // Add deployment type parameters if (deployCode) { // SourceApp is required for code deployments const appsToUse = sourceApps && sourceApps.length > 0 ? sourceApps : ['cms']; // Default to CMS app deploymentParams.sourceApps = appsToUse; // Plural 'sourceApps' per PowerShell source console.error(`Deploying code with apps: ${appsToUse.join(', ')}`); } if (deployContent) { // Add content deployment flags - always include (per PowerShell source) deploymentParams.includeBlob = includeBlob !== false; deploymentParams.includeDB = includeDatabase !== false; console.error(`Deploying content with includeBlob=${deploymentParams.includeBlob}, includeDB=${deploymentParams.includeDB}`); } // Add optional parameters - only if explicitly set to true if (directDeploy === true) { deploymentParams.directDeploy = true; // Lowercase per PowerShell source } if (useMaintenancePage === true) { deploymentParams.maintenancePage = true; // 'maintenancePage' not 'UseMaintenancePage' per PowerShell source } console.error(`Starting deployment via REST API with payload:`); console.error(JSON.stringify(deploymentParams, null, 2)); // DXP-101: Use REST API instead of PowerShell (3-10x faster, no PowerShell dependency) try { const result: DeploymentResult = await DXPRestClient.startDeployment( projectId!, apiKey!, apiSecret!, deploymentParams as any, { apiUrl: args.apiUrl } // Support custom API URLs ); // Format response if (result) { const formatted = DeploymentFormatters.formatDeploymentStarted(result, args); // Extract deployment ID from result and start monitoring if (result.id) { // DXP-136: Emit deployment started event try { DeploymentResourceHandler.emitStarted(result.id, { project: projectName, environment: targetEnvironment, sourceEnvironment: sourceEnvironment, targetEnvironment: targetEnvironment, deploymentType: deploymentType, status: result.status }); } catch (eventError: any) { console.error(`Failed to emit deployment event: ${eventError.message}`); // Don't fail the deployment if event emission fails } // DXP-140: Register webhook if provided if (webhookUrl) { try { const webhookManager = getGlobalWebhookManager(); const registrationResult = webhookManager.register( result.id, // operationId (deploymentId) webhookUrl, { headers: webhookHeaders || {}, project: projectName, environment: targetEnvironment } ); const logger = new StructuredLogger({ context: { tool: 'start_deployment', deployment_id: result.id, project: projectName, environment: targetEnvironment } }); if (registrationResult.success) { logger.info('Webhook registered for deployment', { webhook_url: webhookUrl, deployment_id: result.id }); console.log(`🔔 Webhook registered for deployment ${result.id}`); } else { logger.warn('Webhook registration failed', { error: registrationResult.error, deployment_id: result.id }); console.log(`⚠️ Webhook registration failed: ${registrationResult.error}`); } } catch (webhookError: any) { console.error(`Failed to register webhook: ${webhookError.message}`); // Don't fail the deployment if webhook registration fails } } try { const monitor = getGlobalMonitor(); monitor.startMonitoring({ deploymentId: result.id, projectId: args.projectId!, apiKey: args.apiKey!, apiSecret: args.apiSecret!, interval: 60 * 1000 // 1 minute default }); const logger = new StructuredLogger({ context: { tool: 'start_deployment', deployment_id: result.id } }); logger.info('Auto-monitoring started for deployment', { deployment_id: result.id, interval_ms: 60 * 1000 }); console.log(`🔄 Auto-monitoring started for deployment ${result.id}`); } catch (monitorError: any) { console.error(`Failed to start monitoring: ${monitorError.message}`); // Don't fail the deployment if monitoring fails } } // Return structured response with both data and message return formatted; } return { data: null, message: ResponseBuilder.addFooter('Deployment started but no details available') }; } catch (error: any) { // Handle REST API errors const errorDetails = { operation: 'Start Deployment', projectId, projectName: args.projectName, sourceEnvironment, targetEnvironment, 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); } }
  • Input schema/arguments type for start_deployment tool.
    interface StartDeploymentArgs { apiKey?: string; apiSecret?: string; projectId?: string; projectName?: string; sourceEnvironment?: string; targetEnvironment?: string; deploymentType?: string; sourceApps?: string[]; includeBlob?: boolean; includeDatabase?: boolean; directDeploy?: boolean; useMaintenancePage?: boolean; isSelfHosted?: boolean; connectionString?: string; apiUrl?: string; webhookUrl?: string; webhookHeaders?: Record<string, string>; }
  • Public DeploymentTools.handleStartDeployment - entrypoint delegated to action operations.
    static async handleStartDeployment(args: any): Promise<any> { return DeploymentActionOperations.handleStartDeployment(args);
  • Helper formatter for deployment started response used by handler.
    static formatDeploymentStarted(deployment: Deployment, args: StartDeploymentArgs): StructuredResult { 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: any) => 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; } } // Determine deployment type let deploymentType = args.deploymentType; if (!deploymentType) { // Apply smart defaults const isUpward = this.isUpwardDeployment(args.sourceEnvironment, args.targetEnvironment); deploymentType = isUpward ? 'code' : 'content'; } const needsVerification = args.targetEnvironment === 'Production' && !args.directDeploy; const previewUrl = needsVerification ? this.getPreviewUrl(args.targetEnvironment!, projectId || null) : null; // Build structured data for automation tools const structuredData = { deploymentId: deployment.id, status: deployment.status || 'InProgress', sourceEnvironment: args.sourceEnvironment, targetEnvironment: args.targetEnvironment, deploymentType: deploymentType, projectId: projectId, projectName: projectName, startTime: deployment.startTime || new Date().toISOString(), percentComplete: deployment.percentComplete || 0, needsVerification: needsVerification, previewUrl: previewUrl, sourceApps: args.sourceApps || [], includeBlob: args.includeBlob, includeDatabase: args.includeDatabase, directDeploy: args.directDeploy, useMaintenancePage: args.useMaintenancePage }; // Build human-readable message let message = `${STATUS_ICONS.SUCCESS} **Deployment Started`; if (projectName) { message += ` - ${projectName}**\n\n`; } else { message += `**\n\n`; } message += `**Deployment ID**: ${deployment.id}\n`; message += `**From**: ${args.sourceEnvironment}\n`; message += `**To**: ${args.targetEnvironment}\n`; message += `**Type**: ${deploymentType!.charAt(0).toUpperCase() + deploymentType!.slice(1)}`; if (deploymentType === 'code' && args.sourceApps) { message += ` (${args.sourceApps.join(', ')})`; } message += '\n'; if (deployment.status) { message += `**Status**: ${deployment.status}\n`; } // Always show preview URL for deployments that will need verification if (needsVerification) { if (previewUrl) { message += `\n**🔗 Preview URL (Slot)**: ${previewUrl}\n`; message += `_Your deployment will be available for preview at this slot URL once it enters verification state_\n`; } else { message += `\n**🔗 Verification URL Information**:\n`; message += `• When the deployment reaches verification state, the slot URL will be available in the DXP portal\n`; message += `• Expected format: https://[your-site-name]-slot.dxcloud.episerver.net/\n`; message += `• You'll need to check the DXP portal deployment details for the exact URL\n`; } } message += '\n## 🎯 **Monitoring Options**:\n'; message += `### Option 1: **Continuous Monitoring** (Recommended)\n`; message += `Use \`monitor_deployment\` to automatically check progress every 30 seconds:\n`; message += `\`\`\`\nmonitor_deployment({ deploymentId: "${deployment.id}" })\n\`\`\`\n`; message += `### Option 2: **Manual Status Checks**\n`; message += `Use \`get_deployment_status\` to check progress on demand:\n`; message += `\`\`\`\nget_deployment_status({ deploymentId: "${deployment.id}" })\n\`\`\`\n`; message += '\n**💡 Important**: Use the MCP monitoring tools above instead of bash loops.\n'; message += 'The \`monitor_deployment\` tool provides intelligent progress tracking and automatic notifications.\n'; if (needsVerification) { message += '\n**📋 Deployment Stages**:\n'; message += '1. **In Progress** - Deployment is running\n'; message += '2. **Verification** - Review changes at preview URL\n'; message += '3. **Complete** - After you run `complete_deployment`\n'; } message = ResponseBuilder.addFooter(message); // Return both structured data and message return { data: structuredData, message: message }; }
  • Tool listed in no-cache operations and invalidation map, indicating registration awareness.
    'start_deployment', 'complete_deployment',

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