@statsig/mcp-test
Version:
Statsig MCP Server (TEST VERSION) - Making your AI context-aware with feature flags, experiments, and analytics
198 lines (197 loc) • 6.71 kB
JavaScript
import { z } from "zod";
export async function fetchOpenApiSpec(specUrl) {
const response = await fetch(specUrl);
if (!response.ok) {
throw new Error(`Error fetching openapi spec. Status: ${response.status}`);
}
const json = await response.json();
return json;
}
const methods = [
"get",
"post",
"put",
"delete",
"options",
"head",
"patch",
"trace",
];
function resolveReference(ref, components) {
const parts = ref.replace(/^#\/components\//, "").split("/");
let result = components;
for (const part of parts) {
result = result?.[part];
if (!result)
throw new Error(`Could not resolve reference: ${ref}`);
}
return result;
}
function convertSchemaObjectToZod(schemaObject, components) {
if ("$ref" in schemaObject) {
const resolved = resolveReference(schemaObject.$ref, components ?? {});
return convertSchemaObjectToZod(resolved, components);
}
if (schemaObject.enum && schemaObject.enum.length > 0) {
const enumValues = schemaObject.enum;
return z.enum(enumValues);
}
if (schemaObject.allOf) {
return schemaObject.allOf
.map((s) => convertSchemaObjectToZod(s, components))
.reduce((a, b) => z.intersection(a, b));
}
if (schemaObject.oneOf) {
return z.union(schemaObject.oneOf.map((s) => convertSchemaObjectToZod(s, components)));
}
if (schemaObject.anyOf) {
return z.union(schemaObject.anyOf.map((s) => convertSchemaObjectToZod(s, components)));
}
if (schemaObject.type) {
let base;
switch (schemaObject.type) {
case "string":
base = z.string();
break;
case "number":
case "integer":
base = z.number();
break;
case "boolean":
base = z.boolean();
break;
case "array":
base = z.array(convertSchemaObjectToZod(schemaObject.items, components));
break;
case "object":
const shape = {};
const required = new Set(schemaObject.required ?? []);
for (const [key, prop] of Object.entries(schemaObject.properties ?? {})) {
const zodProp = convertSchemaObjectToZod(prop, components);
shape[key] = required.has(key) ? zodProp : zodProp.optional();
}
base = z.object(shape);
break;
default:
base = z.any();
}
if (schemaObject.nullable) {
base = base.nullable();
}
if (schemaObject.description) {
base = base.describe(schemaObject.description);
}
return base;
}
return z.any();
}
function ensureParameterObject(parameter) {
return "$ref" in parameter ? false : true;
}
function convertParameterToZod(parameter) {
if (!parameter.schema) {
return undefined;
}
let schema = convertSchemaObjectToZod(parameter.schema);
if (!parameter.required) {
schema = schema.optional();
}
if (parameter.description) {
schema = schema.describe(parameter.description);
}
return schema;
}
function convertParametersToZod(parameters) {
if (!parameters) {
return {};
}
return Object.fromEntries(parameters
.filter(ensureParameterObject)
.map((param) => {
const zodSchema = convertParameterToZod(param);
return zodSchema ? [param.name, zodSchema] : null;
})
.filter((entry) => entry !== null));
}
function convertRequestBodyToZod(requestBody, components) {
if (!requestBody)
return undefined;
const actual = "$ref" in requestBody
? resolveReference(requestBody.$ref, components)
: requestBody;
const content = actual.content ?? {};
const mediaTypeObject = content["application/json"];
if (!mediaTypeObject?.schema)
return undefined;
const schema = convertSchemaObjectToZod(mediaTypeObject.schema, components);
const result = {};
if (actual.required) {
result["application/json"] = schema;
}
else {
result["application/json"] = schema.optional();
}
return result;
}
function mergeParameters(operations) {
const allParameters = {};
const pathParameters = new Set();
const methodDescriptions = {};
for (const [method, operation] of Object.entries(operations)) {
methodDescriptions[method] = {
summary: operation.summary,
description: operation.description,
};
const methodParams = convertParametersToZod(operation.parameters);
for (const [paramName, paramSchema] of Object.entries(methodParams)) {
if (allParameters[paramName]) {
allParameters[paramName] = z.union([
allParameters[paramName],
paramSchema,
]);
}
else {
allParameters[paramName] = paramSchema;
}
}
const methodPathParams = (operation.parameters ?? [])
.filter((p) => "in" in p && p.in === "path")
.map((p) => ("in" in p ? p.name : null))
.filter((p) => p != null);
methodPathParams.forEach((param) => pathParameters.add(param));
}
return {
parameters: allParameters,
pathParameters: Array.from(pathParameters),
methodDescriptions,
};
}
export function generateZodSchema(spec) {
const schema = {};
const components = spec.components ?? {};
for (const [path, pathItem] of Object.entries(spec.paths)) {
const availableMethods = [];
const operationsByMethod = {};
for (const method of methods) {
const operation = pathItem[method];
if (operation) {
availableMethods.push(method);
operationsByMethod[method] = operation;
}
}
if (availableMethods.length === 0) {
continue;
}
const { parameters, pathParameters, methodDescriptions } = mergeParameters(operationsByMethod);
schema[path] = {
methods: availableMethods,
summary: operationsByMethod[availableMethods[0]]?.summary,
description: operationsByMethod[availableMethods[0]]?.description,
parameters,
requestBody: convertRequestBodyToZod(operationsByMethod[availableMethods[0]].requestBody, components),
pathParameters,
methodDescriptions,
};
}
return schema;
}