UNPKG

reactbits-mcp-server

Version:

MCP Server for React Bits - Access 99+ React components with animations, backgrounds, and UI elements

117 lines (116 loc) 3.87 kB
import { z } from 'zod'; import { logError } from './logger.js'; /** * Validate and sanitize input parameters for MCP requests * @param method - The method name being called * @param params - The parameters to validate * @returns Validated and sanitized parameters */ export function validateAndSanitizeParams(method, params) { try { // Basic sanitization - remove null/undefined values and trim strings const sanitized = sanitizeObject(params); // Method-specific validation switch (method) { case 'get_component': case 'get_component_demo': case 'get_component_metadata': return validateComponentParams(sanitized); case 'search_components': return validateSearchParams(sanitized); case 'list_components': return validateListParams(sanitized); default: return sanitized; } } catch (error) { logError(`Validation error for method ${method}`, error); throw new Error(`Invalid parameters for ${method}: ${error instanceof Error ? error.message : String(error)}`); } } /** * Sanitize an object by removing null/undefined values and trimming strings * @param obj - Object to sanitize * @returns Sanitized object */ function sanitizeObject(obj) { if (obj === null || obj === undefined) { return {}; } if (typeof obj !== 'object') { return obj; } const sanitized = {}; for (const [key, value] of Object.entries(obj)) { if (value !== null && value !== undefined) { if (typeof value === 'string') { const trimmed = value.trim(); if (trimmed.length > 0) { sanitized[key] = trimmed; } } else if (typeof value === 'object') { sanitized[key] = sanitizeObject(value); } else { sanitized[key] = value; } } } return sanitized; } /** * Validate component-related parameters * @param params - Parameters to validate * @returns Validated parameters */ function validateComponentParams(params) { const schema = z.object({ componentName: z.string().min(1, 'Component name is required'), category: z.enum(['Animations', 'Backgrounds', 'Components', 'TextAnimations']).optional() }); return schema.parse(params); } /** * Validate search parameters * @param params - Parameters to validate * @returns Validated parameters */ function validateSearchParams(params) { const schema = z.object({ query: z.string().min(1, 'Search query is required'), category: z.enum(['Animations', 'Backgrounds', 'Components', 'TextAnimations']).optional() }); return schema.parse(params); } /** * Validate list parameters * @param params - Parameters to validate * @returns Validated parameters */ function validateListParams(params) { const schema = z.object({ category: z.enum(['Animations', 'Backgrounds', 'Components', 'TextAnimations']).optional() }); return schema.parse(params); } /** * Validate component name format * @param componentName - Component name to validate * @returns True if valid, false otherwise */ export function isValidComponentName(componentName) { // Component names should be PascalCase and contain only letters and numbers const componentNameRegex = /^[A-Z][a-zA-Z0-9]*$/; return componentNameRegex.test(componentName); } /** * Validate category name * @param category - Category to validate * @returns True if valid, false otherwise */ export function isValidCategory(category) { const validCategories = ['Animations', 'Backgrounds', 'Components', 'TextAnimations']; return validCategories.includes(category); }