@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
213 lines (183 loc) • 7.63 kB
JavaScript
/**
* API Field Value Generator
*
* This script generates a JSON file detailing the acceptable values (enums, defaults, examples, etc.)
* for a specific field within the request body of a given API endpoint.
*
* Usage:
* node generate-values.js <swagger-file.json> <METHOD> <path> <field-name> <output-file.json>
*
* Example:
* node generate-values.js api.json POST /experiments type experiment_type_values.json
*/
import fs from 'fs/promises';
import path from 'path';
import fetch from 'node-fetch';
class ValueGenerator {
constructor(spec) {
this.spec = spec;
this.specType = spec.openapi ? 'openapi' : 'swagger';
}
/**
* Resolves a $ref pointer to its actual schema object.
* @param {string} ref - The reference string (e.g., '#/definitions/Experiment').
* @returns {object} The resolved schema object.
*/
_resolveRef(ref) {
const parts = ref.split('/');
if (parts[0] !== '#') {
throw new Error(`External $ref pointers are not supported: ${ref}`);
}
let current = this.spec;
for (let i = 1; i < parts.length; i++) {
current = current[parts[i]];
if (!current) {
throw new Error(`Could not resolve $ref: ${ref}`);
}
}
return current;
}
/**
* Finds the schema definition for an endpoint's request body.
* @param {object} operation - The endpoint's operation object from the spec.
* @returns {object|null} The schema for the request body, or null if not found.
*/
_getRequestBodySchema(operation) {
let schemaRef;
if (this.specType === 'openapi' && operation.requestBody?.content?.['application/json']?.schema) {
schemaRef = operation.requestBody.content['application/json'].schema;
} else if (this.specType === 'swagger' && operation.parameters) {
const bodyParam = operation.parameters.find(p => p.in === 'body');
if (bodyParam) {
schemaRef = bodyParam.schema;
}
}
if (!schemaRef) return null;
return schemaRef.$ref ? this._resolveRef(schemaRef.$ref) : schemaRef;
}
/**
* Recursively finds all occurrences of a property within a schema.
* @param {object} schema - The current schema object to search within.
* @param {string} propertyName - The name of the property to find.
* @param {string} currentPath - The dot-notation path to the current schema.
* @param {array} results - The array to store found results.
*/
_findPropertyRecursive(schema, propertyName, currentPath, results) {
if (!schema) return;
// If the schema is a reference, resolve it first.
if (schema.$ref) {
schema = this._resolveRef(schema.$ref);
}
if (schema.type === 'object' && schema.properties) {
for (const [key, subSchema] of Object.entries(schema.properties)) {
const newPath = currentPath ? `${currentPath}.${key}` : key;
if (key.toLowerCase() === propertyName.toLowerCase()) {
results.push({
path: newPath,
details: this._extractFieldInfo(subSchema)
});
}
// Continue searching deeper
this._findPropertyRecursive(subSchema, propertyName, newPath, results);
}
} else if (schema.type === 'array' && schema.items) {
// Path doesn't extend for arrays, but we search within its items.
this._findPropertyRecursive(schema.items, propertyName, currentPath, results);
}
}
/**
* Extracts relevant validation and example information from a property's schema.
* @param {object} propSchema - The schema object for a single property.
* @returns {object} A clean object with all relevant details.
*/
_extractFieldInfo(propSchema) {
if (propSchema.$ref) {
propSchema = this._resolveRef(propSchema.$ref);
}
const info = {
description: propSchema.description,
type: propSchema.type,
default: propSchema.default,
example: propSchema.example,
enum: propSchema.enum,
pattern: propSchema.pattern,
minimum: propSchema.minimum,
maximum: propSchema.maximum,
minLength: propSchema.minLength,
maxLength: propSchema.maxLength,
};
// Clean up undefined properties for a tidier output
return Object.fromEntries(Object.entries(info).filter(([_, v]) => v !== undefined));
}
/**
* Main generation logic.
* @param {string} method - The HTTP method (e.g., 'POST').
* @param {string} apiPath - The endpoint path (e.g., '/experiments').
* @param {string} fieldName - The name of the field to find values for (e.g., 'type').
* @returns {object} An object containing the found field information.
*/
generateValues(method, apiPath, fieldName) {
const operation = this.spec.paths?.[apiPath]?.[method.toLowerCase()];
if (!operation) {
throw new Error(`Endpoint not found: ${method.toUpperCase()} ${apiPath}`);
}
const requestBodySchema = this._getRequestBodySchema(operation);
if (!requestBodySchema) {
return {
endpoint: `${method.toUpperCase()} ${apiPath}`,
fieldName: fieldName,
locations: [],
message: "No request body schema found for this endpoint."
};
}
const results = [];
this._findPropertyRecursive(requestBodySchema, fieldName, '', results);
return {
endpoint: `${method.toUpperCase()} ${apiPath}`,
fieldName: fieldName,
locations: results.length > 0 ? results : `Field '${fieldName}' not found in the request body.`,
};
}
}
async function main() {
const args = process.argv.slice(2);
if (args.length !== 5) {
console.error(`
Usage:
node generate-values.js <swagger-file|url> <METHOD> <path> <field-name> <output-file.json>
Description:
Generates a JSON file detailing acceptable values for a specific field
within the request body of a given API endpoint.
Example:
node generate-values.js api.json POST /experiments status experiment_status_values.json
node generate-values.js https://api.optimizely.com/v2/swagger.json PATCH /experiments/{experiment_id} type experiment_update_type.json
`);
process.exit(1);
}
const [swaggerLocation, method, apiPath, fieldName, outputFile] = args;
try {
console.log(`- Loading API spec from: ${swaggerLocation}`);
let spec;
if (swaggerLocation.startsWith('http')) {
const response = await fetch(swaggerLocation);
if (!response.ok) throw new Error(`Failed to fetch spec: ${response.statusText}`);
spec = await response.json();
} else {
const content = await fs.readFile(swaggerLocation, 'utf-8');
spec = JSON.parse(content);
}
console.log('✔ API spec loaded successfully.');
const generator = new ValueGenerator(spec);
console.log(`- Searching for field '${fieldName}' in endpoint '${method.toUpperCase()} ${apiPath}'...`);
const values = generator.generateValues(method, apiPath, fieldName);
console.log('✔ Search complete.');
const outputContent = JSON.stringify(values, null, 2);
await fs.writeFile(outputFile, outputContent, 'utf-8');
console.log(`✅ Success! Acceptable values written to: ${outputFile}`);
} catch (error) {
console.error(`❌ Error: ${error.message}`);
process.exit(1);
}
}
// main();