Skip to main content
Glama
JaxonDigital

Optimizely DXP MCP Server

by JaxonDigital

update_project

Modify Optimizely DXP project configuration settings including credentials, name, and environment parameters to maintain operational continuity across deployment environments.

Instructions

✏️ Update project configuration settings. INSTANT: <1s. Modifies stored credentials, project name, or environment settings. Changes persist for session. Use with caution - invalid credentials will break subsequent operations. Required: projectName, updates (object with fields to change). Returns updated project config. Test with test_connection() after updating.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
projectNameNo
projectIdNo
renameToNo
apiKeyNo
apiSecretNo
connectionStringNo
blobPathNo
dbPathNo
logPathNo
makeDefaultNoSet this project as default for getCurrentProject() - auto-enabled when providing inline credentials

Implementation Reference

  • Core handler for the 'update_project' tool. Updates project configuration including renaming, credentials (API key/secret or connection string), paths (blob/db/log), and settings (make default). Handles project type conversions (DXP PaaS ↔ Self-Hosted ↔ Unknown). Uses dynamic in-memory configuration storage.
    static updateProject(args: UpdateProjectArgs): any {
        const {
            projectName,
            projectId,
            // Rename
            renameTo,
            // Credentials
            apiKey,
            apiSecret,
            connectionString,
            // Paths
            blobPath,
            dbPath,
            logPath,
            // Settings
            makeDefault
        } = args;
    
        // Find the project to update
        const projects = this.getConfiguredProjects();
        const project = projects.find(p =>
            p.name === projectName ||
            p.name?.toLowerCase() === projectName?.toLowerCase() ||
            p.projectId === projectId
        );
    
        if (!project) {
            // If no existing project, create new one if we have enough info
            if (projectName && (apiKey || connectionString)) {
                return this.getProjectInfo(args as GetProjectArgs); // Use existing creation logic
            }
    
            return ResponseBuilder.formatResponse({
                success: false,
                message: `Project '${projectName || projectId}' not found`,
                details: 'Specify an existing project to update or provide credentials to create a new one'
            });
        }
    
        // Build updated configuration
        const updatedConfig: ProjectConfig = { ...project };
        let changes: string[] = [];
    
        // Handle rename
        if (renameTo && renameTo !== project.name) {
            updatedConfig.name = renameTo;
            updatedConfig.originalName = project.name;
            changes.push(`Renamed from '${project.name}' to '${renameTo}'`);
        }
    
        // Handle credentials update
        if (apiKey && apiKey !== project.apiKey) {
            updatedConfig.apiKey = apiKey;
            changes.push('Updated API key');
            // If providing API credentials, this becomes a DXP project
            if (apiSecret) {
                if (project.projectType === 'self-hosted' || project.isSelfHosted) {
                    changes.push('Converted from Self-Hosted to DXP PaaS');
                }
                updatedConfig.projectType = 'dxp-paas';
                updatedConfig.isSelfHosted = false;
                delete updatedConfig.connectionString;
                updatedConfig.environments = ['Integration', 'Preproduction', 'Production'];
            }
        }
        if (apiSecret && apiSecret !== project.apiSecret) {
            updatedConfig.apiSecret = apiSecret;
            changes.push('Updated API secret');
            // If providing API credentials, this becomes a DXP project
            if (apiKey || updatedConfig.apiKey) {
                if (project.projectType === 'self-hosted' || project.isSelfHosted) {
                    changes.push('Converted from Self-Hosted to DXP PaaS');
                }
                updatedConfig.projectType = 'dxp-paas';
                updatedConfig.isSelfHosted = false;
                delete updatedConfig.connectionString;
                updatedConfig.environments = ['Integration', 'Preproduction', 'Production'];
            }
        }
        if (connectionString && connectionString !== project.connectionString) {
            updatedConfig.connectionString = connectionString;
            if (project.projectType === 'dxp-paas' || (!project.isSelfHosted && project.apiKey)) {
                changes.push('Converted from DXP PaaS to Self-Hosted');
            }
            updatedConfig.isSelfHosted = true;
            updatedConfig.projectType = 'self-hosted';
            updatedConfig.environments = ['Production'];
            // Remove DXP-specific fields
            delete updatedConfig.apiKey;
            delete updatedConfig.apiSecret;
            changes.push('Updated connection string');
        }
    
        // Handle project ID update (for DXP projects)
        if (projectId && projectId !== project.projectId && !project.projectId.startsWith('unknown-')) {
            updatedConfig.projectId = projectId;
            changes.push('Updated project ID');
        }
    
        // Handle paths
        if (blobPath && blobPath !== project.blobPath) {
            updatedConfig.blobPath = blobPath;
            changes.push('Updated blob path');
        }
        if (dbPath && dbPath !== project.dbPath) {
            updatedConfig.dbPath = dbPath;
            changes.push('Updated database path');
        }
        if (logPath && logPath !== project.logPath) {
            updatedConfig.logPath = logPath;
            changes.push('Updated log path');
        }
    
        // Handle default setting
        if (makeDefault) {
            updatedConfig.isDefault = true;
            // Remove default from other projects
            this.dynamicConfigurations.forEach(c => {
                if (c.name !== updatedConfig.name) {
                    c.isDefault = false;
                }
            });
            // Set as last used project for getCurrentProject() resolution
            this.setLastUsedProject(updatedConfig.name);
            changes.push('Set as default project');
        }
    
        // If project was Unknown and now has credentials, mark as configured
        if (project.isUnknown && (apiKey || connectionString)) {
            updatedConfig.isUnknown = false;
            updatedConfig.needsConfiguration = false;
            delete updatedConfig.configurationHint;
            if (apiKey) {
                updatedConfig.projectType = 'dxp-paas';
                changes.push('Upgraded from Unknown to DXP PaaS');
            } else if (connectionString) {
                updatedConfig.projectType = 'self-hosted';
                changes.push('Upgraded from Unknown to Self-Hosted');
            }
        }
    
        if (changes.length === 0) {
            return ResponseBuilder.formatResponse({
                success: true,
                message: 'No changes to apply',
                details: `Project '${project.name}' is already up to date`
            });
        }
    
        // Update timestamps
        updatedConfig.configSource = 'dynamic';
        updatedConfig.lastUpdated = new Date().toISOString();
    
        // Save the configuration
        this.addConfiguration(updatedConfig);
    
        // DXP-148 FIX: When credentials are provided inline, treat as implicit makeDefault
        // This ensures the most recently added/updated project becomes the default
        // Critical for multi-project n8n workflows where projects are added dynamically
        if ((apiKey && apiSecret) || connectionString) {
            this.setLastUsedProject(updatedConfig.name);
            OutputLogger.debug(`[DXP-148] Set '${updatedConfig.name}' as last used project (inline credentials provided)`);
        }
    
        // Build response
        const sections: string[] = [];
        sections.push(`✅ **Project Updated Successfully**`);
        sections.push('');
        sections.push(`**${updatedConfig.name}**`);
        sections.push(`Type: ${updatedConfig.projectType === 'self-hosted' ? 'Self-Hosted' : 'DXP PaaS'}`);
        sections.push('');
        sections.push('**Changes Applied:**');
        changes.forEach(change => sections.push(`• ${change}`));
    
        if (updatedConfig.blobPath || updatedConfig.dbPath || updatedConfig.logPath) {
            sections.push('');
            sections.push('**Download Paths:**');
            if (updatedConfig.blobPath) sections.push(`• Blobs: ${updatedConfig.blobPath}`);
            if (updatedConfig.dbPath) sections.push(`• Database: ${updatedConfig.dbPath}`);
            if (updatedConfig.logPath) sections.push(`• Logs: ${updatedConfig.logPath}`);
        }
    
        return ResponseBuilder.success(sections.join('\n'));
    }
  • Type definition for input parameters to the update_project tool handler.
    interface UpdateProjectArgs {
        projectName?: string;
        projectId?: string;
        renameTo?: string;
        apiKey?: string;
        apiSecret?: string;
        connectionString?: string;
        blobPath?: string;
        dbPath?: string;
        logPath?: string;
        makeDefault?: boolean;
    }
  • Tool availability registration in the central matrix used for hosting-type based access control.
    'update_project': {
        hostingTypes: ['dxp-paas', 'dxp-saas', 'self-hosted', 'unknown'],
        category: 'Project Management',
        description: 'Update project configuration: rename, credentials, paths, or settings'
    },
  • Key helper method that parses environment variables and dynamic configs into ProjectConfig objects, used by update_project for finding and updating projects.
    static getConfiguredProjects(): ProjectConfig[] {
        // Dynamic configurations are kept in memory only for current session
    
        const projects: ProjectConfig[] = [];
        const configErrors: ConfigError[] = [];
    
        // Check ALL environment variables for our specific format
        // Any env var with format: "id=uuid;key=value;secret=value" is treated as an API key configuration
        // Examples:
        //   ACME="id=uuid;key=value;secret=value"
        //   PRODUCTION="id=uuid;key=value;secret=value"
        //   CLIENT_A_STAGING="id=uuid;key=value;secret=value"
    
        // DEBUG: Log all environment variables that contain our format
        const relevantEnvVars = Object.keys(process.env).filter(key => {
            const value = process.env[key];
            return value && typeof value === 'string' &&
                   ((value.includes('id=') && value.includes('key=') && value.includes('secret=')) ||
                    value.startsWith('DefaultEndpointsProtocol=') ||
                    ((value.includes('blobPath=') || value.includes('logPath=')) &&
                     !value.includes('id=') && !value.includes('key=') && !value.includes('secret=')));
        });
        OutputLogger.debug(`Checking environment variables...`);
        OutputLogger.debug(`Found ${relevantEnvVars.length} relevant env vars (DXP, self-hosted, and unknown):`, relevantEnvVars);
    
        Object.keys(process.env).forEach(key => {
            const value = process.env[key];
    
            // Skip if not a string
            if (typeof value !== 'string') {
                return;
            }
    
            // Check if empty string (placeholder project)
            // Only treat as project if name looks like a project name
            if (value === '') {
                // Skip if this doesn't look like a project name
                // Project names typically follow patterns like:
                // - "ACME-int", "CONTOSO-prod", "FABRIKAM-staging" (project-environment)
                // - "DEMO-self", "TEST-local" (project-type)
                // - Single short words like "zilch", "test", "demo"
    
                const hasProjectPattern =
                    // Pattern: WORD-env (like ACME-int, CONTOSO-prod)
                    key.match(/^[A-Z0-9]+-(?:int|prod|staging|test|dev|qa|uat|demo|local|self)$/i) ||
                    // Pattern: Short single word (max 10 chars)
                    (key.match(/^[A-Z0-9]+$/i) && key.length <= 10) ||
                    // Explicitly starts with common project prefixes
                    key.match(/^(?:TEST|DEMO|DEV|PROD|STAGING|QA)[-_]/i);
    
                if (!hasProjectPattern) {
                    return;
                }
    
                // Create Unknown placeholder project
                const projectName = key.replace(/_/g, ' ');
                const projectConfig: ProjectConfig = {
                    name: projectName,
                    projectId: `unknown-${projectName.toLowerCase().replace(/\s+/g, '-')}`,
                    isUnknown: true,
                    projectType: 'unknown',
                    needsConfiguration: true,
                    configurationHint: 'Empty project - add connectionString for self-hosted or id/key/secret for DXP',
                    environments: ['Unknown'],
                    configSource: 'environment'
                };
                projects.push(projectConfig);
                return;
            }
    
            // Check if this looks like our API key format OR a connection string OR self-hosted paths
            // Must contain either:
            // 1. DXP format: (id=, key=, secret=)
            // 2. Azure connection string: (DefaultEndpointsProtocol=)
            // 3. Self-hosted with paths only: (blobPath= or logPath=) but no id/key/secret
            const hasDxpFormat = value.includes('id=') && value.includes('key=') && value.includes('secret=');
            const hasConnectionString = value.startsWith('DefaultEndpointsProtocol=');
            const hasSelfHostedPaths = (value.includes('blobPath=') || value.includes('logPath=')) &&
                                       !value.includes('id=') && !value.includes('key=') && !value.includes('secret=');
    
            const hasCorrectFormat = hasDxpFormat || hasConnectionString || hasSelfHostedPaths;
    
            if (!hasCorrectFormat) {
                return;
            }
    
            // Use the environment variable name as the project name (underscores become spaces)
            const projectName = key.replace(/_/g, ' ');
    
            try {
                // Check if this is a raw Azure connection string (self-hosted mode)
                if (value.startsWith('DefaultEndpointsProtocol=')) {
                    // Extract connection string and any additional parameters
                    // Format: DefaultEndpointsProtocol=...;EndpointSuffix=core.windows.net;blobPath=/path;logPath=/path
    
                    // Find where the connection string ends (after EndpointSuffix)
                    const endpointMatch = value.match(/EndpointSuffix=[^;]+/);
                    let connectionString = value;
                    let additionalParams: Record<string, string> = {};
    
                    if (endpointMatch) {
                        const endIndex = value.indexOf(endpointMatch[0]) + endpointMatch[0].length;
                        connectionString = value.substring(0, endIndex);
    
                        // Parse any additional parameters after the connection string
                        const remaining = value.substring(endIndex);
                        if (remaining) {
                            const extraParts = remaining.split(';').filter(p => p.trim());
                            extraParts.forEach(part => {
                                const [key, val] = part.split('=');
                                if (key && val) {
                                    additionalParams[key] = val;
                                }
                            });
                        }
                    }
    
                    const projectConfig: ProjectConfig = {
                        name: projectName,
                        projectId: `self-hosted-${projectName.toLowerCase().replace(/\s+/g, '-')}`,
                        apiKey: '',
                        apiSecret: '',
                        connectionString: connectionString,
                        isSelfHosted: true,
                        environments: ['Production'], // Self-hosted typically has one environment
                        configSource: 'environment'
                    };
    
                    // Add optional paths if provided
                    if (additionalParams.blobPath) {
                        projectConfig.blobPath = additionalParams.blobPath;
                    }
                    if (additionalParams.logPath) {
                        projectConfig.logPath = additionalParams.logPath;
                    }
                    if (additionalParams.dbPath) {
                        projectConfig.dbPath = additionalParams.dbPath;
                    }
    
                    projects.push(projectConfig);
                    return;
                }
    
                // Otherwise parse semicolon-separated key=value pairs for DXP projects
                const params: Record<string, string> = {};
                const parts = value.split(';').filter(p => p.trim());
    
                if (parts.length === 0) {
                    // Empty configuration - treat as Unknown project placeholder
                    const projectConfig: ProjectConfig = {
                        name: projectName,
                        projectId: `unknown-${projectName.toLowerCase().replace(/\s+/g, '-')}`,
                        isUnknown: true,
                        projectType: 'unknown',
                        needsConfiguration: true,
                        configurationHint: 'Empty project - add connectionString for self-hosted or id/key/secret for DXP',
                        environments: ['Unknown'],
                        configSource: 'environment'
                    };
                    projects.push(projectConfig);
                    return;
                }
    
                parts.forEach(param => {
                    const equalIndex = param.indexOf('=');
                    if (equalIndex === -1) {
                        configErrors.push({
                            project: projectName,
                            error: `Invalid parameter format: "${param}" (expected key=value)`,
                            variable: key
                        });
                        return;
                    }
    
                    const paramKey = param.substring(0, equalIndex).trim();
                    const paramValue = param.substring(equalIndex + 1).trim();
    
                    if (!paramKey || !paramValue) {
                        configErrors.push({
                            project: projectName,
                            error: `Empty key or value in parameter: "${param}"`,
                            variable: key
                        });
                        return;
                    }
    
                    params[paramKey] = paramValue;
                });
    
                // Extract credentials using standard format
                let projectId = params.id;  // Changed to let to allow reassignment
                const apiKey = params.key;
                const apiSecret = params.secret;
                let connectionString = params.connectionString || params.connStr;
    
                // Check if this is a path-only project (has paths but no DXP credentials or connection string)
                const isPathOnlyProject = (params.blobPath || params.logPath || params.dbPath) &&
                                          !params.id && !params.key && !params.secret && !connectionString;
    
                // Determine project type and handle accordingly
                if (connectionString) {
                    // Self-hosted mode with connection string
                    if (!projectId) {
                        // Generate a simple ID from the project name
                        const projectIdBase = projectName.toLowerCase().replace(/\s+/g, '-');
                        projectId = `self-hosted-${projectIdBase}`;
                    }
                } else if (isPathOnlyProject) {
                    // Unknown type - has paths but no clear indication of type
                    if (!projectId) {
                        // Generate a simple ID from the project name
                        const projectIdBase = projectName.toLowerCase().replace(/\s+/g, '-');
                        projectId = `unknown-${projectIdBase}`;
                    }
                    OutputLogger.debug(`Unknown project type "${projectName}" configured with paths only`);
                } else if (!params.id || !params.key || !params.secret) {
                    // DXP mode - needs full API credentials
                    const missingFields: string[] = [];
                    if (!projectId) missingFields.push('id');
                    if (!apiKey) missingFields.push('key');
                    if (!apiSecret) missingFields.push('secret');
    
                    if (missingFields.length > 0) {
                        configErrors.push({
                            project: projectName,
                            error: `Missing required fields: ${missingFields.join(', ')}`,
                            variable: key,
                            hint: `Format: "id=<uuid>;key=<key>;secret=<secret>"`
                        });
                        return;
                    }
                }
    
                // Validate UUID format for project ID (skip for self-hosted and unknown)
                const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
                const isSelfHostedId = projectId && projectId.startsWith('self-hosted-');
                const isUnknownId = projectId && projectId.startsWith('unknown-');
                if (!isSelfHostedId && !isUnknownId && !uuidRegex.test(projectId!)) {
                    configErrors.push({
                        project: projectName,
                        error: `Invalid project ID format: "${projectId}"`,
                        variable: key,
                        hint: `Project ID should be a UUID like: abc12345-1234-5678-9abc-def123456789`
                    });
                }
    
                // Validate environments if specified
                if (params.environments) {
                    const validEnvs = ['Integration', 'Preproduction', 'Production'];
                    const envs = params.environments.split(',').map(e => e.trim());
                    const invalidEnvs = envs.filter(e => !validEnvs.includes(e));
    
                    if (invalidEnvs.length > 0) {
                        configErrors.push({
                            project: projectName,
                            error: `Invalid environments: ${invalidEnvs.join(', ')}`,
                            variable: key,
                            hint: `Valid environments are: Integration, Preproduction, Production`
                        });
                    }
                }
    
                // Add project if validation passed
                const projectConfig: ProjectConfig = {
                    name: projectName,
                    projectId: projectId!,
                    apiKey: apiKey || '',
                    apiSecret: apiSecret || '',
                    environments: params.environments
                        ? params.environments.split(',').map(e => e.trim())
                        : ['Integration', 'Preproduction', 'Production'],
                    configSource: 'environment'
                };
    
                // Determine project type based on available credentials
                if (connectionString) {
                    // Self-hosted with connection string
                    projectConfig.connectionString = connectionString;
                    projectConfig.isSelfHosted = true;
                    projectConfig.projectType = 'self-hosted';
                } else if (isPathOnlyProject) {
                    // Unknown type - has paths but no credentials
                    projectConfig.isUnknown = true;
                    projectConfig.projectType = 'unknown';
                    projectConfig.needsConfiguration = true;
                    // For unknown projects, we need to guide users to add credentials
                    projectConfig.configurationHint = 'Add connectionString for self-hosted or id/key/secret for DXP';
                } else if (apiKey && apiSecret) {
                    // DXP PaaS project
                    projectConfig.projectType = 'dxp-paas';
                }
    
                // Add compact configuration fields if present
                if (params.blobPath) {
                    projectConfig.blobPath = params.blobPath;
                }
                if (params.dbPath) {
                    projectConfig.dbPath = params.dbPath;
                }
                if (params.logPath) {
                    projectConfig.logPath = params.logPath;
                }
                if (params.telemetry) {
                    projectConfig.telemetry = params.telemetry.toLowerCase() === 'true';
                }
    
                projects.push(projectConfig);
    
            } catch (error: any) {
                configErrors.push({
                    project: projectName,
                    error: `Failed to parse configuration: ${error.message}`,
                    variable: key
                });
            }
        });
    
        // Log configuration errors if any
        if (configErrors.length > 0) {
            console.error('\n⚠️  Configuration Errors Found:');
            configErrors.forEach(err => {
                console.error(`\n  Project: ${err.project}`);
                console.error(`  Variable: ${err.variable}`);
                console.error(`  Error: ${err.error}`);
                if (err.hint) {
                    console.error(`  Hint: ${err.hint}`);
                }
                if (err.value) {
                    console.error(`  Value: ${err.value.substring(0, 50)}...`);
                }
            });
            console.error('\n');
        }
    
        // Add dynamically added configurations
        this.dynamicConfigurations.forEach(dynConfig => {
            // Check if this dynamic config should replace an existing project
            const existingIndex = projects.findIndex(p => {
                // Match by original name (for renames)
                if (dynConfig.originalName && p.name === dynConfig.originalName) {
                    return true;
                }
                // Match by current name
                if (p.name === dynConfig.name || p.name.toLowerCase() === dynConfig.name.toLowerCase()) {
                    return true;
                }
                // Match by project ID
                if (p.projectId === dynConfig.projectId) {
                    return true;
                }
                return false;
            });
    
            if (existingIndex >= 0) {
                // Replace existing project with dynamic configuration (upgrade/rename scenario)
                projects[existingIndex] = dynConfig;
            } else if (!projects.find(p => p.projectId === dynConfig.projectId)) {
                // Only add if not already in list (avoid duplicates by ID)
                projects.push(dynConfig);
            }
        });
    
        // First project is always the default (simplified logic)
    
        // DEBUG: Log final projects found
        OutputLogger.debug('Final projects found:');
        projects.forEach((p, i) => {
            OutputLogger.debug(`  ${i + 1}. Name: "${p.name}", ID: ${p.projectId || 'undefined'}, Source: ${p.configSource || 'unknown'}`);
        });
    
        return projects;
  • Helper for adding or updating dynamic (in-memory) project configurations, called by update_project to persist changes.
    static addConfiguration(configInfo: ProjectConfig): ProjectConfig {
        // Check if configuration already exists by ID or name
        // Special handling: If upgrading from Unknown, match by name only
        const existingIndex = this.dynamicConfigurations.findIndex(c => {
            // Match by name (case-insensitive)
            const nameMatch = c.name.toLowerCase() === configInfo.name.toLowerCase();
    
            // If names match and one is Unknown being upgraded, that's a match
            if (nameMatch && (c.isUnknown || configInfo.wasUnknown)) {
                return true;
            }
    
            // Otherwise match by projectId or name
            return c.projectId === configInfo.projectId || c.name === configInfo.name;
        });
    
        if (existingIndex >= 0) {
            // Update existing configuration
            this.dynamicConfigurations[existingIndex] = {
                ...this.dynamicConfigurations[existingIndex],
                ...configInfo,
                lastUsed: new Date().toISOString()
            };
        } else {
            // Add new configuration
            this.dynamicConfigurations.push({
                ...configInfo,
                addedAt: new Date().toISOString(),
                lastUsed: new Date().toISOString()
            });
        }
    
        return configInfo;
    }

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