@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
424 lines (353 loc) โข 15.1 kB
JavaScript
/**
* Tool Consolidation Validation Script
*
* Usage: node validate-tool-consolidation.js <new-tool> <old-tool1> [old-tool2] ...
* Example: node validate-tool-consolidation.js manage_flag_state enable_flag disable_flag
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
// ANSI color codes for output
const colors = {
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
reset: '\x1b[0m'
};
// Configuration
const PATHS_TO_CHECK = [
'src',
'docs',
'test',
'scripts',
'examples'
];
const FILE_EXTENSIONS = [
'.ts',
'.js',
'.md',
'.json',
'.yaml',
'.yml'
];
const CRITICAL_FILES = [
'src/index.ts',
'src/tools/OptimizelyMCPTools.ts',
'docs/development/GET-SUPPORTED-TOOLS.md',
'docs/developer-guides/developer-reference-guide.md'
];
class ToolConsolidationValidator {
constructor(newTool, oldTools) {
this.newTool = newTool;
this.oldTools = oldTools;
this.results = {
oldToolReferences: {},
newToolReferences: {},
missingUpdates: [],
validationErrors: [],
suggestions: []
};
}
log(message, color = 'reset') {
console.log(`${colors[color]}${message}${colors.reset}`);
}
async validate() {
this.log('\n๐ TOOL CONSOLIDATION VALIDATION', 'blue');
this.log('=====================================', 'blue');
this.log(`New Tool: ${this.newTool}`, 'green');
this.log(`Old Tools: ${this.oldTools.join(', ')}`, 'yellow');
this.log('');
// Step 1: Find all references to old tools
this.findOldToolReferences();
// Step 2: Find new tool implementation
this.findNewToolImplementation();
// Step 3: Validate registration
this.validateRegistration();
// Step 4: Check documentation
this.checkDocumentation();
// Step 5: Validate test coverage
this.validateTestCoverage();
// Step 6: Check for common mistakes
this.checkCommonMistakes();
// Step 7: Generate report
this.generateReport();
}
findOldToolReferences() {
this.log('๐ Finding references to old tools...', 'yellow');
this.oldTools.forEach(tool => {
this.results.oldToolReferences[tool] = [];
try {
// Use grep to find references
const grepPattern = tool.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const command = `grep -r "${grepPattern}" ${PATHS_TO_CHECK.join(' ')} --include="*${FILE_EXTENSIONS.join('" --include="*')}" 2>/dev/null || true`;
const output = execSync(command, { encoding: 'utf8' });
const lines = output.split('\n').filter(line => line.trim());
lines.forEach(line => {
const [filePath, ...content] = line.split(':');
if (filePath && content.length > 0) {
this.results.oldToolReferences[tool].push({
file: filePath,
content: content.join(':').trim()
});
}
});
this.log(` Found ${this.results.oldToolReferences[tool].length} references to "${tool}"`,
this.results.oldToolReferences[tool].length > 0 ? 'yellow' : 'green');
} catch (error) {
this.log(` Error searching for ${tool}: ${error.message}`, 'red');
}
});
}
findNewToolImplementation() {
this.log('\n๐ง Checking new tool implementation...', 'yellow');
// Check if new tool is registered
const indexPath = path.join(process.cwd(), 'src/index.ts');
if (fs.existsSync(indexPath)) {
const content = fs.readFileSync(indexPath, 'utf8');
if (content.includes(`name: '${this.newTool}'`)) {
this.log(` โ
Found registration for "${this.newTool}"`, 'green');
// Extract the tool definition
const toolRegex = new RegExp(`{[^}]*name:\\s*'${this.newTool}'[^}]*}`, 's');
const match = content.match(toolRegex);
if (match) {
// Check for required properties
const definition = match[0];
const hasDescription = definition.includes('description:');
const hasInputSchema = definition.includes('inputSchema:');
if (!hasDescription) {
this.results.validationErrors.push('New tool missing description');
}
if (!hasInputSchema) {
this.results.validationErrors.push('New tool missing inputSchema');
}
}
} else {
this.results.validationErrors.push(`Tool "${this.newTool}" not found in index.ts`);
this.log(` โ Registration not found for "${this.newTool}"`, 'red');
}
}
// Check implementation in OptimizelyMCPTools
const toolsPath = path.join(process.cwd(), 'src/tools/OptimizelyMCPTools.ts');
if (fs.existsSync(toolsPath)) {
const content = fs.readFileSync(toolsPath, 'utf8');
const methodName = this.convertToMethodName(this.newTool);
if (content.includes(`async ${methodName}(`)) {
this.log(` โ
Found implementation method "${methodName}"`, 'green');
} else {
this.results.validationErrors.push(`Implementation method "${methodName}" not found`);
this.log(` โ Implementation not found for "${methodName}"`, 'red');
}
}
}
validateRegistration() {
this.log('\n๐ Validating tool registration...', 'yellow');
const indexPath = path.join(process.cwd(), 'src/index.ts');
if (!fs.existsSync(indexPath)) return;
const content = fs.readFileSync(indexPath, 'utf8');
// Check if old tools are marked deprecated
this.oldTools.forEach(tool => {
const toolRegex = new RegExp(`name:\\s*'${tool}'[^}]*}`, 's');
const match = content.match(toolRegex);
if (match) {
const definition = match[0];
if (definition.includes('deprecated') || definition.includes('DEPRECATED')) {
this.log(` โ
"${tool}" marked as deprecated`, 'green');
} else {
this.results.missingUpdates.push(`Mark "${tool}" as deprecated in registration`);
this.log(` โ ๏ธ "${tool}" not marked as deprecated`, 'yellow');
}
}
});
// Check validTools array
if (content.includes('validTools')) {
const validToolsMatch = content.match(/validTools\s*=\s*\[[^\]]+\]/);
if (validToolsMatch) {
const validToolsArray = validToolsMatch[0];
if (!validToolsArray.includes(`'${this.newTool}'`)) {
this.results.validationErrors.push(`"${this.newTool}" not in validTools array`);
}
// Check if old tools are still in validTools (should be for backward compatibility)
this.oldTools.forEach(tool => {
if (!validToolsArray.includes(`'${tool}'`)) {
this.results.suggestions.push(`Consider keeping "${tool}" in validTools for backward compatibility`);
}
});
}
}
}
checkDocumentation() {
this.log('\n๐ Checking documentation...', 'yellow');
CRITICAL_FILES.forEach(filePath => {
const fullPath = path.join(process.cwd(), filePath);
if (!fs.existsSync(fullPath)) {
this.log(` โ ๏ธ File not found: ${filePath}`, 'yellow');
return;
}
const content = fs.readFileSync(fullPath, 'utf8');
// Check if new tool is documented
if (!content.includes(this.newTool)) {
this.results.missingUpdates.push(`Document "${this.newTool}" in ${filePath}`);
this.log(` โ "${this.newTool}" not found in ${filePath}`, 'red');
} else {
this.log(` โ
"${this.newTool}" documented in ${filePath}`, 'green');
}
// Check if old tools have deprecation notices
this.oldTools.forEach(tool => {
if (content.includes(tool)) {
const toolContext = this.getContextAroundMatch(content, tool);
if (!toolContext.toLowerCase().includes('deprecated') &&
!toolContext.toLowerCase().includes('legacy') &&
!toolContext.toLowerCase().includes('migrate')) {
this.results.missingUpdates.push(`Add deprecation notice for "${tool}" in ${filePath}`);
}
}
});
});
}
validateTestCoverage() {
this.log('\n๐งช Checking test coverage...', 'yellow');
// Look for test files
const testPatterns = [
`**/*${this.newTool}*.test.*`,
`**/*${this.newTool}*.spec.*`,
`**/test*${this.convertToMethodName(this.newTool)}*`
];
let testsFound = false;
testPatterns.forEach(pattern => {
try {
const command = `find test -name "${pattern}" 2>/dev/null || true`;
const output = execSync(command, { encoding: 'utf8' }).trim();
if (output) {
testsFound = true;
this.log(` โ
Found test file: ${output}`, 'green');
}
} catch (error) {
// Ignore errors
}
});
if (!testsFound) {
this.results.validationErrors.push(`No test files found for "${this.newTool}"`);
this.log(` โ No test files found for "${this.newTool}"`, 'red');
}
}
checkCommonMistakes() {
this.log('\nโ ๏ธ Checking for common mistakes...', 'yellow');
// Check 1: Ensure parameter routing is implemented
const toolsPath = path.join(process.cwd(), 'src/tools/OptimizelyMCPTools.ts');
if (fs.existsSync(toolsPath)) {
const content = fs.readFileSync(toolsPath, 'utf8');
const methodName = this.convertToMethodName(this.newTool);
// Look for switch statement or if-else for parameter routing
const methodMatch = content.match(new RegExp(`async ${methodName}\\([^)]*\\)[^{]*{[^}]+}`, 's'));
if (methodMatch && !methodMatch[0].includes('switch') && !methodMatch[0].includes('if')) {
this.results.suggestions.push('Consider adding parameter routing (switch/if) for action-based consolidation');
}
}
// Check 2: Backward compatibility wrappers
this.oldTools.forEach(tool => {
const oldMethodName = this.convertToMethodName(tool);
const hasWrapper = this.checkForBackwardCompatibility(oldMethodName, this.convertToMethodName(this.newTool));
if (!hasWrapper) {
this.results.missingUpdates.push(`Add backward compatibility wrapper for "${oldMethodName}"`);
}
});
// Check 3: Error messages still reference old tools
this.results.oldToolReferences[this.oldTools[0]]?.forEach(ref => {
if (ref.content.includes('Error') || ref.content.includes('error') ||
ref.content.includes('throw') || ref.content.includes('reject')) {
this.results.suggestions.push(`Update error message in ${ref.file} to reference "${this.newTool}"`);
}
});
}
generateReport() {
this.log('\n๐ VALIDATION REPORT', 'blue');
this.log('=====================================', 'blue');
// Summary
const totalOldRefs = Object.values(this.results.oldToolReferences)
.reduce((sum, refs) => sum + refs.length, 0);
this.log(`\n๐ Summary:`, 'blue');
this.log(` Total old tool references: ${totalOldRefs}`);
this.log(` Validation errors: ${this.results.validationErrors.length}`);
this.log(` Missing updates: ${this.results.missingUpdates.length}`);
this.log(` Suggestions: ${this.results.suggestions.length}`);
// Validation Errors (Critical)
if (this.results.validationErrors.length > 0) {
this.log(`\nโ VALIDATION ERRORS (Must Fix):`, 'red');
this.results.validationErrors.forEach(error => {
this.log(` โข ${error}`, 'red');
});
}
// Missing Updates (Important)
if (this.results.missingUpdates.length > 0) {
this.log(`\nโ ๏ธ MISSING UPDATES (Should Fix):`, 'yellow');
this.results.missingUpdates.forEach(update => {
this.log(` โข ${update}`, 'yellow');
});
}
// Suggestions (Nice to have)
if (this.results.suggestions.length > 0) {
this.log(`\n๐ก SUGGESTIONS (Consider):`, 'blue');
this.results.suggestions.forEach(suggestion => {
this.log(` โข ${suggestion}`, 'blue');
});
}
// Old tool references details
this.log(`\n๐ Old Tool References:`, 'yellow');
Object.entries(this.results.oldToolReferences).forEach(([tool, refs]) => {
if (refs.length > 0) {
this.log(`\n "${tool}" (${refs.length} references):`, 'yellow');
refs.slice(0, 5).forEach(ref => {
this.log(` ${ref.file}: ${ref.content.substring(0, 80)}...`);
});
if (refs.length > 5) {
this.log(` ... and ${refs.length - 5} more references`);
}
}
});
// Action items
this.log(`\nโ
ACTION ITEMS:`, 'green');
this.log(` 1. Fix all validation errors first`);
this.log(` 2. Address missing updates`);
this.log(` 3. Consider implementing suggestions`);
this.log(` 4. Run validation again after fixes`);
// Completion status
const isComplete = this.results.validationErrors.length === 0 &&
this.results.missingUpdates.length === 0;
this.log(`\n๐ CONSOLIDATION STATUS: ${isComplete ? 'โ
READY' : 'โ NOT READY'}`,
isComplete ? 'green' : 'red');
}
// Helper methods
convertToMethodName(toolName) {
// Convert snake_case to camelCase
return toolName.replace(/_([a-z])/g, (g) => g[1].toUpperCase());
}
getContextAroundMatch(content, searchTerm) {
const index = content.indexOf(searchTerm);
if (index === -1) return '';
const start = Math.max(0, index - 100);
const end = Math.min(content.length, index + searchTerm.length + 100);
return content.substring(start, end);
}
checkForBackwardCompatibility(oldMethod, newMethod) {
const toolsPath = path.join(process.cwd(), 'src/tools/OptimizelyMCPTools.ts');
if (!fs.existsSync(toolsPath)) return false;
const content = fs.readFileSync(toolsPath, 'utf8');
const pattern = new RegExp(`${oldMethod}[^{]*{[^}]*${newMethod}`, 's');
return pattern.test(content);
}
}
// Main execution
if (require.main === module) {
const args = process.argv.slice(2);
if (args.length < 2) {
console.error('Usage: node validate-tool-consolidation.js <new-tool> <old-tool1> [old-tool2] ...');
console.error('Example: node validate-tool-consolidation.js manage_flag_state enable_flag disable_flag');
process.exit(1);
}
const [newTool, ...oldTools] = args;
const validator = new ToolConsolidationValidator(newTool, oldTools);
validator.validate();
}
module.exports = ToolConsolidationValidator;