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
JavaScript
/**
* 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;
}
}