Skip to main content
Glama

Motion.dev MCP Server

validators.ts8.55 kB
/** * Input validation utilities for Motion.dev MCP server */ import { z } from 'zod'; import { Framework, DocumentationCategory, AnimationType } from '../types/motion.js'; import { createValidationError } from './errors.js'; // Framework validation export const FrameworkSchema = z.enum(['react', 'js', 'vue']); export function validateFramework(value: unknown): Framework { try { return FrameworkSchema.parse(value); } catch (error) { throw createValidationError('framework', value, 'react | js | vue'); } } // URL validation export const UrlSchema = z.string().url().or( z.string().regex(/^\/docs\/[a-z-]+$/, 'Must be a valid documentation path') ); export function validateUrl(value: unknown): string { try { return UrlSchema.parse(value); } catch (error) { throw createValidationError('url', value, 'valid URL or documentation path'); } } // Documentation category validation export const DocumentationCategorySchema = z.enum([ 'animation', 'gestures', 'layout-animations', 'scroll-animations', 'components', 'api-reference', 'guides', 'examples', 'best-practices' ]); export function validateDocumentationCategory(value: unknown): DocumentationCategory { try { return DocumentationCategorySchema.parse(value); } catch (error) { throw createValidationError('category', value, 'valid documentation category'); } } // Animation type validation export const AnimationTypeSchema = z.enum([ 'fadeIn', 'fadeOut', 'slideIn', 'slideOut', 'scaleIn', 'scaleOut', 'rotate', 'spin', 'bounce', 'pulse', 'shake', 'wobble', 'flip', 'zoom', 'custom' ]); export function validateAnimationType(value: unknown): AnimationType { try { return AnimationTypeSchema.parse(value); } catch (error) { throw createValidationError('animationType', value, 'valid animation type'); } } // Component name validation export const ComponentNameSchema = z.string() .min(1, 'Component name cannot be empty') .regex(/^[A-Z][a-zA-Z0-9]*$/, 'Component name must be PascalCase'); export function validateComponentName(value: unknown): string { try { return ComponentNameSchema.parse(value); } catch (error) { throw createValidationError('componentName', value, 'PascalCase component name'); } } // Complexity level validation export const ComplexitySchema = z.enum(['basic', 'intermediate', 'advanced']); export function validateComplexity(value: unknown): 'basic' | 'intermediate' | 'advanced' { try { return ComplexitySchema.parse(value); } catch (error) { throw createValidationError('complexity', value, 'basic | intermediate | advanced'); } } // Format validation export const FormatSchema = z.enum(['markdown', 'json', 'raw']); export function validateFormat(value: unknown): 'markdown' | 'json' | 'raw' { try { return FormatSchema.parse(value); } catch (error) { throw createValidationError('format', value, 'markdown | json | raw'); } } // Tool parameter schemas export const GetMotionDocsParamsSchema = z.object({ url: UrlSchema, format: FormatSchema.optional().default('markdown'), includeExamples: z.boolean().optional().default(true), includeApiRef: z.boolean().optional().default(true) }); export const SearchMotionDocsParamsSchema = z.object({ query: z.string().min(1, 'Query cannot be empty'), framework: FrameworkSchema.optional(), category: DocumentationCategorySchema.optional(), limit: z.number().int().min(1).max(50).optional().default(10) }); export const GetComponentApiParamsSchema = z.object({ component: z.string().min(1, 'Component name cannot be empty'), framework: FrameworkSchema, includeProps: z.boolean().optional().default(true), includeExamples: z.boolean().optional().default(true) }); export const GetExamplesByCategoryParamsSchema = z.object({ category: z.string().min(1, 'Category cannot be empty'), framework: FrameworkSchema.optional(), complexity: ComplexitySchema.optional(), format: z.enum(['code-only', 'with-explanation']).optional().default('code-only') }); export const GenerateMotionComponentParamsSchema = z.object({ framework: FrameworkSchema, componentName: ComponentNameSchema, animations: z.array(z.string()).min(1, 'At least one animation is required'), props: z.array(z.string()).optional().default([]), typescript: z.boolean().optional().default(true) }); export const CreateAnimationSequenceParamsSchema = z.object({ framework: FrameworkSchema, sequence: z.array(z.object({ element: z.string().min(1), animate: z.record(z.any()), delay: z.number().optional(), duration: z.number().optional(), easing: z.string().optional() })).min(1, 'At least one animation step is required'), stagger: z.boolean().optional().default(false) }); export const OptimizeMotionCodeParamsSchema = z.object({ code: z.string().min(1, 'Code cannot be empty'), framework: FrameworkSchema, focusAreas: z.array(z.enum(['performance', 'accessibility', 'bundle-size'])).optional() }); export const ConvertBetweenFrameworksParamsSchema = z.object({ from: FrameworkSchema, to: FrameworkSchema, code: z.string().min(1, 'Code cannot be empty'), preserveComments: z.boolean().optional().default(true) }); export const ValidateMotionSyntaxParamsSchema = z.object({ code: z.string().min(1, 'Code cannot be empty'), framework: FrameworkSchema, strict: z.boolean().optional().default(false) }); // Validation helper function export function validateParams<T>(schema: z.ZodSchema<T>, params: unknown): T { try { return schema.parse(params); } catch (error) { if (error instanceof z.ZodError) { // const errorDetails = error.errors.map(err => ({ // Reserved for future use // path: err.path.join('.'), // message: err.message, // received: (err as any).input || 'unknown' // })); throw createValidationError('params', params, error.errors.map(e => e.message).join(', ')); } throw error; } } // Resource URI validation export function validateResourceUri(uri: string): boolean { const validPrefixes = [ 'motion://docs/react', 'motion://docs/js', 'motion://docs/vue', 'motion://examples', 'motion://best-practices' ]; return validPrefixes.some(prefix => uri.startsWith(prefix)); } // Code validation helper export function validateCode(code: string): boolean { // Basic code validation - check for minimum length and basic structure if (code.length < 10) return false; // Check for obvious malicious patterns const dangerousPatterns = [ /eval\s*\(/, /Function\s*\(/, /document\.write/, /innerHTML\s*=/, /setTimeout\s*\(/, /setInterval\s*\(/ ]; return !dangerousPatterns.some(pattern => pattern.test(code)); } // Specific validation functions for MCP tools export function validateGetMotionDocsParams(args: any): any { try { return GetMotionDocsParamsSchema.parse(args); } catch (error) { if (error instanceof z.ZodError) { const firstIssue = error.issues[0]; throw createValidationError( firstIssue.path.join('.'), args, firstIssue.message ); } throw createValidationError('unknown', args, String(error)); } } export function validateSearchMotionDocsParams(args: any): any { try { return SearchMotionDocsParamsSchema.parse(args); } catch (error) { if (error instanceof z.ZodError) { const firstIssue = error.issues[0]; throw createValidationError( firstIssue.path.join('.'), args, firstIssue.message ); } throw createValidationError('unknown', args, String(error)); } } export function validateGetComponentApiParams(args: any): any { try { return GetComponentApiParamsSchema.parse(args); } catch (error) { if (error instanceof z.ZodError) { const firstIssue = error.issues[0]; throw createValidationError( firstIssue.path.join('.'), args, firstIssue.message ); } throw createValidationError('unknown', args, String(error)); } } export function validateGetExamplesByCategoryParams(args: any): any { try { return GetExamplesByCategoryParamsSchema.parse(args); } catch (error) { if (error instanceof z.ZodError) { const firstIssue = error.issues[0]; throw createValidationError( firstIssue.path.join('.'), args, firstIssue.message ); } throw createValidationError('unknown', args, String(error)); } } export { z };

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/Abhishekrajpurohit/motion-dev-mcp'

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