UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

213 lines (183 loc) 7.63 kB
#!/usr/bin/env node /** * 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();