UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

163 lines (161 loc) 6.37 kB
/** * 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