@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
320 lines (271 loc) ⢠9.63 kB
JavaScript
/**
* Comprehensive Swagger Default Extractor
* Extracts ALL default values from Optimizely's OpenAPI specs for auto-correction system
*/
import fs from 'fs/promises';
import { OpenAPIParser } from './open-api-parser.js';
class SwaggerDefaultExtractor {
constructor() {
this.webDefaults = {};
this.featureDefaults = {};
this.allEntities = {};
}
/**
* Extract all defaults from both Web and Feature Experimentation APIs
*/
async extractAllDefaults() {
console.log('š Starting comprehensive default extraction...');
// Download and parse Web Experimentation API
console.log('š„ Fetching Web Experimentation API...');
const webResponse = await fetch('https://api.optimizely.com/v2/swagger.json');
const webSpec = await webResponse.json();
const webParser = new OpenAPIParser(webSpec);
const webSchemas = webParser.getAllSchemas();
// Load Feature Experimentation API
console.log('š„ Loading Feature Experimentation API...');
const featureSpec = JSON.parse(await fs.readFile('./docs/api-reference/api-spec/optimizely-swagger-fx.json', 'utf8'));
const featureParser = new OpenAPIParser(featureSpec);
const featureSchemas = featureParser.getAllSchemas();
console.log('š Extracting defaults from all schemas...');
// Extract from Web API
this.webDefaults = this.extractDefaultsFromSchemas(webSchemas, 'web');
// Extract from Feature API
this.featureDefaults = this.extractDefaultsFromSchemas(featureSchemas, 'feature');
// Combine and normalize
this.allEntities = this.combineAndNormalize();
console.log('š Extraction Summary:');
console.log(` Web entities: ${Object.keys(this.webDefaults).length}`);
console.log(` Feature entities: ${Object.keys(this.featureDefaults).length}`);
console.log(` Total unique entities: ${Object.keys(this.allEntities).length}`);
return this.allEntities;
}
/**
* Extract defaults from a set of schemas
*/
extractDefaultsFromSchemas(schemas, apiType) {
const defaults = {};
for (const [entityName, schema] of Object.entries(schemas)) {
const entityKey = entityName.toLowerCase();
// Skip non-entity schemas
if (this.isUtilitySchema(entityName)) continue;
const entityDefaults = {
apiType,
entityName,
swaggerDefaults: {},
enumFields: {},
requiredFields: schema.required || [],
fieldTypes: {},
fieldDescriptions: {},
examples: {}
};
// Extract field-level defaults
if (schema.properties) {
for (const [fieldName, fieldSchema] of Object.entries(schema.properties)) {
// Extract default values
if (fieldSchema.default !== undefined) {
entityDefaults.swaggerDefaults[fieldName] = fieldSchema.default;
}
// Extract enum values
if (fieldSchema.enum) {
entityDefaults.enumFields[fieldName] = fieldSchema.enum;
}
// Extract field types
entityDefaults.fieldTypes[fieldName] = fieldSchema.type;
// Extract descriptions
if (fieldSchema.description) {
entityDefaults.fieldDescriptions[fieldName] = fieldSchema.description;
}
// Extract examples
if (fieldSchema.example !== undefined) {
entityDefaults.examples[fieldName] = fieldSchema.example;
}
}
}
defaults[entityKey] = entityDefaults;
}
return defaults;
}
/**
* Check if schema is a utility/helper schema vs entity schema
*/
isUtilitySchema(name) {
const utilityPatterns = [
'Error', 'Response', 'Request', 'Result', 'Status', 'Meta',
'Page', 'List', 'Collection', 'Wrapper', 'Container'
];
return utilityPatterns.some(pattern =>
name.includes(pattern) && !['Page'].includes(name) // Page is an actual entity
);
}
/**
* Combine Web and Feature defaults, handling overlaps
*/
combineAndNormalize() {
const combined = {};
// Add all web entities
for (const [key, entity] of Object.entries(this.webDefaults)) {
combined[key] = entity;
}
// Add feature entities, merging if overlap
for (const [key, featureEntity] of Object.entries(this.featureDefaults)) {
if (combined[key]) {
// Merge overlapping entities
combined[key] = this.mergeEntityDefaults(combined[key], featureEntity);
} else {
combined[key] = featureEntity;
}
}
return combined;
}
/**
* Merge defaults from web and feature APIs for same entity
*/
mergeEntityDefaults(webEntity, featureEntity) {
return {
...webEntity,
apiType: 'both',
featureDefaults: featureEntity.swaggerDefaults,
featureEnums: featureEntity.enumFields,
featureFieldTypes: featureEntity.fieldTypes,
swaggerDefaults: {
...webEntity.swaggerDefaults,
...featureEntity.swaggerDefaults
},
enumFields: {
...webEntity.enumFields,
...featureEntity.enumFields
},
fieldTypes: {
...webEntity.fieldTypes,
...featureEntity.fieldTypes
}
};
}
/**
* Generate the comprehensive defaults file
*/
async generateDefaultsFile() {
const output = {
generatedAt: new Date().toISOString(),
totalEntities: Object.keys(this.allEntities).length,
entities: this.allEntities,
// Quick lookup maps
lookups: {
entitiesWithDefaults: this.getEntitiesWithDefaults(),
entitiesWithEnums: this.getEntitiesWithEnums(),
strictModeEntities: this.getStrictModeEntities(),
urlFields: this.getUrlFields(),
enumFieldsByEntity: this.getEnumFieldsByEntity()
}
};
await fs.writeFile('./src/generated/comprehensive-defaults.json', JSON.stringify(output, null, 2));
console.log('ā
Generated comprehensive-defaults.json');
return output;
}
/**
* Get entities that have swagger default values
*/
getEntitiesWithDefaults() {
const withDefaults = {};
for (const [key, entity] of Object.entries(this.allEntities)) {
if (Object.keys(entity.swaggerDefaults).length > 0) {
withDefaults[key] = Object.keys(entity.swaggerDefaults);
}
}
return withDefaults;
}
/**
* Get entities that have enum fields
*/
getEntitiesWithEnums() {
const withEnums = {};
for (const [key, entity] of Object.entries(this.allEntities)) {
if (Object.keys(entity.enumFields).length > 0) {
withEnums[key] = Object.keys(entity.enumFields);
}
}
return withEnums;
}
/**
* Identify entities that should be in strict mode (no auto-correction)
*/
getStrictModeEntities() {
// Events are strict - wrong event types break analytics
// These entities should fail fast, not auto-correct
return ['event', 'metric', 'revenue'];
}
/**
* Find all URL fields across entities
*/
getUrlFields() {
const urlFields = {};
for (const [entityKey, entity] of Object.entries(this.allEntities)) {
const urls = [];
for (const [fieldName, fieldType] of Object.entries(entity.fieldTypes)) {
if (fieldName.includes('url') || fieldName.includes('webhook') || fieldName === 'edit_url') {
urls.push(fieldName);
}
}
if (urls.length > 0) {
urlFields[entityKey] = urls;
}
}
return urlFields;
}
/**
* Get enum fields organized by entity
*/
getEnumFieldsByEntity() {
const organized = {};
for (const [entityKey, entity] of Object.entries(this.allEntities)) {
if (Object.keys(entity.enumFields).length > 0) {
organized[entityKey] = entity.enumFields;
}
}
return organized;
}
/**
* Display comprehensive summary
*/
displaySummary() {
console.log('\nš COMPREHENSIVE DEFAULTS SUMMARY');
console.log('=====================================');
for (const [entityKey, entity] of Object.entries(this.allEntities)) {
console.log(`\nš ${entityKey.toUpperCase()} (${entity.apiType})`);
if (Object.keys(entity.swaggerDefaults).length > 0) {
console.log(' ā
Swagger Defaults:');
for (const [field, value] of Object.entries(entity.swaggerDefaults)) {
console.log(` ${field}: ${JSON.stringify(value)}`);
}
}
if (Object.keys(entity.enumFields).length > 0) {
console.log(' šÆ Enum Fields:');
for (const [field, values] of Object.entries(entity.enumFields)) {
console.log(` ${field}: [${values.slice(0, 3).join(', ')}${values.length > 3 ? '...' : ''}]`);
}
}
if (entity.requiredFields.length > 0) {
console.log(` ā ļø Required: ${entity.requiredFields.join(', ')}`);
}
}
}
}
// CLI execution
async function main() {
try {
const extractor = new SwaggerDefaultExtractor();
await extractor.extractAllDefaults();
await extractor.generateDefaultsFile();
extractor.displaySummary();
console.log('\nā
Comprehensive default extraction complete!');
console.log('š Output: ./src/generated/comprehensive-defaults.json');
} catch (error) {
console.error('ā Error:', error.message);
process.exit(1);
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}
export { SwaggerDefaultExtractor };