@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
190 lines • 6.16 kB
JavaScript
/**
* POC: Metrics Scope Validator
*
* This proof of concept validates the critical orchestration rule:
* - Campaigns MUST use metrics scope: "session"
* - Experiments MUST use metrics scope: "visitor"
*
* This single file proves the entire validation architecture concept.
*/
import * as fs from 'fs';
// Orchestration-specific rules that extend the base OpenAPI fields
const ORCHESTRATION_RULES = {
web: {
campaign: {
metrics: {
scope: {
required: 'session',
error: 'Web campaigns MUST use metrics scope: "session", not "visitor"'
}
}
},
experiment: {
metrics: {
scope: {
required: 'visitor',
error: 'Web experiments MUST use metrics scope: "visitor", not "session"'
}
}
}
},
feature: {
// Feature experimentation doesn't have the same metrics scope restrictions
}
};
/**
* Extract entity type from system_template_id
* Examples:
* - "optimizely_experiment_simple" -> "experiment"
* - "optimizely_campaign_basic" -> "campaign"
*/
function extractEntityType(systemTemplateId) {
const patterns = [
{ regex: /optimizely_experiment_/, type: 'experiment' },
{ regex: /optimizely_campaign_/, type: 'campaign' },
{ regex: /optimizely_audience_/, type: 'audience' },
{ regex: /optimizely_event_/, type: 'event' },
{ regex: /optimizely_flag_/, type: 'flag' },
{ regex: /optimizely_ruleset_/, type: 'ruleset' }
];
for (const pattern of patterns) {
if (pattern.regex.test(systemTemplateId)) {
return pattern.type;
}
}
return null;
}
/**
* Validate metrics scope for a single step
*/
function validateMetricsScope(step, platform = 'web') {
// Only validate template steps
if (step.type !== 'template')
return null;
const entityType = extractEntityType(step.template.system_template_id);
if (!entityType)
return null;
// Get orchestration rules for this entity
const platformRules = ORCHESTRATION_RULES[platform];
if (!platformRules)
return null;
const rules = platformRules[entityType];
if (!rules || typeof rules !== 'object' || !('metrics' in rules))
return null;
const metricsRules = rules.metrics;
if (!metricsRules || typeof metricsRules !== 'object' || !('scope' in metricsRules))
return null;
// Check if step has metrics
const metrics = step.template.inputs?.metrics;
if (!metrics || !Array.isArray(metrics))
return null;
// Validate each metric's scope
const scopeRule = metricsRules.scope;
if (!scopeRule || typeof scopeRule !== 'object')
return null;
for (let i = 0; i < metrics.length; i++) {
const metric = metrics[i];
if (metric.scope !== scopeRule.required) {
return {
stepId: step.id,
entityType,
field: `metrics[${i}].scope`,
message: scopeRule.error,
found: metric.scope,
expected: scopeRule.required,
fix: {
path: `steps.${step.id}.template.inputs.metrics[${i}].scope`,
value: scopeRule.required
}
};
}
}
return null;
}
/**
* Validate an entire orchestration template
*/
export function validateTemplate(template) {
const errors = [];
const warnings = [];
const platform = template.platform || 'web';
let validSteps = 0;
// Validate each step
for (const step of template.steps) {
const error = validateMetricsScope(step, platform);
if (error) {
errors.push(error);
}
else {
validSteps++;
}
}
return {
valid: errors.length === 0,
errors,
warnings,
summary: {
totalSteps: template.steps.length,
validSteps,
errorCount: errors.length
}
};
}
/**
* Format validation results for display
*/
export function formatResults(result) {
const lines = [];
lines.push('ORCHESTRATION TEMPLATE VALIDATION');
lines.push('=================================\n');
if (result.valid) {
lines.push('✅ Template is valid!');
lines.push(` ${result.summary.totalSteps} steps validated successfully`);
}
else {
lines.push(`❌ Found ${result.errors.length} validation error(s):\n`);
for (const error of result.errors) {
lines.push(`Error in step "${error.stepId}" (${error.entityType}):`);
lines.push(` Field: ${error.field}`);
lines.push(` Issue: ${error.message}`);
lines.push(` Found: "${error.found}"`);
lines.push(` Expected: "${error.expected}"`);
if (error.fix) {
lines.push(` Fix: Set ${error.fix.path} to "${error.fix.value}"`);
}
lines.push('');
}
}
lines.push('\nSUMMARY:');
lines.push(` Total steps: ${result.summary.totalSteps}`);
lines.push(` Valid steps: ${result.summary.validSteps}`);
lines.push(` Errors: ${result.summary.errorCount}`);
return lines.join('\n');
}
/**
* CLI entry point for testing
*/
function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.log('Usage: ts-node validate-metrics-scope.ts <template.json>');
process.exit(1);
}
const templatePath = args[0];
try {
const templateContent = fs.readFileSync(templatePath, 'utf8');
const template = JSON.parse(templateContent);
const result = validateTemplate(template);
console.log(formatResults(result));
process.exit(result.valid ? 0 : 1);
}
catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
}
// Run if called directly
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}
//# sourceMappingURL=validate-metrics-scope.js.map