/** * Tools Service - Manages MCP tools catalog and execution */ import api from '../core/api.js'; import logger from '../core/logger.js'; class ToolsService { constructor() { this.tools = []; this.toolsMetadata = this._getToolsMetadata(); this.executionHistory = []; this.toolStatus = {}; this.teamToolMappings = this._getTeamToolMappings(); } /** * Define which tools are relevant for each team */ _getTeamToolMappings() { return { 'ui': [ 'extract_tokens', 'extract_components', 'generate_component_code', 'sync_tokens_to_file', 'ingest_css_tokens', 'ingest_scss_tokens', 'ingest_tailwind_tokens', 'merge_tokens', 'export_tokens', 'scan_storybook', 'generate_story', 'generate_stories_batch', 'generate_storybook_theme' ], 'ux': [ 'analyze_style_values', 'check_naming_consistency', 'find_style_patterns', 'validate_tokens', 'extract_tokens', 'export_tokens', 'discover_project', 'get_sync_history' ], 'qa': [ 'get_quick_wins', 'get_quick_wins_report', 'validate_tokens', 'find_unused_styles', 'find_inline_styles', 'analyze_react_components', 'build_source_graph', 'get_story_coverage' ], 'all': [] // Empty means all tools }; } /** * Get metadata for all 32 MCP tools */ _getToolsMetadata() { return { // Projects list_projects: { name: 'list_projects', category: 'Projects', description: 'List all design system projects', parameters: [], icon: 'πŸ“' }, create_project: { name: 'create_project', category: 'Projects', description: 'Create a new design system project', parameters: ['name', 'description', 'figma_file_key'], icon: 'βž•' }, get_project: { name: 'get_project', category: 'Projects', description: 'Get details of a specific project', parameters: ['project_id'], icon: 'πŸ”' }, // Figma extract_tokens: { name: 'extract_tokens', category: 'Figma', description: 'Extract design tokens from Figma file', parameters: ['file_key', 'format'], icon: '🎨', requiresConfig: ['figma_token'] }, extract_components: { name: 'extract_components', category: 'Figma', description: 'Extract components from Figma file', parameters: ['file_key'], icon: '🧩', requiresConfig: ['figma_token'] }, generate_component_code: { name: 'generate_component_code', category: 'Figma', description: 'Generate React code from Figma component', parameters: ['file_key', 'node_id', 'framework'], icon: 'βš›οΈ', requiresConfig: ['figma_token'] }, sync_tokens_to_file: { name: 'sync_tokens_to_file', category: 'Figma', description: 'Sync tokens to output file', parameters: ['file_key', 'output_path', 'format'], icon: 'πŸ”„', requiresConfig: ['figma_token'] }, // Ingestion ingest_css_tokens: { name: 'ingest_css_tokens', category: 'Ingestion', description: 'Ingest tokens from CSS variables', parameters: ['source'], icon: 'πŸ“₯' }, ingest_scss_tokens: { name: 'ingest_scss_tokens', category: 'Ingestion', description: 'Ingest tokens from SCSS variables', parameters: ['source'], icon: 'πŸ“₯' }, ingest_tailwind_tokens: { name: 'ingest_tailwind_tokens', category: 'Ingestion', description: 'Ingest tokens from Tailwind config', parameters: ['source'], icon: 'πŸ“₯' }, ingest_json_tokens: { name: 'ingest_json_tokens', category: 'Ingestion', description: 'Ingest tokens from JSON format', parameters: ['source'], icon: 'πŸ“₯' }, merge_tokens: { name: 'merge_tokens', category: 'Ingestion', description: 'Merge tokens from multiple sources', parameters: ['sources', 'strategy'], icon: 'πŸ”—' }, export_tokens: { name: 'export_tokens', category: 'Ingestion', description: 'Export tokens to specified format', parameters: ['format', 'output_path'], icon: 'πŸ“€' }, validate_tokens: { name: 'validate_tokens', category: 'Ingestion', description: 'Validate token structure and values', parameters: ['source'], icon: 'βœ“' }, // Analysis discover_project: { name: 'discover_project', category: 'Analysis', description: 'Discover project structure and frameworks', parameters: ['path'], icon: 'πŸ”Ž', quickWin: true }, analyze_react_components: { name: 'analyze_react_components', category: 'Analysis', description: 'Analyze React components in project', parameters: ['path'], icon: 'βš›οΈ', quickWin: true }, find_inline_styles: { name: 'find_inline_styles', category: 'Analysis', description: 'Find components with inline styles', parameters: ['path'], icon: '🎯', quickWin: true }, find_style_patterns: { name: 'find_style_patterns', category: 'Analysis', description: 'Identify common style patterns', parameters: ['path'], icon: 'πŸ“Š', quickWin: true }, analyze_style_values: { name: 'analyze_style_values', category: 'Analysis', description: 'Analyze style property values', parameters: ['path', 'property'], icon: 'πŸ“ˆ', quickWin: true }, find_unused_styles: { name: 'find_unused_styles', category: 'Analysis', description: 'Find unused style definitions', parameters: ['path'], icon: 'πŸ—‘οΈ', quickWin: true }, build_source_graph: { name: 'build_source_graph', category: 'Analysis', description: 'Build component dependency graph', parameters: ['path', 'depth'], icon: 'πŸ•ΈοΈ' }, get_quick_wins: { name: 'get_quick_wins', category: 'Analysis', description: 'Get actionable improvement opportunities', parameters: ['path'], icon: '⚑', quickWin: true, featured: true }, get_quick_wins_report: { name: 'get_quick_wins_report', category: 'Analysis', description: 'Generate detailed quick wins report', parameters: ['path'], icon: 'πŸ“‹', quickWin: true }, check_naming_consistency: { name: 'check_naming_consistency', category: 'Analysis', description: 'Check component naming consistency', parameters: ['path'], icon: '🏷️', quickWin: true }, // Storybook scan_storybook: { name: 'scan_storybook', category: 'Storybook', description: 'Scan existing Storybook configuration', parameters: ['path'], icon: 'πŸ“š' }, generate_story: { name: 'generate_story', category: 'Storybook', description: 'Generate Storybook story for component', parameters: ['component_path', 'template'], icon: 'πŸ“–' }, generate_stories_batch: { name: 'generate_stories_batch', category: 'Storybook', description: 'Generate stories for multiple components', parameters: ['components', 'template'], icon: 'πŸ“š' }, generate_storybook_theme: { name: 'generate_storybook_theme', category: 'Storybook', description: 'Generate Storybook theme from tokens', parameters: ['tokens', 'output_path'], icon: '🎨' }, get_story_coverage: { name: 'get_story_coverage', category: 'Storybook', description: 'Check which components have stories', parameters: ['path'], icon: 'πŸ“Š' }, // Activity get_status: { name: 'get_status', category: 'Activity', description: 'Get server status and statistics', parameters: [], icon: 'πŸ’š' }, get_sync_history: { name: 'get_sync_history', category: 'Activity', description: 'Get token sync history', parameters: ['limit'], icon: 'πŸ“œ' }, get_activity: { name: 'get_activity', category: 'Activity', description: 'Get recent activity log', parameters: ['limit'], icon: 'πŸ“' } }; } /** * Get all tools with metadata */ getAllTools() { return Object.values(this.toolsMetadata).map(tool => ({ ...tool, status: this.toolStatus[tool.name] || 'available', lastUsed: this._getLastUsed(tool.name) })); } /** * Get tools by category */ getToolsByCategory(category) { return this.getAllTools().filter(tool => tool.category === category); } /** * Get quick win tools */ getQuickWinTools() { return this.getAllTools().filter(tool => tool.quickWin); } /** * Get featured tools */ getFeaturedTools() { return this.getAllTools().filter(tool => tool.featured); } /** * Get tools filtered by team context */ getToolsByTeam(teamContext = 'all') { if (teamContext === 'all' || !this.teamToolMappings[teamContext]) { return this.getAllTools(); } const teamTools = this.teamToolMappings[teamContext]; return this.getAllTools().filter(tool => teamTools.includes(tool.name)); } /** * Execute a tool */ async executeTool(toolName, params = {}) { logger.info('Tools', `Executing tool: ${toolName}`, params); this.toolStatus[toolName] = 'executing'; const startTime = Date.now(); try { // Map tool name to API endpoint const result = await api.executeMCPTool(toolName, params); const duration = Date.now() - startTime; this.toolStatus[toolName] = 'success'; // Log execution this._logExecution(toolName, params, result, duration, 'success'); logger.info('Tools', `Tool ${toolName} succeeded in ${duration}ms`); return result; } catch (error) { const duration = Date.now() - startTime; this.toolStatus[toolName] = 'error'; // Log error this._logExecution(toolName, params, null, duration, 'error', error.message); logger.error('Tools', `Tool ${toolName} failed: ${error.message}`, error); throw error; } } /** * Log tool execution */ _logExecution(toolName, params, result, duration, status, error = null) { const execution = { toolName, params, result, duration, status, error, timestamp: new Date().toISOString() }; this.executionHistory.push(execution); // Keep only last 100 executions if (this.executionHistory.length > 100) { this.executionHistory.shift(); } } /** * Get execution history for a tool */ getToolHistory(toolName) { return this.executionHistory .filter(exec => exec.toolName === toolName) .slice(-10); } /** * Get last used timestamp */ _getLastUsed(toolName) { const executions = this.executionHistory.filter(exec => exec.toolName === toolName); if (executions.length === 0) return null; return executions[executions.length - 1].timestamp; } /** * Get tool success rate */ getSuccessRate(toolName) { const executions = this.executionHistory.filter(exec => exec.toolName === toolName); if (executions.length === 0) return null; const successful = executions.filter(exec => exec.status === 'success').length; return successful / executions.length; } /** * Get all categories */ getCategories() { const categories = new Set(); Object.values(this.toolsMetadata).forEach(tool => { categories.add(tool.category); }); return Array.from(categories); } } // Singleton instance const toolsService = new ToolsService(); export default toolsService; export { ToolsService };