Skip to main content
Glama

Quickbase MCP Server

MIT License
2
4
  • Apple
  • Linux
validation.ts7.23 kB
import { z } from 'zod'; import { createLogger } from './logger'; const logger = createLogger('Validation'); /** * Custom validation error with additional context */ export class ValidationError extends Error { constructor( message: string, public readonly context?: { toolName?: string; issues?: z.ZodIssue[]; originalParams?: unknown; } ) { super(message); this.name = 'ValidationError'; } } /** * Type guard for valid JSON Schema objects */ function isValidJSONSchema(schema: unknown): schema is Record<string, unknown> { return typeof schema === 'object' && schema !== null && 'type' in schema; } /** * Schema cache for performance optimization */ class SchemaCache { private static cache = new Map<string, z.ZodSchema>(); private static readonly MAX_SIZE = 100; static get(key: string): z.ZodSchema | undefined { 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: string, schema: z.ZodSchema): void { // 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(): void { this.cache.clear(); } static getStats(): { size: number; maxSize: number } { return { size: this.cache.size, maxSize: this.MAX_SIZE }; } } /** * Convert JSON Schema property to Zod type */ function convertPropertyToZod(prop: Record<string, unknown>): z.ZodTypeAny { if (!prop || typeof prop !== 'object') { logger.warn('Invalid property schema, defaulting to unknown', { prop }); return 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 z.enum(enumValues as [string, ...string[]]); } const literals = enumValues.map(v => z.literal(v)); if (literals.length === 1) { return literals[0]; } return z.union([literals[0], literals[1], ...literals.slice(2)]); } // Handle primitive types switch (type) { case 'string': { let stringSchema = z.string(); if (format === 'email') stringSchema = stringSchema.email(); if (format === 'uri') stringSchema = stringSchema.url(); return stringSchema; } case 'number': return z.number(); case 'integer': return z.number().int(); case 'boolean': return z.boolean(); case 'array': if (items && typeof items === 'object') { const itemSchema = convertPropertyToZod(items as Record<string, unknown>); return z.array(itemSchema); } return z.array(z.unknown()); case 'object': // For nested objects, we'd need recursive handling // For now, treat as record of unknown values return z.record(z.unknown()); default: logger.warn('Unsupported schema type, defaulting to unknown', { type, prop }); return z.unknown(); } } /** * Create Zod schema for MCP server tool registration * Returns object with property schemas for MCP server */ export function createMcpZodSchema(schema: Record<string, unknown>): Record<string, z.ZodTypeAny> { if (!isValidJSONSchema(schema)) { logger.warn('Invalid schema provided to createMcpZodSchema', { schema }); return {}; } if (schema.type !== 'object' || !schema.properties) { return {}; } const properties = schema.properties as Record<string, Record<string, unknown>>; const required = (schema.required as string[]) || []; const zodSchemaObj: Record<string, z.ZodTypeAny> = {}; 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 */ export function createValidationSchema(schema: Record<string, unknown>): z.ZodSchema { if (!isValidJSONSchema(schema)) { logger.warn('Invalid schema provided to createValidationSchema', { schema }); return z.object({}); } // Create cache key const cacheKey = JSON.stringify(schema); // Check cache first const cached = SchemaCache.get(cacheKey); if (cached) { return cached; } let zodSchema: z.ZodSchema; try { if (schema.type === 'object' && schema.properties) { const mcpSchema = createMcpZodSchema(schema); zodSchema = z.object(mcpSchema); } else { // Non-object schemas (rare but possible) zodSchema = z.object({}); } } catch (error) { logger.error('Error creating validation schema', { error, schema }); zodSchema = z.object({}); } // Cache the result SchemaCache.set(cacheKey, zodSchema); return zodSchema; } /** * Validate parameters using JSON Schema with comprehensive error handling */ export function validateParams<T>( params: unknown, schema: Record<string, unknown>, toolName?: string ): T { 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 as T; } catch (error) { if (error instanceof 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 export const createZodSchema = createMcpZodSchema; export const createZodObjectSchema = createValidationSchema;

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/danielbushman/MCP-Quickbase'

If you have feedback or need assistance with the MCP directory API, please join our Discord server