Skip to main content
Glama
ezhou89

Medical Research MCP Suite

by ezhou89
validators.ts9.8 kB
// src/utils/validators.ts export interface ValidationResult { isValid: boolean; errors: string[]; } export interface ValidationRule<T> { field: keyof T; required?: boolean; type?: 'string' | 'number' | 'boolean' | 'array' | 'object'; minLength?: number; maxLength?: number; min?: number; max?: number; pattern?: RegExp; enum?: any[]; custom?: (value: any) => boolean | string; } export class Validator { static validate<T>(data: any, rules: ValidationRule<T>[]): ValidationResult { const errors: string[] = []; for (const rule of rules) { const value = data[rule.field]; const fieldName = String(rule.field); // Check required fields if (rule.required && (value === undefined || value === null || value === '')) { errors.push(`${fieldName} is required`); continue; } // Skip validation if field is not required and empty if (!rule.required && (value === undefined || value === null || value === '')) { continue; } // Type validation if (rule.type) { if (!this.validateType(value, rule.type)) { errors.push(`${fieldName} must be of type ${rule.type}`); continue; } } // String validations if (typeof value === 'string') { if (rule.minLength && value.length < rule.minLength) { errors.push(`${fieldName} must be at least ${rule.minLength} characters long`); } if (rule.maxLength && value.length > rule.maxLength) { errors.push(`${fieldName} must be no more than ${rule.maxLength} characters long`); } if (rule.pattern && !rule.pattern.test(value)) { errors.push(`${fieldName} format is invalid`); } } // Number validations if (typeof value === 'number') { if (rule.min !== undefined && value < rule.min) { errors.push(`${fieldName} must be at least ${rule.min}`); } if (rule.max !== undefined && value > rule.max) { errors.push(`${fieldName} must be no more than ${rule.max}`); } } // Enum validation if (rule.enum && !rule.enum.includes(value)) { errors.push(`${fieldName} must be one of: ${rule.enum.join(', ')}`); } // Custom validation if (rule.custom) { const customResult = rule.custom(value); if (customResult !== true) { errors.push(typeof customResult === 'string' ? customResult : `${fieldName} is invalid`); } } } return { isValid: errors.length === 0, errors, }; } private static validateType(value: any, type: string): boolean { switch (type) { case 'string': return typeof value === 'string'; case 'number': return typeof value === 'number' && !isNaN(value); case 'boolean': return typeof value === 'boolean'; case 'array': return Array.isArray(value); case 'object': return typeof value === 'object' && value !== null && !Array.isArray(value); default: return true; } } } // Predefined validation rules for common use cases export const CommonValidators = { nctId: (value: string) => { const nctPattern = /^NCT\d{8}$/; return nctPattern.test(value) || 'NCT ID must be in format NCT########'; }, pmid: (value: string) => { const pmidPattern = /^\d+$/; return pmidPattern.test(value) || 'PMID must be a numeric string'; }, drugName: (value: string) => { if (!value || value.trim().length === 0) { return 'Drug name cannot be empty'; } if (value.length > 200) { return 'Drug name too long'; } // Basic sanitation check const dangerousPattern = /<script|javascript:|data:/i; return !dangerousPattern.test(value) || 'Drug name contains invalid characters'; }, dateRange: (dateRange: { from?: string; to?: string }) => { if (!dateRange || typeof dateRange !== 'object') { return 'Date range must be an object'; } const datePattern = /^\d{4}-\d{2}-\d{2}$/; if (dateRange.from && !datePattern.test(dateRange.from)) { return 'From date must be in YYYY-MM-DD format'; } if (dateRange.to && !datePattern.test(dateRange.to)) { return 'To date must be in YYYY-MM-DD format'; } if (dateRange.from && dateRange.to) { const fromDate = new Date(dateRange.from); const toDate = new Date(dateRange.to); if (fromDate > toDate) { return 'From date must be before to date'; } } return true; }, pageSize: (value: number) => { if (value < 1) return 'Page size must be at least 1'; if (value > 1000) return 'Page size cannot exceed 1000'; return true; }, analysisDepth: (value: string) => { const validDepths = ['basic', 'detailed', 'comprehensive']; return validDepths.includes(value) || `Analysis depth must be one of: ${validDepths.join(', ')}`; }, timeframe: (value: string) => { const validTimeframes = ['1year', '2years', '5years', 'all']; return validTimeframes.includes(value) || `Timeframe must be one of: ${validTimeframes.join(', ')}`; }, email: (value: string) => { const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailPattern.test(value) || 'Invalid email format'; }, url: (value: string) => { try { new URL(value); return true; } catch { return 'Invalid URL format'; } }, }; // Validation rule sets for different API endpoints export const ValidationRules = { clinicalTrialsSearch: [ { field: 'condition' as const, type: 'string' as const, custom: CommonValidators.drugName }, { field: 'intervention' as const, type: 'string' as const, custom: CommonValidators.drugName }, { field: 'pageSize' as const, type: 'number' as const, custom: CommonValidators.pageSize }, { field: 'phase' as const, type: 'array' as const }, { field: 'status' as const, type: 'array' as const }, ], clinicalTrialsStudy: [ { field: 'nctId' as const, required: true, type: 'string' as const, custom: CommonValidators.nctId }, ], pubmedSearch: [ { field: 'query' as const, required: true, type: 'string' as const, minLength: 1, maxLength: 500 }, { field: 'maxResults' as const, type: 'number' as const, custom: CommonValidators.pageSize }, { field: 'dateRange' as const, type: 'object' as const, custom: CommonValidators.dateRange }, ], fdaDrugSearch: [ { field: 'drugName' as const, type: 'string' as const, custom: CommonValidators.drugName }, { field: 'activeIngredient' as const, type: 'string' as const, custom: CommonValidators.drugName }, { field: 'limit' as const, type: 'number' as const, custom: CommonValidators.pageSize }, ], fdaAdverseEvents: [ { field: 'drugName' as const, required: true, type: 'string' as const, custom: CommonValidators.drugName }, { field: 'dateRange' as const, type: 'object' as const, custom: CommonValidators.dateRange }, { field: 'limit' as const, type: 'number' as const, custom: CommonValidators.pageSize }, ], comprehensiveAnalysis: [ { field: 'drugName' as const, required: true, type: 'string' as const, custom: CommonValidators.drugName }, { field: 'condition' as const, required: true, type: 'string' as const, custom: CommonValidators.drugName }, { field: 'analysisDepth' as const, type: 'string' as const, custom: CommonValidators.analysisDepth }, ], drugSafetyProfile: [ { field: 'drugName' as const, required: true, type: 'string' as const, custom: CommonValidators.drugName }, { field: 'timeframe' as const, type: 'string' as const, custom: CommonValidators.timeframe }, { field: 'includeTrials' as const, type: 'boolean' as const }, { field: 'includeFDA' as const, type: 'boolean' as const }, ], competitiveLandscape: [ { field: 'targetCondition' as const, required: true, type: 'string' as const, custom: CommonValidators.drugName }, { field: 'competitorDrugs' as const, type: 'array' as const }, { field: 'includeGlobal' as const, type: 'boolean' as const }, ], }; // Sanitization functions export const Sanitizer = { cleanString: (input: string): string => { if (typeof input !== 'string') return ''; return input .trim() .replace(/[<>]/g, '') // Remove angle brackets .replace(/javascript:/gi, '') // Remove javascript: protocol .replace(/data:/gi, '') // Remove data: protocol .replace(/on\w+=/gi, '') // Remove event handlers .substring(0, 1000); // Limit length }, cleanArray: (input: any[]): any[] => { if (!Array.isArray(input)) return []; return input .filter(item => item !== null && item !== undefined) .map(item => typeof item === 'string' ? Sanitizer.cleanString(item) : item) .slice(0, 100); // Limit array size }, cleanObject: (input: any): any => { if (typeof input !== 'object' || input === null) return {}; const cleaned: any = {}; const allowedKeys = 50; // Limit object size let keyCount = 0; for (const [key, value] of Object.entries(input)) { if (keyCount >= allowedKeys) break; const cleanKey = Sanitizer.cleanString(key); if (cleanKey.length > 0) { if (typeof value === 'string') { cleaned[cleanKey] = Sanitizer.cleanString(value); } else if (Array.isArray(value)) { cleaned[cleanKey] = Sanitizer.cleanArray(value); } else if (typeof value === 'object' && value !== null) { cleaned[cleanKey] = Sanitizer.cleanObject(value); } else { cleaned[cleanKey] = value; } keyCount++; } } return cleaned; }, }; export default Validator;

Latest Blog Posts

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/ezhou89/medical-research-mcp-suite'

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