Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
450 lines
12 KiB
JavaScript
450 lines
12 KiB
JavaScript
/**
|
||
* 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 };
|