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
| Name | Required | Description | Default |
|---|---|---|---|
| sourceEnvironment | Yes | ||
| targetEnvironment | Yes | ||
| projectName | No | ||
| projectId | No | ||
| apiKey | No | ||
| apiSecret | No |
Implementation Reference
- lib/tools/content-tools.ts:72-150 (handler)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); } }
- lib/tools/content-tools.ts:15-25 (schema)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; }
- lib/tools/content-tools.ts:155-223 (helper)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}`); } }
- lib/tools/content-tools.ts:44-47 (helper)Output type for copy_content result, including structured data and formatted message.export interface ContentCopyResult { data: ContentCopyData; message: string; }
- lib/tools/content-tools.ts:30-39 (schema)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; }