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
JavaScript
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);
}