@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
599 lines • 29.6 kB
JavaScript
/**
* Entity Documentation Helper
* Provides quick documentation lookups using generated field data
*
* Note on Environments:
* Both Feature Experimentation and Web Experimentation have "environment" entities,
* but they use different APIs and have different schemas. In our system:
* - "environment" refers to the entity (context-dependent)
* - "fx_environment" is used internally for Feature Experimentation environments
* The system automatically routes to the correct API based on project type.
*/
import { FIELDS } from '../generated/fields.generated.js';
export class EntityDocumentationHelper {
/**
* Get documentation for a specific entity type
*/
static getEntityDocumentation(entityType) {
// Normalize entity type
let normalizedType = entityType.toLowerCase().replace(/-/g, '_');
// Handle special mappings
if (normalizedType === 'environment') {
// For now, we'll check both environment and fx_environment
// In the future, this should be determined by project context
if ('environment' in FIELDS) {
normalizedType = 'environment';
}
else if ('fx_environment' in FIELDS) {
normalizedType = 'fx_environment';
}
}
// Check if entity exists in FIELDS
if (!(normalizedType in FIELDS)) {
return null;
}
const entityData = FIELDS[normalizedType];
// Determine operations from endpoints
const endpoints = entityData.endpoints || {};
const endpointValues = Object.values(endpoints).map(e => String(e));
const endpointString = endpointValues.join(' ');
// Get accurate operations based on entity type
const operations = this.getEntityOperations(normalizedType, endpoints, endpointString);
// Get description from field descriptions or use a default
const description = this.getEntityDescription(normalizedType);
// Determine platform availability
const platformAvailability = this.getPlatformAvailability(normalizedType);
// Get template mode information
const templateModeInfo = this.getTemplateModeInfo(normalizedType);
// Build comprehensive field documentation
const fieldDocumentation = this.buildFieldDocumentation(normalizedType, entityData);
// Get usage notes for the entity
const usageNotes = this.getUsageNotes(normalizedType);
// Get entity relationships
const relationships = this.getEntityRelationships(normalizedType);
// Get common patterns
const commonPatterns = this.getCommonPatterns(normalizedType);
// Get update behavior
const updateBehavior = this.getUpdateBehavior(normalizedType);
return {
entity_type: normalizedType,
description,
operations,
required_fields: entityData.required || [],
optional_fields: entityData.optional || [],
endpoints: entityData.endpoints || {},
examples: entityData.fieldExamples,
validation: entityData.validation,
platform_availability: platformAvailability,
template_mode_info: templateModeInfo,
field_documentation: fieldDocumentation,
usage_notes: usageNotes,
relationships,
common_patterns: commonPatterns,
update_behavior: updateBehavior
};
}
/**
* Get a comprehensive description for the entity
*/
static getEntityDescription(entityType) {
const descriptions = {
project: "Container for all Optimizely entities and experiments. Projects define the platform (web, mobile, server-side) and contain flags, experiments, audiences, and events.",
flag: "Feature flag for controlling feature rollout and A/B testing (Feature Experimentation). Flags contain variable definitions and are configured per environment through rulesets. Template mode automatically creates variations, rulesets, and dependencies.",
experiment: "A/B test or multivariate test. PLATFORM DIFFERENCES: In Web Experimentation, experiments are standalone entities with variations, pages, and metrics. In Feature Experimentation, 'experiments' are actually rules with type='a/b' within flag rulesets - query using flags_unified_view WHERE rule_type='a/b' or experiments_unified_view WHERE platform='feature'. Template mode handles all dependencies automatically.",
variation: "Different experiences or values in an experiment. In Feature Experimentation, variations have no weight and contain variable values. In Web Experimentation, variations have weights and contain visual changes. The 'off' variation is a default that always exists for flags.",
campaign: "Container for Web Experimentation experiments. Campaigns group related experiments and can apply holdback to reserve a portion of traffic. Experiments within a campaign share audiences and metrics.",
page: "Web page configuration for experiments (Web Experimentation). Pages define where experiments run using URL patterns and activation conditions. Can trigger on immediate load, DOM changes, or custom events.",
audience: "User segment definition for targeting. Audiences use conditions based on attributes, behaviors, or custom criteria. Can be shared across experiments and platforms.",
attribute: "User attribute for audience targeting. Attributes define the data points available for segmentation (e.g., location, device, custom properties). Different from list_attributes which contain predefined value lists.",
event: "Custom event for tracking conversions and metrics. Events can be clicks, pageviews, custom actions, or revenue. Shared across experiments for consistent measurement.",
rule: "Targeting logic within a ruleset (Feature Experimentation). Rules define how traffic is allocated to variations. Types include a/b (experiment), targeted_delivery (specific audience), and rollout (percentage-based).",
ruleset: "Container for targeting rules in Feature Experimentation. Each flag has one ruleset per environment. Contains rules with types: 'a/b' (A/B test experiments), 'targeted_delivery' (specific audience), 'rollout' (percentage). To find A/B tests: query rules WHERE type='a/b'. Updated using JSON Patch format.",
variable_definition: "Schema definition for flag variables (Feature Experimentation). Defines the variables available within a flag including type (string, boolean, integer, double, json), default values, and descriptions.",
environment: "Deployment environment configuration. Represents different stages like development, staging, production. In Feature Experimentation, each environment has separate flag configurations via rulesets.",
fx_environment: "Feature Experimentation specific environment configuration. Contains environment-level settings and feature flag states.",
extension: "Custom JavaScript functionality for Web experiments. Extensions run code on pages for advanced modifications, analytics integrations, or custom behaviors.",
webhook: "HTTP callback for Optimizely events (Web Experimentation). Webhooks notify external systems about experiment changes, results, or user actions.",
group: "Mutual exclusion group for experiments. Groups ensure users see only one experiment from the group, preventing interaction effects. Traffic is allocated to experiments based on group policy.",
collaborator: "User with project access. Collaborators have roles and permissions defining what they can view or modify within a project.",
account: "Top-level Optimizely account container. Accounts contain projects and manage billing, users, and organization-wide settings.",
feature: "Legacy feature configuration (deprecated - use flags instead). Features were the predecessor to flags in earlier Optimizely versions.",
list_attribute: "Predefined list of values for targeting (Web Experimentation). Unlike regular attributes, list_attributes contain specific values like zip codes, query parameters, or cookie values for precise targeting.",
results: "Experiment or campaign performance data. Results include metrics like conversion rates, statistical significance, and confidence intervals. Updated periodically as experiments run.",
variable: "Individual variable within a feature flag. Variables store the actual values delivered to users based on targeting rules and variations.",
segment: "User segment for Web Experimentation personalization. Segments define audiences for targeted experiences outside of traditional A/B tests."
};
return descriptions[entityType] || `Optimizely ${entityType} entity`;
}
/**
* Determine platform availability based on entity type
*/
static getPlatformAvailability(entityType) {
const featureOnly = ['flag', 'rule', 'ruleset', 'variable_definition', 'feature', 'variable', 'fx_environment'];
const webOnly = ['campaign', 'page', 'extension', 'segment', 'webhook', 'list_attribute'];
const both = ['project', 'experiment', 'audience', 'attribute', 'event', 'group', 'environment', 'variation', 'collaborator', 'results'];
if (featureOnly.includes(entityType)) {
return { feature_experimentation: true, web_experimentation: false };
}
else if (webOnly.includes(entityType)) {
return { feature_experimentation: false, web_experimentation: true };
}
else if (both.includes(entityType)) {
return { feature_experimentation: true, web_experimentation: true };
}
// Handle special cases
if (entityType === 'account') {
return { feature_experimentation: true, web_experimentation: true };
}
return { feature_experimentation: false, web_experimentation: false };
}
/**
* Get field-specific documentation
*/
static getFieldDocumentation(entityType, fieldName) {
const normalizedType = entityType.toLowerCase().replace(/-/g, '_');
if (!(normalizedType in FIELDS)) {
return null;
}
const entityData = FIELDS[normalizedType];
// Handle the const assertion types with proper type guards
const fieldDescriptions = entityData.fieldDescriptions || {};
const fieldTypes = entityData.fieldTypes || {};
const fieldExamples = entityData.fieldExamples || {};
const enums = entityData.enums || {};
const validation = entityData.validation || {};
return {
description: fieldName in fieldDescriptions ? fieldDescriptions[fieldName] : undefined,
type: fieldName in fieldTypes ? fieldTypes[fieldName] : undefined,
required: Boolean(entityData.required?.some(field => field === fieldName)),
enum_values: fieldName in enums ? enums[fieldName] : undefined,
example: fieldName in fieldExamples ? fieldExamples[fieldName] : undefined,
validation: {
minLength: validation.minLength && fieldName in validation.minLength ? validation.minLength[fieldName] : undefined,
maxLength: validation.maxLength && fieldName in validation.maxLength ? validation.maxLength[fieldName] : undefined,
pattern: validation.pattern && fieldName in validation.pattern ? validation.pattern[fieldName] : undefined,
minimum: validation.minimum && fieldName in validation.minimum ? validation.minimum[fieldName] : undefined,
maximum: validation.maximum && fieldName in validation.maximum ? validation.maximum[fieldName] : undefined
}
};
}
/**
* Search for entities by capability
*/
static findEntitiesByCapability(capability) {
const result = [];
for (const [entityType, entityData] of Object.entries(FIELDS)) {
const endpoints = entityData.endpoints || {};
const endpointValues = Object.values(endpoints).map(e => String(e));
let hasCapability = false;
switch (capability) {
case 'create':
hasCapability = 'create' in endpoints || endpointValues.some(e => e.includes('POST'));
break;
case 'update':
hasCapability = 'update' in endpoints || endpointValues.some(e => e.includes('PATCH') || e.includes('PUT'));
break;
case 'delete':
hasCapability = 'delete' in endpoints || endpointValues.some(e => e.includes('DELETE'));
break;
case 'list':
hasCapability = 'list' in endpoints || endpointValues.some(e => e.includes('GET') && !e.includes('{'));
break;
}
if (hasCapability) {
result.push(entityType);
}
}
return result;
}
/**
* Get template mode information for an entity type
*/
static getTemplateModeInfo(entityType) {
const templateInfo = {
flag: {
supported: true,
scenarios: [
'Creating a flag with A/B test experiment',
'Creating a flag with multiple rules and variations',
'Creating a flag with custom events for metrics'
],
automatic_orchestration: [
'Creates flag with proper variable definitions',
'Sets up rulesets for each environment',
'Creates or reuses events for metrics',
'Configures experiments with variations',
'Handles all API dependencies in correct order'
]
},
experiment: {
supported: true,
scenarios: [
'Creating an experiment with new pages',
'Creating an experiment with custom events',
'Creating an experiment with audience targeting',
'Setting up multivariate tests'
],
automatic_orchestration: [
'Creates or reuses pages for Web experiments',
'Creates or reuses events for metrics',
'Sets up audiences if needed',
'Configures variations and traffic allocation',
'Links all entities properly'
]
},
campaign: {
supported: true,
scenarios: [
'Creating a campaign with multiple experiments',
'Setting up a campaign with shared pages and events'
],
automatic_orchestration: [
'Creates campaign container',
'Sets up all child experiments',
'Manages shared resources (pages, events)',
'Configures holdback settings'
]
}
};
return templateInfo[entityType];
}
/**
* Get accurate operations based on entity type
*/
static getEntityOperations(entityType, endpoints, endpointString) {
// Special handling for entities that have non-standard operations
const specialOperations = {
ruleset: {
create: false, // Rulesets are auto-created with flags
read: true,
update: true, // Via PATCH with JSON Patch format
delete: false, // Can't delete, only disable
list: false // No list endpoint, accessed via flag
},
rule: {
create: false, // Rules are managed within rulesets
read: true,
update: false, // Updated via ruleset PATCH
delete: false, // Deleted via ruleset PATCH
list: true // Can list rules within a ruleset
},
variation: {
create: true, // Can create variations for flags
read: true,
update: true,
delete: true,
list: true
},
variable_definition: {
create: true,
read: true,
update: true,
delete: true,
list: true
}
};
if (entityType in specialOperations) {
return specialOperations[entityType];
}
// Default detection logic
return {
create: 'create' in endpoints || endpointString.includes('POST'),
read: 'get' in endpoints || endpointString.includes('GET'),
update: 'update' in endpoints || endpointString.includes('PATCH') || endpointString.includes('PUT'),
delete: 'delete' in endpoints || endpointString.includes('DELETE'),
list: 'list' in endpoints || Object.values(endpoints).some(e => String(e).includes('GET') && !String(e).includes('{'))
};
}
/**
* Build comprehensive field documentation
*/
static buildFieldDocumentation(entityType, entityData) {
const fieldDocs = {};
const allFields = new Set([
...(entityData.required || []),
...(entityData.optional || [])
]);
// Get swagger defaults and safety fallbacks
const swaggerDefaults = this.getSwaggerDefaults(entityType);
const safetyFallbacks = this.getSafetyFallbacks(entityType);
for (const field of allFields) {
const fieldDoc = this.getFieldDocumentation(entityType, field);
if (fieldDoc) {
// Enhance with defaults
if (swaggerDefaults[field] !== undefined) {
fieldDoc.default_value = swaggerDefaults[field];
}
else if (safetyFallbacks[field] !== undefined) {
fieldDoc.default_value = safetyFallbacks[field];
}
// Add format information for special fields
if (field === 'conditions' || field === 'audience_conditions') {
fieldDoc.format = 'JSON array of condition objects or "everyone" string';
fieldDoc.update_format = 'Same format for updates';
}
if (field === 'rules' && entityType === 'ruleset') {
fieldDoc.update_format = 'JSON Patch operations (RFC 6902)';
}
fieldDocs[field] = fieldDoc;
}
}
return Object.keys(fieldDocs).length > 0 ? fieldDocs : undefined;
}
/**
* Get swagger defaults for an entity
*/
static getSwaggerDefaults(entityType) {
// Import defaults from ComprehensiveAutoCorrector concepts
const defaults = {
experiment: {
audience_conditions: "everyone",
is_classic: false,
metrics: [],
type: "a/b"
},
project: {
platform: "web",
status: "active"
},
audience: {
archived: false,
segmentation: false
},
page: {
archived: false,
category: "other",
activation_type: "immediate"
},
event: {
category: "other",
is_classic: false
},
attribute: {
archived: false,
condition_type: "custom_attribute"
},
campaign: {
type: "personalization"
},
feature: {
archived: false
},
extension: {
fields: []
},
flag: {
archived: false
},
variation: {
archived: false,
enabled: true
}
};
return defaults[entityType] || {};
}
/**
* Get safety fallbacks for an entity
*/
static getSafetyFallbacks(entityType) {
const fallbacks = {
page: {
activation_type: "immediate",
page_type: "single_url"
},
audience: {
conditions: "[]"
},
experiment: {
status: "not_started",
percentage_included: 1.0
},
flag: {
archived: false,
outlier_filtering_enabled: false,
variable_definitions: {}
},
variation: {
weight: null,
actions: []
}
};
return fallbacks[entityType] || {};
}
/**
* Get usage notes for an entity
*/
static getUsageNotes(entityType) {
const notes = {
experiment: [
"CRITICAL: 'Experiment' means different things by platform:",
"Web Experimentation: Standalone experiment entities with ID, variations, pages",
"Feature Experimentation: Rules with type='a/b' within flag rulesets",
"To query Feature Experimentation A/B tests: Use flags_unified_view WHERE rule_type='a/b'",
"To query Web Experimentation experiments: Use experiments_unified_view WHERE platform='web'",
"The experiments_unified_view combines BOTH into one view with a 'platform' field"
],
ruleset: [
"Rulesets are automatically created when a flag is created (one per environment)",
"Update rulesets using JSON Patch format with Content-Type: application/json-patch+json",
"Rules within a ruleset are evaluated in priority order",
"Cannot create or delete rulesets directly - they're managed through flags",
"Use enable/disable endpoints to control ruleset state"
],
rule: [
"Rules are managed within rulesets, not as standalone entities",
"Rule types: 'a/b' (experiment), 'targeted_delivery' (specific audience), 'rollout' (percentage)",
"Rules are evaluated in the order specified by rule_priorities in the ruleset",
"Percentage values use basis points (10000 = 100%)",
"Each rule must have a unique key within its ruleset"
],
variation: [
"In Feature Experimentation, variations don't have weights (weight field ignored)",
"The 'off' variation is a system default that always exists for flags",
"Variation keys must be unique within a flag",
"Variable values in variations must match the flag's variable definitions"
],
page: [
"Pages can use immediate, manual, polling, callback, dom_changed, or url_changed activation",
"URL patterns support wildcards and regex",
"Conditions are automatically generated from edit_url if not provided",
"Pages can be shared across multiple experiments"
],
flag: [
"Flags are the primary entity in Feature Experimentation",
"Each flag has rulesets that control behavior per environment",
"Variable definitions define the schema for flag values",
"Use template mode for complex flag creation with experiments"
]
};
return notes[entityType] || [];
}
/**
* Get entity relationships
*/
static getEntityRelationships(entityType) {
const relationships = {
ruleset: {
parent: "flag (one ruleset per flag per environment)",
children: ["rules"],
references: ["variations", "environment"]
},
rule: {
parent: "ruleset",
references: ["variations", "audiences", "metrics"]
},
variation: {
parent: "flag (Feature) or experiment (Web)",
references: ["variable_definitions"]
},
flag: {
parent: "project",
children: ["rulesets", "variations", "variable_definitions"],
references: ["environments"]
},
experiment: {
parent: "project (Web) or rule (Feature)",
children: ["variations (Web only)"],
references: ["pages", "events", "audiences", "metrics"]
},
page: {
parent: "project",
references: ["experiments", "campaigns"]
}
};
return relationships[entityType];
}
/**
* Get common patterns for an entity
*/
static getCommonPatterns(entityType) {
const patterns = {
ruleset: [
{
name: "Add A/B test rule",
description: "Add an experiment rule to test variations",
example: {
op: "add",
path: "/rules/my_experiment",
value: {
key: "my_experiment",
type: "a/b",
percentage_included: 10000,
audience_conditions: [],
variations: {
on: { key: "on", percentage_included: 5000 },
variant_1: { key: "variant_1", percentage_included: 5000 }
}
}
}
},
{
name: "Enable ruleset",
description: "Enable a ruleset in an environment",
example: "POST /flags/v1/projects/{project_id}/flags/{flag_key}/environments/{environment_key}/enable"
}
],
page: [
{
name: "Simple URL targeting",
description: "Target a specific page URL",
example: {
name: "Homepage",
edit_url: "https://example.com/",
activation_type: "immediate"
}
},
{
name: "Pattern matching",
description: "Target multiple URLs with wildcards",
example: {
name: "Product Pages",
edit_url: "https://example.com/products/*",
activation_type: "immediate",
activation_code: "function(){return window.location.pathname.includes('/products/');}"
}
}
],
variation: [
{
name: "Feature flag variation",
description: "Variation with variable values",
example: {
key: "treatment",
name: "Treatment Group",
variables: {
"show_banner": true,
"banner_text": "Special Offer!"
}
}
}
]
};
return patterns[entityType];
}
/**
* Get update behavior for an entity
*/
static getUpdateBehavior(entityType) {
const behaviors = {
ruleset: {
method: "PATCH",
format: "JSON Patch (RFC 6902)",
notes: [
"Use Content-Type: application/json-patch+json",
"Operations: add, remove, replace, move, copy, test",
"Path format: /rules/{rule_key} for rules",
"Validate operations with 'test' before applying changes"
]
},
flag: {
method: "PATCH",
format: "Standard JSON merge patch",
notes: [
"Only specified fields are updated",
"Cannot change key after creation",
"Variable definitions updated separately"
]
},
experiment: {
method: "PATCH",
format: "Standard JSON merge patch",
notes: [
"Status changes may have side effects",
"Some fields locked when experiment is running",
"Variations managed separately in Feature Experimentation"
]
},
page: {
method: "PATCH",
format: "Standard JSON merge patch",
notes: [
"URL changes may affect running experiments",
"Activation type changes take effect immediately"
]
}
};
return behaviors[entityType];
}
}
//# sourceMappingURL=EntityDocumentationHelper.js.map