function isSchemaRecord(value: unknown): value is Record { return value !== null && typeof value === 'object' && !Array.isArray(value) } function deepEqualJsonValue(a: unknown, b: unknown): boolean { if (Object.is(a, b)) return true if (typeof a !== typeof b) return false if (Array.isArray(a) && Array.isArray(b)) { return ( a.length === b.length && a.every((value, index) => deepEqualJsonValue(value, b[index])) ) } if (isSchemaRecord(a) && isSchemaRecord(b)) { const aKeys = Object.keys(a) const bKeys = Object.keys(b) return ( aKeys.length === bKeys.length && aKeys.every(key => key in b && deepEqualJsonValue(a[key], b[key])) ) } return false } function matchesJsonSchemaType(type: string, value: unknown): boolean { switch (type) { case 'string': return typeof value === 'string' case 'number': return typeof value === 'number' && Number.isFinite(value) case 'integer': return typeof value === 'number' && Number.isInteger(value) case 'boolean': return typeof value === 'boolean' case 'object': return value !== null && typeof value === 'object' && !Array.isArray(value) case 'array': return Array.isArray(value) case 'null': return value === null default: return true } } function getJsonSchemaTypes(record: Record): string[] { const raw = record.type if (typeof raw === 'string') { return [raw] } if (Array.isArray(raw)) { return raw.filter((value): value is string => typeof value === 'string') } return [] } function schemaAllowsValue(schema: Record, value: unknown): boolean { if (Array.isArray(schema.anyOf)) { return schema.anyOf.some(item => schemaAllowsValue(sanitizeSchemaForOpenAICompat(item), value), ) } if (Array.isArray(schema.oneOf)) { return ( schema.oneOf.filter(item => schemaAllowsValue(sanitizeSchemaForOpenAICompat(item), value), ).length === 1 ) } if (Array.isArray(schema.allOf)) { return schema.allOf.every(item => schemaAllowsValue(sanitizeSchemaForOpenAICompat(item), value), ) } if ('const' in schema && !deepEqualJsonValue(schema.const, value)) { return false } if (Array.isArray(schema.enum)) { if (!schema.enum.some(item => deepEqualJsonValue(item, value))) { return false } } const types = getJsonSchemaTypes(schema) if (types.length > 0 && !types.some(type => matchesJsonSchemaType(type, value))) { return false } return true } function sanitizeTypeField(record: Record): void { const allowed = new Set([ 'string', 'number', 'integer', 'boolean', 'object', 'array', 'null', ]) const raw = record.type if (typeof raw === 'string') { if (!allowed.has(raw)) delete record.type return } if (!Array.isArray(raw)) return const filtered = raw.filter( (value, index): value is string => typeof value === 'string' && allowed.has(value) && raw.indexOf(value) === index, ) if (filtered.length === 0) { delete record.type } else if (filtered.length === 1) { record.type = filtered[0] } else { record.type = filtered } } /** * Sanitize loose/invalid JSON Schema into a form OpenAI-compatible providers * are more likely to accept. This is intentionally defensive for external MCP * servers that may advertise imperfect schemas. */ export function sanitizeSchemaForOpenAICompat( schema: unknown, ): Record { if (!isSchemaRecord(schema)) { return {} } const record = { ...schema } delete record.$schema delete record.propertyNames sanitizeTypeField(record) if (isSchemaRecord(record.properties)) { const sanitizedProps: Record = {} for (const [key, value] of Object.entries(record.properties)) { sanitizedProps[key] = sanitizeSchemaForOpenAICompat(value) } record.properties = sanitizedProps } if ('items' in record) { if (Array.isArray(record.items)) { record.items = record.items.map(item => sanitizeSchemaForOpenAICompat(item), ) } else { record.items = sanitizeSchemaForOpenAICompat(record.items) } } for (const key of ['anyOf', 'oneOf', 'allOf'] as const) { if (Array.isArray(record[key])) { record[key] = record[key].map(item => sanitizeSchemaForOpenAICompat(item), ) } } if (Array.isArray(record.required) && isSchemaRecord(record.properties)) { record.required = record.required.filter( (value): value is string => typeof value === 'string' && value in record.properties, ) } const schemaWithoutEnum = { ...record } delete schemaWithoutEnum.enum if (Array.isArray(record.enum)) { const filteredEnum = record.enum.filter(value => schemaAllowsValue(schemaWithoutEnum, value), ) if (filteredEnum.length > 0) { record.enum = filteredEnum } else { delete record.enum } } const schemaWithoutConst = { ...record } delete schemaWithoutConst.const if ('const' in record && !schemaAllowsValue(schemaWithoutConst, record.const)) { delete record.const } const schemaWithoutDefault = { ...record } delete schemaWithoutDefault.default if ( 'default' in record && !schemaAllowsValue(schemaWithoutDefault, record.default) ) { delete record.default } return record }