UNPKG

mcp-quickbase

Version:

Work with Quickbase via Model Context Protocol

230 lines 7.73 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createZodObjectSchema = exports.createZodSchema = exports.ValidationError = void 0; exports.createMcpZodSchema = createMcpZodSchema; exports.createValidationSchema = createValidationSchema; exports.validateParams = validateParams; const zod_1 = require("zod"); const logger_1 = require("./logger"); const logger = (0, logger_1.createLogger)("Validation"); /** * Custom validation error with additional context */ class ValidationError extends Error { constructor(message, context) { super(message); this.context = context; this.name = "ValidationError"; } } exports.ValidationError = ValidationError; /** * Type guard for valid JSON Schema objects */ function isValidJSONSchema(schema) { return typeof schema === "object" && schema !== null && "type" in schema; } /** * Schema cache for performance optimization */ class SchemaCache { static get(key) { const value = this.cache.get(key); if (value !== undefined) { // Move to end for LRU behavior this.cache.delete(key); this.cache.set(key, value); } return value; } static set(key, schema) { // Remove existing key if present (for LRU update) if (this.cache.has(key)) { this.cache.delete(key); } // Evict oldest entries if cache is full (proper LRU eviction) while (this.cache.size >= this.MAX_SIZE) { const firstKey = this.cache.keys().next().value; if (firstKey !== undefined) { this.cache.delete(firstKey); } else { break; // Safety check } } this.cache.set(key, schema); } static clear() { this.cache.clear(); } static getStats() { return { size: this.cache.size, maxSize: this.MAX_SIZE }; } } SchemaCache.cache = new Map(); SchemaCache.MAX_SIZE = 100; /** * Convert JSON Schema property to Zod type */ function convertPropertyToZod(prop) { if (!prop || typeof prop !== "object") { logger.warn("Invalid property schema, defaulting to unknown", { prop }); return zod_1.z.unknown(); } const { type, enum: enumValues, items, format } = prop; // Handle enum constraints if (enumValues && Array.isArray(enumValues) && enumValues.length > 0) { if (enumValues.every((v) => typeof v === "string")) { return zod_1.z.enum(enumValues); } const literals = enumValues.map((v) => zod_1.z.literal(v)); if (literals.length === 1) { return literals[0]; } return zod_1.z.union([literals[0], literals[1], ...literals.slice(2)]); } // Handle primitive types switch (type) { case "string": { let stringSchema = zod_1.z.string(); if (format === "email") stringSchema = stringSchema.email(); if (format === "uri") stringSchema = stringSchema.url(); return stringSchema; } case "number": return zod_1.z.number(); case "integer": return zod_1.z.number().int(); case "boolean": return zod_1.z.boolean(); case "array": if (items && typeof items === "object") { const itemSchema = convertPropertyToZod(items); return zod_1.z.array(itemSchema); } return zod_1.z.array(zod_1.z.unknown()); case "object": // For nested objects, we'd need recursive handling // For now, treat as record of unknown values return zod_1.z.record(zod_1.z.unknown()); default: logger.warn("Unsupported schema type, defaulting to unknown", { type, prop, }); return zod_1.z.unknown(); } } /** * Create Zod schema for MCP server tool registration * Returns object with property schemas for MCP server */ function createMcpZodSchema(schema) { if (!isValidJSONSchema(schema)) { logger.warn("Invalid schema provided to createMcpZodSchema", { schema }); return {}; } if (schema.type !== "object" || !schema.properties) { return {}; } const properties = schema.properties; const required = schema.required || []; const zodSchemaObj = {}; try { Object.entries(properties).forEach(([key, prop]) => { let zodType = convertPropertyToZod(prop); // Make optional if not in required array if (!required.includes(key)) { zodType = zodType.optional(); } zodSchemaObj[key] = zodType; }); } catch (error) { logger.error("Error creating MCP Zod schema", { error, schema }); return {}; } return zodSchemaObj; } /** * Create Zod object schema for parameter validation * Returns complete Zod schema for validation */ function createValidationSchema(schema) { if (!isValidJSONSchema(schema)) { logger.warn("Invalid schema provided to createValidationSchema", { schema, }); return zod_1.z.object({}); } // Create cache key const cacheKey = JSON.stringify(schema); // Check cache first const cached = SchemaCache.get(cacheKey); if (cached) { return cached; } let zodSchema; try { if (schema.type === "object" && schema.properties) { const mcpSchema = createMcpZodSchema(schema); zodSchema = zod_1.z.object(mcpSchema); } else { // Non-object schemas (rare but possible) zodSchema = zod_1.z.object({}); } } catch (error) { logger.error("Error creating validation schema", { error, schema }); zodSchema = zod_1.z.object({}); } // Cache the result SchemaCache.set(cacheKey, zodSchema); return zodSchema; } /** * Validate parameters using JSON Schema with comprehensive error handling */ function validateParams(params, schema, toolName) { try { const zodSchema = createValidationSchema(schema); logger.debug("Validating parameters", { toolName, paramsType: typeof params, schemaType: schema.type, }); const result = zodSchema.parse(params); logger.debug("Parameter validation successful", { toolName }); return result; } catch (error) { if (error instanceof zod_1.z.ZodError) { const context = toolName ? ` in tool "${toolName}"` : ""; const issues = error.errors.map((err) => { const path = err.path.length > 0 ? err.path.join(".") : "root"; return `${path}: ${err.message}`; }); const validationError = new ValidationError(`Parameter validation failed${context}: ${issues.join(", ")}`, { toolName, issues: error.errors, originalParams: params, }); logger.error("Parameter validation failed", { toolName, error: validationError.message, issues: error.errors, params: typeof params === "object" ? "object" : params, }); throw validationError; } logger.error("Unexpected validation error", { error, toolName }); throw error; } } // Legacy exports for backward compatibility exports.createZodSchema = createMcpZodSchema; exports.createZodObjectSchema = createValidationSchema; //# sourceMappingURL=validation.js.map