Skip to main content
Glama

Motion.dev MCP Server

server.ts22.8 kB
/** * Motion.dev MCP Server Implementation * Main MCP server providing Motion.dev documentation and code generation tools */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { MotionDocService } from './services/motion-doc-service.js'; import { DocumentationTool } from './tools/documentation.js'; import { ExamplesTool } from './tools/examples.js'; import { ApiTool } from './tools/api.js'; import { CodeGenerationTool } from './tools/generation.js'; import { FrameworkDocsManager } from './resources/framework-docs.js'; import { ExamplesLibraryManager } from './resources/examples-library.js'; import { BestPracticesManager } from './resources/best-practices.js'; import { logger } from './utils/logger.js'; import { createToolError, createValidationError } from './utils/errors.js'; import { validateGetMotionDocsParams, validateSearchMotionDocsParams, validateGetComponentApiParams, validateGetExamplesByCategoryParams } from './utils/validators.js'; export class MotionMCPServer { private server: Server; private docService: MotionDocService; // Tools private documentationTool: DocumentationTool; private examplesTool: ExamplesTool; private apiTool: ApiTool; private codeGenerationTool: CodeGenerationTool; // Resources private frameworkDocsManager: FrameworkDocsManager; private examplesLibraryManager: ExamplesLibraryManager; private bestPracticesManager: BestPracticesManager; private isInitialized = false; private logger = logger; constructor(dbPath: string = 'docs/motion-docs.db') { this.server = new Server( { name: 'motion-dev-mcp', version: '1.0.0', description: 'Model Context Protocol server for Motion.dev animation library with SQLite backend' }, { capabilities: { tools: {}, resources: {} } } ); // Initialize documentation service this.docService = new MotionDocService(undefined, undefined, dbPath); // Initialize tools with doc service this.documentationTool = new DocumentationTool(this.docService); this.examplesTool = new ExamplesTool(this.docService); this.apiTool = new ApiTool(this.docService); this.codeGenerationTool = new CodeGenerationTool(); // Initialize resources this.frameworkDocsManager = new FrameworkDocsManager(); this.examplesLibraryManager = new ExamplesLibraryManager(); this.bestPracticesManager = new BestPracticesManager(); this.setupHandlers(); } private setupHandlers(): void { // List available tools this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'get_motion_docs', description: 'Fetch specific Motion.dev documentation by URL or topic', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'Documentation URL or endpoint path' }, useCache: { type: 'boolean', description: 'Whether to use cached content (default: true)', default: true } }, required: ['url'] } }, { name: 'search_motion_docs', description: 'Full-text search across Motion.dev documentation', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query string' }, framework: { type: 'string', enum: ['react', 'js', 'vue', 'general'], description: 'Filter by framework (optional)' }, category: { type: 'string', enum: ['animation', 'gestures', 'layout-animations', 'scroll-animations', 'components', 'api-reference', 'guides', 'examples', 'best-practices'], description: 'Filter by category (optional)' }, limit: { type: 'number', description: 'Maximum number of results (default: 10)', default: 10 } }, required: ['query'] } }, { name: 'get_component_api', description: 'Extract component API reference and props documentation', inputSchema: { type: 'object', properties: { component: { type: 'string', description: 'Component name (e.g., motion.div, Transition)' }, framework: { type: 'string', enum: ['react', 'js', 'vue'], description: 'Target framework for API reference' } }, required: ['component', 'framework'] } }, { name: 'get_examples_by_category', description: 'Retrieve code examples by animation category and complexity', inputSchema: { type: 'object', properties: { category: { type: 'string', enum: ['animation', 'gestures', 'layout-animations', 'scroll-animations', 'components'], description: 'Animation category' }, framework: { type: 'string', enum: ['react', 'js', 'vue'], description: 'Target framework' }, complexity: { type: 'string', enum: ['basic', 'intermediate', 'advanced'], description: 'Code complexity level (optional)' }, limit: { type: 'number', description: 'Maximum number of examples (default: 5)', default: 5 } }, required: ['category', 'framework'] } }, { name: 'generate_motion_component', description: 'Generate Motion.dev component code for React, JavaScript, or Vue', inputSchema: { type: 'object', properties: { framework: { type: 'string', enum: ['react', 'js', 'vue'], description: 'Target framework for code generation' }, componentName: { type: 'string', description: 'Name of the component to generate' }, animations: { type: 'array', items: { type: 'string' }, description: 'Array of animation pattern IDs to include' }, props: { type: 'array', items: { type: 'string' }, description: 'Optional component props', default: [] }, typescript: { type: 'boolean', description: 'Generate TypeScript code (default: true)', default: true }, styleSystem: { type: 'string', enum: ['css', 'styled-components', 'emotion', 'tailwind'], description: 'CSS styling system to use (optional)' } }, required: ['framework', 'componentName', 'animations'] } }, { name: 'create_animation_sequence', description: 'Create complex animation sequences with staggering and timeline control', inputSchema: { type: 'object', properties: { framework: { type: 'string', enum: ['react', 'js', 'vue'], description: 'Target framework' }, sequence: { type: 'array', items: { type: 'object', properties: { element: { type: 'string', description: 'Element selector or type' }, animate: { type: 'object', description: 'Animation properties' }, delay: { type: 'number', description: 'Animation delay in seconds' }, duration: { type: 'number', description: 'Animation duration in seconds' }, easing: { type: 'string', description: 'Easing function' } }, required: ['element', 'animate'] }, description: 'Array of animation steps' }, stagger: { type: 'boolean', description: 'Enable staggered animations (default: false)', default: false }, timeline: { type: 'boolean', description: 'Create timeline-based sequence (default: false)', default: false } }, required: ['framework', 'sequence'] } }, { name: 'optimize_motion_code', description: 'Optimize Motion.dev code for performance, accessibility, and bundle size', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'Motion.dev code to optimize' }, framework: { type: 'string', enum: ['react', 'js', 'vue'], description: 'Framework of the provided code' }, focusAreas: { type: 'array', items: { type: 'string', enum: ['performance', 'accessibility', 'bundle-size'] }, description: 'Areas to focus optimization on (optional)' }, target: { type: 'string', enum: ['production', 'development'], description: 'Optimization target environment', default: 'production' } }, required: ['code', 'framework'] } }, { name: 'convert_between_frameworks', description: 'Convert Motion.dev code between React, JavaScript, and Vue', inputSchema: { type: 'object', properties: { from: { type: 'string', enum: ['react', 'js', 'vue'], description: 'Source framework' }, to: { type: 'string', enum: ['react', 'js', 'vue'], description: 'Target framework' }, code: { type: 'string', description: 'Source code to convert' }, preserveComments: { type: 'boolean', description: 'Preserve code comments (default: true)', default: true }, optimization: { type: 'boolean', description: 'Apply optimizations during conversion (default: false)', default: false } }, required: ['from', 'to', 'code'] } }, { name: 'validate_motion_syntax', description: 'Validate Motion.dev code syntax and suggest improvements', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'Motion.dev code to validate' }, framework: { type: 'string', enum: ['react', 'js', 'vue'], description: 'Framework of the provided code' }, strict: { type: 'boolean', description: 'Enable strict validation mode (default: false)', default: false }, rules: { type: 'array', items: { type: 'string' }, description: 'Specific validation rules to apply (optional)' } }, required: ['code', 'framework'] } } ] }; }); // List available resources this.server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [ { uri: 'motion://docs/react', name: 'React Motion Documentation', description: 'Complete React-specific Motion.dev documentation' }, { uri: 'motion://docs/js', name: 'JavaScript Motion Documentation', description: 'Vanilla JavaScript Motion.dev documentation' }, { uri: 'motion://docs/vue', name: 'Vue Motion Documentation', description: 'Vue-specific Motion.dev integration guides' }, { uri: 'motion://examples', name: 'Motion Examples Library', description: 'Curated code examples by category and framework' }, { uri: 'motion://best-practices', name: 'Motion Best Practices', description: 'Performance and accessibility guidelines for Motion.dev' } ] }; }); // Handle tool calls this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'get_motion_docs': return await this.handleGetMotionDocs(args); case 'search_motion_docs': return await this.handleSearchMotionDocs(args); case 'get_component_api': return await this.handleGetComponentApi(args); case 'get_examples_by_category': return await this.handleGetExamplesByCategory(args); case 'generate_motion_component': return await this.handleGenerateMotionComponent(args); case 'create_animation_sequence': return await this.handleCreateAnimationSequence(args); case 'optimize_motion_code': return await this.handleOptimizeMotionCode(args); case 'convert_between_frameworks': return await this.handleConvertBetweenFrameworks(args); case 'validate_motion_syntax': return await this.handleValidateMotionSyntax(args); default: throw createToolError(name, `Unknown tool: ${name}`); } } catch (error) { logger.error(`Tool execution failed: ${name}`, error as Error); throw error; } }); // Handle resource reads this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params; try { return await this.handleReadResource(uri); } catch (error) { logger.error(`Resource read failed: ${uri}`, error as Error); throw error; } }); } private async handleGetMotionDocs(args: any) { const params = validateGetMotionDocsParams(args); const response = await this.documentationTool.getMotionDocs(params); return { content: [{ type: 'text', text: JSON.stringify(response, null, 2) }] }; } private async handleSearchMotionDocs(args: any) { const params = validateSearchMotionDocsParams(args); const response = await this.documentationTool.searchMotionDocs(params); // Format response to match expected search format const searchResponse = { success: response.success, results: response.documents || [], totalFound: response.totalFound || 0, searchTime: response.queryTime || 0, query: params.query || '', filters: { framework: params.framework, category: params.category } }; return { content: [{ type: 'text', text: JSON.stringify(searchResponse, null, 2) }] }; } private async handleGetComponentApi(args: any) { const params = validateGetComponentApiParams(args); const response = await this.apiTool.getComponentApi(params); return { content: [{ type: 'text', text: JSON.stringify(response, null, 2) }] }; } private async handleGetExamplesByCategory(args: any) { const params = validateGetExamplesByCategoryParams(args); const response = await this.examplesTool.getExamplesByCategory(params); return { content: [{ type: 'text', text: JSON.stringify(response, null, 2) }] }; } // Code generation tool handlers private async handleGenerateMotionComponent(args: any) { const response = await this.codeGenerationTool.generateMotionComponent(args); return { content: [{ type: 'text', text: JSON.stringify(response, null, 2) }] }; } private async handleCreateAnimationSequence(args: any) { const response = await this.codeGenerationTool.createAnimationSequence(args); return { content: [{ type: 'text', text: JSON.stringify(response, null, 2) }] }; } private async handleOptimizeMotionCode(args: any) { const response = await this.codeGenerationTool.optimizeMotionCode(args); return { content: [{ type: 'text', text: JSON.stringify(response, null, 2) }] }; } private async handleConvertBetweenFrameworks(args: any) { const response = await this.codeGenerationTool.convertBetweenFrameworks(args); return { content: [{ type: 'text', text: JSON.stringify(response, null, 2) }] }; } private async handleValidateMotionSyntax(args: any) { const response = await this.codeGenerationTool.validateMotionSyntax(args); return { content: [{ type: 'text', text: JSON.stringify(response, null, 2) }] }; } private async handleReadResource(uri: string) { logger.debug(`Reading resource: ${uri}`); const startTime = Date.now(); try { switch (uri) { case 'motion://docs/react': const reactDocs = this.frameworkDocsManager.getReactDocs(); return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(reactDocs, null, 2) }] }; case 'motion://docs/js': const jsDocs = this.frameworkDocsManager.getJavaScriptDocs(); return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(jsDocs, null, 2) }] }; case 'motion://docs/vue': const vueDocs = this.frameworkDocsManager.getVueDocs(); return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(vueDocs, null, 2) }] }; case 'motion://examples': const examplesLibrary = this.examplesLibraryManager.getExamplesLibrary(); return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(examplesLibrary, null, 2) }] }; case 'motion://best-practices': const bestPractices = this.bestPracticesManager.getBestPractices(); return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(bestPractices, null, 2) }] }; default: throw createValidationError('resource', uri, 'Unknown resource URI'); } } catch (error) { logger.error(`Resource read failed: ${uri}`, error as Error); throw error; } finally { logger.logPerformanceMetric('read_resource', Date.now() - startTime, 'ms'); } } async start(): Promise<void> { logger.info('Initializing Motion.dev MCP Server...'); try { // Ensure database is initialized and populated await this.ensureInitialized(); // Start server const transport = new StdioServerTransport(); await this.server.connect(transport); // Get database statistics for logging const stats = await this.docService.getStatistics(); logger.info('Motion.dev MCP Server initialized successfully', stats); } catch (error) { logger.error('Failed to start MCP server', error as Error); throw error; } } async stop(): Promise<void> { logger.info('Shutting down Motion.dev MCP Server...'); try { await this.server.close(); this.docService.close(); logger.info('MCP Server shutdown completed'); } catch (error) { logger.error('Error during server shutdown', error as Error); throw error; } } private async ensureInitialized(): Promise<void> { if (this.isInitialized) return; this.logger.info('Initializing documentation database...'); try { // Check if database has content, populate if empty const stats = await this.docService.getStatistics(); if (stats.totalDocs === 0) { this.logger.info('Database is empty, populating with Motion.dev documentation...'); await this.docService.populateDatabase(); this.logger.info('Database population completed'); } else { this.logger.info('Database already populated', stats); } this.isInitialized = true; } catch (error) { this.logger.error('Failed to initialize documentation database', error as Error); throw error; } } }

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