Skip to main content
Glama
JaxonDigital

Optimizely DXP MCP Server

by JaxonDigital

copy_content

Copy database and blob content between Optimizely DXP environments to refresh staging/test environments with production data or promote content changes.

Instructions

📋 Copy database and/or blob content between environments. ASYNC: 30-90min depending on content size. Use for refreshing staging/test environments with production data or promoting content changes. Set includeBlob=true to copy static files/media (slower). Set includeDB=true to copy CMS/Commerce databases. CONTENT typically flows downward (Production→Preproduction→Integration). This is a heavy operation - verify target environment before starting. Required: sourceEnvironment, targetEnvironment. Returns operation ID for tracking. Use status() tool to monitor progress.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sourceEnvironmentYes
targetEnvironmentYes
projectNameNo
projectIdNo
apiKeyNo
apiSecretNo

Implementation Reference

  • Main handler for 'copy_content' tool: validates input parameters, checks self-hosted restrictions, verifies permissions for source and target environments, provides helpful access suggestions if insufficient permissions, and delegates to copyContent implementation.
    static async handleCopyContent(args: ContentCopyArgs): Promise<any> {
        // Check if this is a self-hosted project
        if (args.isSelfHosted || args.connectionString) {
            return ResponseBuilder.invalidParams('Content copy is not available for self-hosted projects. Self-hosted projects can only download existing backups and blobs.');
        }
    
        // Validate parameters
        if (!args.apiKey || !args.apiSecret || !args.projectId ||
            !args.sourceEnvironment || !args.targetEnvironment) {
            return ResponseBuilder.invalidParams('Missing required parameters for content copy');
        }
    
        // Validate that source and target are different
        if (args.sourceEnvironment === args.targetEnvironment) {
            return ResponseBuilder.invalidParams('Source and target environments must be different');
        }
    
        // Check permissions for both environments
        const PermissionChecker = require('./permission-checker');
        const projectConfig: ProjectConfig = {
            apiKey: args.apiKey,
            apiSecret: args.apiSecret,
            projectId: args.projectId,
            id: args.projectId,
            name: args.projectName || 'Project'
        };
    
        const permissions = await PermissionChecker.getOrCheckPermissionsSafe(projectConfig);
    
        // Check if user has access to both source and target
        const missingAccess: string[] = [];
        if (!permissions.accessible.includes(args.sourceEnvironment)) {
            missingAccess.push(args.sourceEnvironment);
        }
        if (!permissions.accessible.includes(args.targetEnvironment)) {
            missingAccess.push(args.targetEnvironment);
        }
    
        if (missingAccess.length > 0) {
            let response = `ℹ️ **Access Level Check**\n\n`;
            response += `Content copy requires access to both source and target environments.\n\n`;
            response += `**Requested:** ${args.sourceEnvironment} → ${args.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 alternatives based on what they have access to
            if (permissions.accessible.length >= 2) {
                response += `**Available Content Copy Options:**\n`;
    
                // Show valid copy directions based on their access
                const copyOptions = PermissionChecker.getContentCopyDefaults(permissions);
                if (copyOptions) {
                    response += `• ${copyOptions.description}: \`copy_content sourceEnvironment: "${copyOptions.source}" targetEnvironment: "${copyOptions.target}"\`\n`;
                }
    
                response += `\n💡 **Tip:** Content typically flows from higher to lower environments (Prod→Pre→Int).`;
            } else if (permissions.accessible.length === 1) {
                response += `⚠️ You need access to at least 2 environments to copy content.\n`;
                response += `Your API key only has access to ${permissions.accessible[0]}.`;
            }
    
            return ResponseBuilder.success(response);
        }
    
        try {
            const result = await this.copyContent(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('Content copy error:', error);
            return ResponseBuilder.internalError('Content copy failed', error.message);
        }
    }
  • TypeScript interface defining input parameters for the copy_content tool handler.
    export interface ContentCopyArgs {
        apiKey?: string;
        apiSecret?: string;
        projectId?: string;
        projectName?: string;
        sourceEnvironment?: string;
        targetEnvironment?: string;
        isSelfHosted?: boolean;
        connectionString?: string;
        apiUrl?: string;
    }
  • Core implementation of content copy: initiates a special deployment via DXP REST API with database and blobs included (no code), handles errors, supports custom API URLs.
    static async copyContent(args: ContentCopyArgs): Promise<ContentCopyResult> {
        const { apiKey, apiSecret, projectId, sourceEnvironment, targetEnvironment } = args;
    
        console.error(`Starting content copy from ${sourceEnvironment} to ${targetEnvironment}`);
    
        // DXP-101: Use REST API instead of PowerShell (3-10x faster, no PowerShell dependency)
        // Content copy is essentially a deployment with includeBlob=true and includeDB=true
        try {
            const deploymentParams = {
                sourceEnvironment: sourceEnvironment!,
                targetEnvironment: targetEnvironment!,
                includeBlob: true,  // Include blob storage
                includeDB: true     // Include database
                // Note: No sourceApps = content only (no code deployment)
            };
    
            const options = args.apiUrl ? { apiUrl: args.apiUrl as string } : {};
            const result: DeploymentResponse = await DXPRestClient.startDeployment(
                projectId!,
                apiKey!,
                apiSecret!,
                deploymentParams as any,
                options // Support custom API URLs
            );
    
            // Format and return response
            if (result) {
                return this.formatContentCopyResponse(result, sourceEnvironment!, targetEnvironment!);
            }
    
            return {
                data: {
                    deploymentId: null,
                    sourceEnvironment: sourceEnvironment!,
                    targetEnvironment: targetEnvironment!,
                    type: 'content',
                    status: 'InProgress',
                    includesCode: false,
                    includesDatabase: true,
                    includesBlobs: true
                },
                message: ResponseBuilder.addFooter(
                    `Content copy initiated from ${sourceEnvironment} to ${targetEnvironment}.`,
                    true
                )
            };
    
        } catch (error: any) {
            // Handle REST API errors
            const errorDetails = {
                operation: 'Content Copy',
                projectId,
                sourceEnvironment,
                targetEnvironment,
                error: error.message
            };
    
            // 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'
                } as any, errorDetails) as any;
            }
    
            // Generic error handling
            throw new Error(`Content copy failed: ${error.message}`);
        }
    }
  • Output type for copy_content result, including structured data and formatted message.
    export interface ContentCopyResult {
        data: ContentCopyData;
        message: string;
    }
  • Structured data returned in the copy_content tool response.
    export interface ContentCopyData {
        deploymentId: string | null;
        sourceEnvironment: string;
        targetEnvironment: string;
        type: string;
        status: string;
        includesCode: boolean;
        includesDatabase: boolean;
        includesBlobs: boolean;
    }

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