Skip to main content
Glama
JaxonDigital

Optimizely DXP MCP Server

by JaxonDigital

list_projects

Discover available Optimizely DXP projects with names, IDs, hosting types, and active status to prepare for project switching.

Instructions

šŸ“‚ List all configured projects in multi-project setup. REAL-TIME: <1s. Returns project names, IDs, hosting types (DXP/self-hosted), and active status. Use to discover available projects before switch_project() call. No parameters. Returns array of project summaries.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
limitNoMax results to return (1-100)
offsetNoPagination offset

Implementation Reference

  • Main handler function for the 'list_projects' tool. Parses environment variables and dynamic configs to list all configured Optimizely projects with diagnostics, validation status (DXP/Self-hosted/Unknown), pagination support, security sanitization, and structured JSON output for automation.
    static async listProjects(_args: any = {}): Promise<any> {
        try {
            // DXP-76-3: Add pagination support
            const { limit = 20, offset = 0 } = _args;
    
            // List all projects
            const allProjects = this.getConfiguredProjects();
            const diagnostics = this.validateConfiguration();
    
            // Apply pagination
            const total = allProjects.length;
            const projects = allProjects.slice(offset, offset + limit);
    
            if (allProjects.length === 0) {
                return ResponseBuilder.formatResponse({
                    success: false,
                    message: 'No projects configured yet',
                    details: [
                        'āš ļø No Optimizely projects found.',
                        '',
                        '**Quick Start - Just provide credentials when using any command:**',
                        '',
                        'Simply include ALL these parameters with your first command:',
                        '• projectName: "Your Project Name" (e.g., "Production", "Staging")',
                        '• projectId: "REPLACE_WITH_UUID"',
                        '• apiKey: "REPLACE_WITH_ACTUAL_KEY"',
                        '• apiSecret: "REPLACE_WITH_ACTUAL_SECRET"',
                        '',
                        '**Example:**',
                        '"List deployments for Production with projectName Production, projectId abc-123, apiKey SAMPLE_API_KEY, apiSecret SAMPLE_API_SECRET"',
                        '',
                        '**After the first use:**',
                        'The project will be auto-registered and you can simply say:',
                        '"List deployments for Production"',
                        '"Deploy on Production"',
                        '',
                        'šŸ’” **Why Project Names Matter:**',
                        'Project names make it easy to reference your projects without remembering UUIDs!',
                        '',
                        '**Alternative: Pre-configure projects:**',
                        'Set environment variables like:',
                        'PRODUCTION="id=uuid;key=value;secret=value"',
                        'STAGING="id=uuid;key=value;secret=value"',
                        'ACME_CORP="id=uuid;key=value;secret=value"'
                    ].join('\n')
                });
            }
    
            const sections: string[] = [];
    
            // Header (DXP-76-3: Show pagination info)
            const paginationInfo = total > limit ? ` (showing ${offset + 1}-${Math.min(offset + limit, total)} of ${total})` : '';
            sections.push(`šŸ“‚ Configured Optimizely Projects${paginationInfo}`);
            sections.push('=' .repeat(50));
    
            // List each project (name first for easier reference)
            // DXP-76-3: Adjust numbering for pagination offset
            projects.forEach((project, index) => {
                const sanitized = SecurityHelper.sanitizeObject(project);
                const actualIndex = offset + index;
                const defaultLabel = (actualIndex === 0) ? ' ⭐ (Default)' : '';
                const dynamicLabel = project.addedAt ? ' šŸ“ (Added)' : '';
    
                // Check for configuration issues for this project
                const projectDiag = diagnostics.projects.find(p => p.name === project.name);
                const hasErrors = projectDiag && projectDiag.errors.length > 0;
                const hasWarnings = projectDiag && projectDiag.warnings.length > 0;
    
                sections.push('');
                let typeLabel = ' ā˜ļø (DXP PaaS)';
                if (project.isSelfHosted) {
                    typeLabel = ' šŸ  (Self-Hosted)';
                } else if (project.isUnknown) {
                    typeLabel = ' ā“ (Unknown - Needs Config)';
                }
                sections.push(`${actualIndex + 1}. **${project.name}**${typeLabel}${defaultLabel}${dynamicLabel}${hasErrors ? ' āš ļø' : ''}`);
                sections.push(`   Project ID: ${project.projectId}`);
    
                if (project.isUnknown) {
                    // Show Unknown project info and guidance
                    sections.push(`   Type: Unknown (Paths Only)`);
                    sections.push(`   Status: āš ļø Needs Configuration`);
                    if (project.blobPath) sections.push(`   Blob Path: ${project.blobPath}`);
                    if (project.logPath) sections.push(`   Log Path: ${project.logPath}`);
                    if (project.dbPath) sections.push(`   DB Path: ${project.dbPath}`);
                    sections.push(`   šŸ’” To configure: Add connectionString (self-hosted) or id/key/secret (DXP)`);
                } else if (project.isSelfHosted) {
                    // Show connection string status for self-hosted
                    sections.push(`   Type: Self-Hosted Azure`);
                    sections.push(`   Connection String: ${sanitized.connectionString ? 'āœ… Configured' : 'āŒ Not configured'}`);
                    if (project.blobPath) sections.push(`   Blob Path: ${project.blobPath}`);
                    if (project.logPath) sections.push(`   Log Path: ${project.logPath}`);
                } else {
                    // Show API credentials for DXP PaaS
                    sections.push(`   Type: DXP PaaS`);
                    sections.push(`   API Key: ${sanitized.apiKey ? sanitized.apiKey : 'āŒ Not configured'}`);
                    sections.push(`   API Secret: ${sanitized.apiSecret ? 'āœ… Configured' : 'āŒ Not configured'}`);
                }
    
                // Show configuration errors/warnings
                if (hasErrors) {
                    projectDiag!.errors.forEach(err => {
                        sections.push(`   āŒ Error: ${err}`);
                    });
                }
    
                if (hasWarnings) {
                    projectDiag!.warnings.forEach(warn => {
                        sections.push(`   āš ļø  Warning: ${warn}`);
                    });
                }
    
                if (project.lastUsed) {
                    const lastUsed = new Date(project.lastUsed);
                    const now = new Date();
                    const diffHours = Math.floor((now.getTime() - lastUsed.getTime()) / (1000 * 60 * 60));
                    if (diffHours < 1) {
                        sections.push(`   Last used: Just now`);
                    } else if (diffHours < 24) {
                        sections.push(`   Last used: ${diffHours} hour${diffHours > 1 ? 's' : ''} ago`);
                    } else {
                        const diffDays = Math.floor(diffHours / 24);
                        sections.push(`   Last used: ${diffDays} day${diffDays > 1 ? 's' : ''} ago`);
                    }
                }
            });
    
            // Footer with usage instructions
            sections.push('');
            sections.push('=' .repeat(50));
            sections.push('šŸ’” Usage Tips:');
            sections.push('• Use project name or ID in commands');
            sections.push('• Example: "Deploy on Project 1"');
            sections.push('• Example: "List deployments for ' + (projects[0]?.name || 'project-name') + '"');
    
            // DXP-66: Build structured data for automation tools
            // DXP-76-3: Add pagination metadata
            const structuredData = {
                totalProjects: total,
                projects: projects.map((project, index) => ({
                    name: project.name,
                    projectId: project.projectId,
                    type: project.isSelfHosted ? 'self-hosted' : project.isUnknown ? 'unknown' : 'dxp',
                    isDefault: (offset + index) === 0,
                    isDynamic: !!project.addedAt,
                    hasConnectionString: !!project.connectionString,
                    hasApiCredentials: !!(project.apiKey && project.apiSecret),
                    blobPath: project.blobPath || null,
                    logPath: project.logPath || null,
                    dbPath: project.dbPath || null,
                    lastUsed: project.lastUsed || null
                })),
                pagination: {
                    total,
                    limit,
                    offset,
                    hasMore: (offset + limit) < total
                }
            };
    
            return ResponseBuilder.successWithStructuredData(
                structuredData,
                sections.join('\n')
            );
    
        } catch (error: any) {
            return ResponseBuilder.formatResponse({
                success: false,
                message: 'Failed to list projects',
                error: error.message
            });
        }
    }
  • Core helper function that discovers and parses all project configurations from environment variables (DXP API keys, self-hosted connection strings, unknown placeholders) and dynamic in-memory additions. Handles validation, error logging, and merging.
    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;
    }
  • Validates all discovered projects, categorizes by type (DXP PaaS, Self-hosted, Unknown), checks credential validity, detects placeholders/errors, and returns comprehensive diagnostics used by list_projects.
    static validateConfiguration(): Diagnostics {
        const projects = this.getConfiguredProjects();
        const diagnostics: Diagnostics = {
            valid: true,
            projectCount: projects.length,
            hasDefault: false,
            errors: [],
            warnings: [],
            projects: []
        };
    
        // First project is always the default
        if (projects.length > 0) {
            diagnostics.hasDefault = true;
        }
    
        // Validate each project
        projects.forEach(project => {
            const projectDiag: ProjectDiag = {
                name: project.name,
                valid: true,
                errors: [],
                warnings: []
            };
    
            // Check credentials based on project type
            if (project.isUnknown) {
                // Unknown projects need configuration guidance
                projectDiag.warnings.push('Project type unknown - needs connection string (self-hosted) or API credentials (DXP)');
                projectDiag.warnings.push('Currently using local paths only');
                // Unknown projects are valid but need configuration for full functionality
                projectDiag.valid = true;
            } else if (project.isSelfHosted) {
                // Self-hosted projects use connection strings
                if (!project.connectionString || project.connectionString.length < 50) {
                    projectDiag.errors.push('Connection string appears invalid or too short');
                    projectDiag.valid = false;
                }
            } else {
                // DXP projects use API Key/Secret
                if (!project.apiKey || project.apiKey.length < 20) {
                    projectDiag.errors.push('API Key appears invalid or too short');
                    projectDiag.valid = false;
                }
    
                if (!project.apiSecret || project.apiSecret.length < 20) {
                    projectDiag.errors.push('API Secret appears invalid or too short');
                    projectDiag.valid = false;
                }
    
                // Check for common mistakes
                if (project.apiKey && (project.apiKey.includes('REPLACE_WITH') || project.apiKey.includes('PLACEHOLDER') || project.apiKey.includes('SAMPLE'))) {
                    projectDiag.errors.push('API Key is a placeholder value');
                    projectDiag.valid = false;
                }
    
                if (project.apiSecret && (project.apiSecret.includes('REPLACE_WITH') || project.apiSecret.includes('PLACEHOLDER') || project.apiSecret.includes('SAMPLE'))) {
                    projectDiag.errors.push('API Secret is a placeholder value');
                    projectDiag.valid = false;
                }
            }
    
            // Removed warning about environment names in project names
            // This is actually a valid use case - some organizations create
            // separate API keys per environment for security reasons
    
            if (projectDiag.errors.length > 0) {
                diagnostics.valid = false;
            }
    
            diagnostics.projects.push(projectDiag);
        });
    
        return diagnostics;
    }
  • Tool availability registration in the matrix that defines hosting compatibility (all types) and metadata for 'list_projects'.
    'list_projects': {
        hostingTypes: ['dxp-paas', 'dxp-saas', 'self-hosted', 'unknown'],
        category: 'Project Management',
        description: 'List all configured projects'
    },
  • NLP pattern matching rules that route natural language queries about projects to the list_projects tool.
    { pattern: /^(what|which|show).*(current.*project)/i, tool: 'list_projects', category: 'info', options: { current: true } },
    { pattern: /^(list|show).*(project)/i, tool: 'list_projects', category: 'info' },
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden and does well by disclosing performance characteristics ('REAL-TIME: <1s'), return format ('Returns project names, IDs, hosting types, and active status'), and output structure ('Returns array of project summaries'). It doesn't mention pagination behavior or error conditions, keeping it from a perfect score.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Extremely well-structured and front-loaded: starts with purpose, adds performance context, specifies return format, provides usage guidance, and notes parameter situation. Every sentence earns its place with zero wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only listing tool with no output schema, the description provides excellent context: purpose, performance, return format, usage guidance. It's missing details about pagination behavior (implied by parameters but not explained) and error cases, but otherwise quite complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already fully documents both parameters. The description states 'No parameters' which contradicts the schema showing 2 optional parameters, but since schema coverage is complete, baseline 3 is appropriate. The description doesn't add meaningful parameter semantics beyond what the schema provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb ('List') and resource ('all configured projects in multi-project setup'), and distinguishes from siblings by specifying it's for discovering available projects before using switch_project(). It's specific about scope and differentiation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to use this tool ('Use to discover available projects before switch_project() call'), providing clear context and naming a specific alternative. This gives the agent perfect guidance on when this tool is appropriate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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