UNPKG

mcp-orchestrator

Version:

MCP Orchestrator - Discover and install MCPs with automatic OAuth support. Uses Claude CLI for OAuth MCPs (Canva, Asana, etc). 34 trusted MCPs from Claude Partners.

271 lines (270 loc) 9.51 kB
/** * Embedding Generator * Creates vector embeddings for MCP descriptions */ import OpenAI from 'openai'; import * as dotenv from 'dotenv'; // Load environment variables dotenv.config(); export class EmbeddingGenerator { openai = null; useOpenAI = false; constructor() { // Check if OpenAI API key is available if (process.env.OPENAI_API_KEY) { this.openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); this.useOpenAI = true; console.log('✅ Using OpenAI for embeddings (best quality)'); } else { console.log('⚠️ No OpenAI API key found, using simple embeddings'); console.log('💡 Add OPENAI_API_KEY to .env for better quality'); } } /** * Generate embedding for a single MCP */ async generateEmbedding(mcp) { // Create rich text representation for embedding const textForEmbedding = this.createEmbeddingText(mcp); let embedding; if (this.useOpenAI && this.openai) { // Use OpenAI for high-quality embeddings embedding = await this.generateOpenAIEmbedding(textForEmbedding); } else { // Use simple keyword-based embeddings (fallback) embedding = this.generateSimpleEmbedding(textForEmbedding); } // Add use cases and capabilities const enriched = { ...mcp, embedding, useCases: this.generateUseCases(mcp), capabilities: this.extractCapabilities(mcp) }; return enriched; } /** * Create rich text for embedding generation */ createEmbeddingText(mcp) { return ` ${mcp.name} MCP Server ${mcp.description} Keywords: ${mcp.keywords.join(', ')} Category: ${this.inferCategory(mcp)} Use for: ${this.generateUseCases(mcp).join(', ')} `.trim(); } /** * Generate OpenAI embedding */ async generateOpenAIEmbedding(text) { try { const response = await this.openai.embeddings.create({ model: 'text-embedding-3-small', input: text, dimensions: 384 // Smaller size for efficiency }); return response.data[0].embedding; } catch (error) { console.error('OpenAI embedding failed, using fallback:', error); return this.generateSimpleEmbedding(text); } } /** * Generate simple keyword-based embedding (fallback) */ generateSimpleEmbedding(text) { // Create a simple but effective embedding based on keywords const keywords = [ // File operations 'file', 'read', 'write', 'directory', 'folder', 'csv', 'json', 'text', // Data operations 'database', 'sql', 'query', 'data', 'table', 'sqlite', 'postgres', // Web operations 'web', 'http', 'api', 'fetch', 'scrape', 'browser', 'html', // Version control 'git', 'github', 'commit', 'repository', 'branch', 'merge', // Communication 'slack', 'message', 'chat', 'email', 'notification', // Cloud 'cloud', 'storage', 'drive', 'upload', 'download', // Search 'search', 'find', 'query', 'lookup', // Time 'time', 'date', 'schedule', 'calendar', // Analysis 'analyze', 'process', 'transform', 'extract' ]; const embedding = new Array(384).fill(0); const lowerText = text.toLowerCase(); // Set values based on keyword presence keywords.forEach((keyword, index) => { if (lowerText.includes(keyword)) { // Use different positions for different keywords const position = (index * 13) % 384; // Distribute across embedding embedding[position] = 1; // Add some neighboring values for smoothness if (position > 0) embedding[position - 1] = 0.5; if (position < 383) embedding[position + 1] = 0.5; } }); // Normalize the embedding const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0)); if (magnitude > 0) { return embedding.map(val => val / magnitude); } return embedding; } /** * Generate use cases for an MCP */ generateUseCases(mcp) { const useCaseMap = { 'filesystem': [ 'Read CSV files for analysis', 'Write JSON configuration', 'Process log files', 'List directory contents', 'Create project structure' ], 'git': [ 'Check repository status', 'View commit history', 'Compare file changes', 'Create commits', 'Switch branches' ], 'sqlite': [ 'Query local databases', 'Analyze data with SQL', 'Create database tables', 'Import CSV to database', 'Generate reports' ], 'github': [ 'Create GitHub issues', 'Review pull requests', 'Search repositories', 'Manage releases', 'Check workflow status' ], 'slack': [ 'Send notifications', 'Read channel messages', 'Create reminders', 'Share files', 'Manage users' ], 'puppeteer': [ 'Take website screenshots', 'Generate PDFs from web pages', 'Scrape dynamic content', 'Automate browser testing', 'Fill web forms' ], 'fetch': [ 'Fetch web page content', 'Call REST APIs', 'Download files', 'Convert HTML to markdown', 'Monitor websites' ], 'notion': [ 'Manage Notion pages', 'Query databases', 'Create content blocks', 'Sync workspaces', 'Export documentation' ] }; return useCaseMap[mcp.id] || [ `Use ${mcp.name} for operations`, `Integrate with ${mcp.name}`, `Automate ${mcp.name} tasks` ]; } /** * Extract capabilities from description and keywords */ extractCapabilities(mcp) { const capabilities = []; // Extract from description const descWords = mcp.description.toLowerCase(); if (descWords.includes('read')) capabilities.push('read'); if (descWords.includes('write')) capabilities.push('write'); if (descWords.includes('create')) capabilities.push('create'); if (descWords.includes('delete')) capabilities.push('delete'); if (descWords.includes('query')) capabilities.push('query'); if (descWords.includes('search')) capabilities.push('search'); if (descWords.includes('fetch')) capabilities.push('fetch'); if (descWords.includes('manage')) capabilities.push('manage'); // Add from keywords mcp.keywords.forEach(keyword => { if (['api', 'database', 'file', 'web', 'git'].includes(keyword)) { capabilities.push(keyword); } }); return [...new Set(capabilities)]; // Remove duplicates } /** * Infer category from MCP */ inferCategory(mcp) { const id = mcp.id.toLowerCase(); const keywords = mcp.keywords.join(' ').toLowerCase(); if (id.includes('file') || id.includes('drive') || keywords.includes('storage')) { return 'storage'; } if (id.includes('git') || keywords.includes('version')) { return 'version-control'; } if (id.includes('sql') || id.includes('database')) { return 'database'; } if (id.includes('slack') || id.includes('email')) { return 'communication'; } if (keywords.includes('web') || keywords.includes('browser')) { return 'web'; } if (keywords.includes('search')) { return 'search'; } return 'general'; } /** * Generate embeddings for multiple MCPs */ async generateBatchEmbeddings(mcps) { console.log(`🎯 Generating embeddings for ${mcps.length} MCPs...`); const results = []; for (let i = 0; i < mcps.length; i++) { const mcp = mcps[i]; console.log(` [${i + 1}/${mcps.length}] Processing ${mcp.name}...`); const withEmbedding = await this.generateEmbedding(mcp); results.push(withEmbedding); // Rate limiting for OpenAI if (this.useOpenAI && i < mcps.length - 1) { await new Promise(resolve => setTimeout(resolve, 100)); } } console.log(`✅ Generated embeddings for ${results.length} MCPs`); return results; } }