@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
163 lines (161 loc) • 6.37 kB
JavaScript
/**
* Flag A/B Test Transformer
*
* Detects and prevents agents from trying to update flags with ab_test structures,
* which is a Web Experimentation pattern that doesn't exist in Feature Experimentation.
*
* @author Optimizely MCP Server
* @version 1.0.0
*/
import { getLogger } from '../logging/Logger.js';
export class FlagAbTestTransformer {
logger = getLogger();
/**
* Check if a flag update contains invalid ab_test structure
*/
checkFlagUpdate(entityType, operation, payload, platform) {
// Only check flag updates
if (entityType !== 'flag' || operation !== 'update') {
return { shouldReject: false };
}
// Check for ab_test structure
const hasAbTest = payload?.ab_test ||
payload?.entity_data?.ab_test ||
payload?.data?.ab_test;
if (!hasAbTest) {
return { shouldReject: false };
}
this.logger.warn({
entityType,
operation,
platform,
payloadKeys: Object.keys(payload || {})
}, 'FlagAbTestTransformer: Detected ab_test structure in flag update');
// Extract information from the ab_test structure
const abTest = payload.ab_test || payload.entity_data?.ab_test || payload.data?.ab_test;
const variations = abTest.variations || [];
const metrics = abTest.metrics || [];
const environment = abTest.environment?.ref?.key || 'development';
const flagKey = payload.entity_id || payload.flag_key || 'unknown_flag';
return {
shouldReject: true,
reason: "Feature Flags cannot be updated with 'ab_test' structures",
guidance: `Feature Experimentation uses a different workflow for A/B tests:
❌ INCORRECT: Updating flag with ab_test structure
✅ CORRECT: Create variations and update ruleset
The correct workflow is:
1. Create variations as separate entities
2. Update the ruleset to add an A/B test rule
3. Create events for metrics
4. Create an experiment to track results`,
suggestedWorkflow: this.generateCorrectWorkflow(flagKey, environment, variations, metrics)
};
}
/**
* Generate the correct workflow steps
*/
generateCorrectWorkflow(flagKey, environment, variations, metrics) {
const workflow = [];
// Step 1: Create variations
variations.forEach((variation, index) => {
workflow.push({
step: `1.${index + 1}`,
description: `Create variation: ${variation.key}`,
tool_call: {
tool: "manage_entity_lifecycle",
arguments: {
operation: "create",
entity_type: "variation",
entity_data: {
key: variation.key,
name: variation.name || variation.key,
weight: variation.weight || Math.floor(10000 / variations.length)
},
options: {
flag_key: flagKey
}
}
}
});
});
// Step 2: Create events for metrics
metrics.forEach((metric, index) => {
const eventTemplate = metric.ref?.template || {};
workflow.push({
step: `2.${index + 1}`,
description: `Create event: ${eventTemplate.key}`,
tool_call: {
tool: "manage_entity_lifecycle",
arguments: {
operation: "create",
entity_type: "event",
entity_data: {
key: eventTemplate.key,
name: eventTemplate.name || eventTemplate.key,
event_type: eventTemplate.event_type || "custom"
}
}
}
});
});
// Step 3: Update ruleset
const variationWeights = variations.map(v => ({
key: v.key,
weight: v.weight || Math.floor(10000 / variations.length)
}));
workflow.push({
step: "3",
description: "Update ruleset with A/B test rule",
tool_call: {
tool: "manage_entity_lifecycle",
arguments: {
operation: "update",
entity_type: "ruleset",
entity_data: [{
op: "add",
path: "/rules/0",
value: {
key: "ab_test_rule",
enabled: true,
audience_conditions: "everyone",
percentage_included: 10000,
variations: variationWeights
}
}],
options: {
flag_key: flagKey,
environment_key: environment
}
}
}
});
// Step 4: Create experiment
const metricIds = metrics.map(m => m.ref?.template?.key || 'metric');
workflow.push({
step: "4",
description: "Create experiment to track results",
tool_call: {
tool: "manage_entity_lifecycle",
arguments: {
operation: "create",
entity_type: "experiment",
entity_data: {
key: `${flagKey}_experiment`,
name: `A/B Test for ${flagKey}`,
flag_key: flagKey,
variations: variations.map(v => ({
key: v.key,
name: v.name || v.key,
feature_enabled: true
})),
metrics: metricIds
}
}
}
});
return workflow;
}
}
// Export singleton
export const flagAbTestTransformer = new FlagAbTestTransformer();
//# sourceMappingURL=FlagAbTestTransformer.js.map