Skip to main content
Glama

Motion.dev MCP Server

examples.ts11.9 kB
/** * Code examples retrieval tool implementation * Handles filtering and organizing Motion.dev code examples with SQLite backend */ import { MotionDocService } from '../services/motion-doc-service'; import { MotionExample } from '../database/motion-repository'; import { Logger } from '../utils/logger'; import { MotionMCPError, createValidationError } from '../utils/errors'; import { Framework } from '../types/motion'; export interface GetExamplesByCategoryParams { category: string; framework?: Framework; difficulty?: 'beginner' | 'intermediate' | 'advanced'; limit?: number; } export interface GetExamplesByCategoryResponse { success: boolean; category: string; framework?: Framework; difficulty?: string; examples: MotionExample[]; totalFound: number; queryTime: number; error?: string; } export interface SearchExamplesParams { query: string; framework?: Framework; category?: string; difficulty?: 'beginner' | 'intermediate' | 'advanced'; limit?: number; } export interface ExampleFilter { framework?: Framework; category?: string; difficulty?: 'beginner' | 'intermediate' | 'advanced'; tags?: string[]; minLength?: number; maxLength?: number; } export class ExamplesTool { private docService: MotionDocService; private logger = Logger.getInstance(); constructor(docService: MotionDocService) { this.docService = docService; } async getExamplesByCategory(params: GetExamplesByCategoryParams): Promise<GetExamplesByCategoryResponse> { const startTime = Date.now(); this.logger.info('get_examples_by_category called', params); try { // Validate parameters if (!params.category) { throw createValidationError('category', params.category, 'Category is required'); } if (params.limit && (params.limit < 1 || params.limit > 50)) { throw createValidationError('limit', params.limit, 'Limit must be between 1 and 50'); } const limit = params.limit || 20; // Get examples from database const examples = await this.docService.getExamplesByCategory( params.category, params.framework ); // Filter by difficulty if specified let filteredExamples = examples; if (params.difficulty) { filteredExamples = examples.filter(example => example.difficulty === params.difficulty ); } // Sort by difficulty and title filteredExamples.sort((a, b) => { const difficultyOrder = { beginner: 1, intermediate: 2, advanced: 3 }; const aDifficulty = difficultyOrder[a.difficulty || 'beginner']; const bDifficulty = difficultyOrder[b.difficulty || 'beginner']; if (aDifficulty !== bDifficulty) { return aDifficulty - bDifficulty; } return a.title.localeCompare(b.title); }); // Limit results const limitedExamples = filteredExamples.slice(0, limit); const response: GetExamplesByCategoryResponse = { success: true, category: params.category, framework: params.framework, difficulty: params.difficulty, examples: limitedExamples, totalFound: filteredExamples.length, queryTime: Date.now() - startTime }; this.logger.info(`Retrieved ${response.examples.length} examples for ${params.framework || 'all'}/${params.category}`); return response; } catch (error) { this.logger.error(`Examples retrieval failed: ${params.category}/${params.framework}`, error as Error); return { success: false, category: params.category, framework: params.framework, difficulty: params.difficulty, examples: [], totalFound: 0, queryTime: Date.now() - startTime, error: error instanceof MotionMCPError ? error.message : String(error) }; } } async searchExamples(params: SearchExamplesParams): Promise<GetExamplesByCategoryResponse> { const startTime = Date.now(); this.logger.info('search_examples called', params); try { const examples = await this.docService.searchExamples(params.query, { framework: params.framework, category: params.category, limit: params.limit || 20 }); // Filter by difficulty if specified let filteredExamples = examples; if (params.difficulty) { filteredExamples = examples.filter(example => example.difficulty === params.difficulty ); } return { success: true, category: params.category || 'search', framework: params.framework, difficulty: params.difficulty, examples: filteredExamples, totalFound: filteredExamples.length, queryTime: Date.now() - startTime }; } catch (error) { this.logger.error('Examples search failed', error as Error); return { success: false, category: params.category || 'search', framework: params.framework, difficulty: params.difficulty, examples: [], totalFound: 0, queryTime: Date.now() - startTime, error: error instanceof MotionMCPError ? error.message : String(error) }; } } async getExamplesByDifficulty( difficulty: 'beginner' | 'intermediate' | 'advanced', framework?: Framework, limit: number = 10 ): Promise<MotionExample[]> { try { // Search for examples with empty query but filter by difficulty and framework const examples = await this.docService.searchExamples('', { framework, limit: limit * 2 // Get more to filter from }); const difficultyExamples = examples .filter(example => example.difficulty === difficulty) .slice(0, limit); this.logger.debug(`Found ${difficultyExamples.length} ${difficulty} examples`); return difficultyExamples; } catch (error) { this.logger.error(`Failed to get examples by difficulty: ${difficulty}`, error as Error); return []; } } async getExamplesByTags( tags: string[], framework?: Framework, limit: number = 10 ): Promise<MotionExample[]> { try { // Use search with tag keywords const tagQuery = tags.join(' '); const examples = await this.docService.searchExamples(tagQuery, { framework, limit: limit * 2 // Get more to filter from }); // Filter examples that have any of the specified tags const taggedExamples = examples.filter(example => { if (!example.tags) return false; const exampleTags = JSON.parse(example.tags) as string[]; return tags.some(tag => exampleTags.some(exampleTag => exampleTag.toLowerCase().includes(tag.toLowerCase()) ) ); }).slice(0, limit); this.logger.debug(`Found ${taggedExamples.length} examples with tags: ${tags.join(', ')}`); return taggedExamples; } catch (error) { this.logger.error(`Failed to get examples by tags: ${tags.join(', ')}`, error as Error); return []; } } async filterExamples(examples: MotionExample[], filter: ExampleFilter): Promise<MotionExample[]> { try { let filtered = examples; // Framework filter if (filter.framework) { filtered = filtered.filter(example => example.framework === filter.framework); } // Difficulty filter if (filter.difficulty) { filtered = filtered.filter(example => example.difficulty === filter.difficulty); } // Tags filter if (filter.tags && filter.tags.length > 0) { filtered = filtered.filter(example => { if (!example.tags) return false; try { const exampleTags = JSON.parse(example.tags) as string[]; return filter.tags!.some(tag => exampleTags.some(exampleTag => exampleTag.toLowerCase().includes(tag.toLowerCase()) ) ); } catch { return false; } }); } // Length filters if (filter.minLength) { filtered = filtered.filter(example => example.code.length >= filter.minLength!); } if (filter.maxLength) { filtered = filtered.filter(example => example.code.length <= filter.maxLength!); } return filtered; } catch (error) { this.logger.error('Example filtering failed', error as Error); return examples; // Return original examples if filtering fails } } async getExampleStats(): Promise<{ totalExamples: number; byFramework: Record<Framework, number>; byDifficulty: Record<string, number>; byCategory: Record<string, number>; averageCodeLength: number; topTags: Array<{ tag: string; count: number }>; }> { try { const dbStats = await this.docService.getStatistics(); // Get all examples for detailed stats const allExamples = await this.docService.searchExamples('', { limit: 1000 }); const stats = { totalExamples: dbStats.totalExamples, byFramework: {} as Record<Framework, number>, byDifficulty: {} as Record<string, number>, byCategory: {} as Record<string, number>, averageCodeLength: 0, topTags: [] as Array<{ tag: string; count: number }> }; // Calculate stats from examples let totalCodeLength = 0; const tagCounts: Record<string, number> = {}; for (const example of allExamples) { // Framework counts stats.byFramework[example.framework] = (stats.byFramework[example.framework] || 0) + 1; // Difficulty counts const difficulty = example.difficulty || 'beginner'; stats.byDifficulty[difficulty] = (stats.byDifficulty[difficulty] || 0) + 1; // Category counts if (example.category) { stats.byCategory[example.category] = (stats.byCategory[example.category] || 0) + 1; } // Code length totalCodeLength += example.code.length; // Tag counts if (example.tags) { try { const tags = JSON.parse(example.tags) as string[]; for (const tag of tags) { tagCounts[tag] = (tagCounts[tag] || 0) + 1; } } catch { // Ignore parsing errors } } } // Calculate averages stats.averageCodeLength = allExamples.length > 0 ? Math.round(totalCodeLength / allExamples.length) : 0; // Top tags stats.topTags = Object.entries(tagCounts) .sort(([, a], [, b]) => b - a) .slice(0, 10) .map(([tag, count]) => ({ tag, count })); return stats; } catch (error) { this.logger.error('Failed to calculate example stats', error as Error); return { totalExamples: 0, byFramework: {} as Record<Framework, number>, byDifficulty: {}, byCategory: {} as Record<string, number>, averageCodeLength: 0, topTags: [] }; } } async getFrameworkSpecificExamples(framework: Framework): Promise<{ beginner: MotionExample[]; intermediate: MotionExample[]; advanced: MotionExample[]; }> { try { const allExamples = await this.docService.searchExamples('', { framework, limit: 100 }); return { beginner: allExamples.filter(e => e.difficulty === 'beginner').slice(0, 5), intermediate: allExamples.filter(e => e.difficulty === 'intermediate').slice(0, 5), advanced: allExamples.filter(e => e.difficulty === 'advanced').slice(0, 5) }; } catch (error) { this.logger.error(`Failed to get ${framework} examples`, error as Error); return { beginner: [], intermediate: [], advanced: [] }; } } }

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