Skip to main content
Glama
registry.ts8.68 kB
// src/core/registry.ts // Système d'enregistrement automatique des outils import { existsSync, readdirSync, statSync } from 'fs'; import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; import { ToolDefinition, ToolHandler, toolRegistry } from "./tool-registry.js"; /** * Interface pour un module d'outil */ export interface ToolModule { [key: string]: any; } /** * Configuration du registre */ export interface RegistryConfig { toolDirectories: string[]; toolFilePattern: RegExp; toolExportPattern: RegExp; verbose?: boolean; } /** * Configuration par défaut */ const DEFAULT_CONFIG: RegistryConfig = { toolDirectories: [ 'build/tools/graph', 'build/tools/rag' ], toolFilePattern: /\.js$/, toolExportPattern: /(Tool|Handler)$/, verbose: true }; /** * Classe principale du registre automatique */ export class AutoRegistry { private config: RegistryConfig; private registeredTools: Set<string> = new Set(); constructor(config: Partial<RegistryConfig> = {}) { this.config = { ...DEFAULT_CONFIG, ...config }; } /** * Découvre tous les modules d'outils dans les répertoires configurés */ async discoverToolModules(): Promise<Array<{ path: string; module: ToolModule }>> { const modules: Array<{ path: string; module: ToolModule }> = []; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const projectRoot = join(__dirname, '..', '..'); for (const dir of this.config.toolDirectories) { const fullPath = join(projectRoot, dir); if (!existsSync(fullPath)) { if (this.config.verbose) { console.warn(`⚠️ Répertoire non trouvé: ${fullPath}`); } continue; } const files = this.scanDirectory(fullPath); for (const file of files) { if (this.config.toolFilePattern.test(file)) { try { // Construire le chemin relatif correct depuis la racine du projet const relativePath = join(dir, file).replace(/\\/g, '/'); const modulePath = `../../${relativePath}`.replace(/\.js$/, '.js'); const module = await import(modulePath); modules.push({ path: relativePath, module }); } catch (error) { if (this.config.verbose) { console.error(`❌ Erreur lors du chargement du module ${file}:`, error); } } } } } return modules; } /** * Scanne récursivement un répertoire pour trouver des fichiers */ private scanDirectory(dir: string): string[] { const files: string[] = []; try { const entries = readdirSync(dir); for (const entry of entries) { const fullPath = join(dir, entry); const stat = statSync(fullPath); if (stat.isDirectory()) { files.push(...this.scanDirectory(fullPath).map(f => join(entry, f))); } else if (stat.isFile()) { files.push(entry); } } } catch (error) { if (this.config.verbose) { console.error(`❌ Erreur lors du scan de ${dir}:`, error); } } return files; } /** * Extrait les outils d'un module */ extractToolsFromModule(module: ToolModule): Array<{ tool: ToolDefinition; handler: ToolHandler }> { const tools: Array<{ tool: ToolDefinition; handler: ToolHandler }> = []; // Chercher les paires tool/handler const toolEntries = Object.entries(module).filter(([key]) => this.config.toolExportPattern.test(key) ); // Grouper par nom d'outil (sans suffixe) const toolGroups = new Map<string, { tool?: ToolDefinition; handler?: ToolHandler }>(); for (const [key, value] of toolEntries) { const baseName = key.replace(/(Tool|Handler)$/, ''); if (!toolGroups.has(baseName)) { toolGroups.set(baseName, {}); } const group = toolGroups.get(baseName)!; if (key.endsWith('Tool')) { group.tool = value as ToolDefinition; } else if (key.endsWith('Handler')) { group.handler = value as ToolHandler; } } // Créer les paires complètes for (const [baseName, group] of toolGroups) { if (group.tool && group.handler) { tools.push({ tool: group.tool, handler: group.handler }); } else if (this.config.verbose) { console.warn(`⚠️ Paire incomplète pour ${baseName}:`, { hasTool: !!group.tool, hasHandler: !!group.handler }); } } return tools; } /** * Enregistre automatiquement tous les outils découverts */ async autoRegister(): Promise<number> { if (this.config.verbose) { console.log('🔍 Découverte automatique des outils...'); } const modules = await this.discoverToolModules(); let registeredCount = 0; for (const { path, module } of modules) { const tools = this.extractToolsFromModule(module); for (const { tool, handler } of tools) { if (this.registeredTools.has(tool.name)) { if (this.config.verbose) { console.log(`⏭️ Outil déjà enregistré: ${tool.name}`); } continue; } try { toolRegistry.register(tool, handler); this.registeredTools.add(tool.name); registeredCount++; if (this.config.verbose) { console.log(`✅ Outil enregistré automatiquement: ${tool.name} (${path})`); } } catch (error) { if (this.config.verbose) { console.error(`❌ Erreur lors de l'enregistrement de ${tool.name}:`, error); } } } } if (this.config.verbose) { console.log(`🎉 Enregistrement automatique terminé: ${registeredCount} outils enregistrés`); console.log(`📊 Total d'outils dans le registre: ${toolRegistry.size()}`); } return registeredCount; } /** * Vérifie que tous les outils attendus sont enregistrés */ verifyRegistration(expectedTools: string[] = []): boolean { const missingTools: string[] = []; for (const toolName of expectedTools) { if (!toolRegistry.hasTool(toolName)) { missingTools.push(toolName); } } if (missingTools.length > 0) { console.error(`❌ Outils manquants: ${missingTools.join(', ')}`); return false; } console.log(`✅ Tous les outils attendus sont enregistrés (${expectedTools.length} outils)`); return true; } /** * Liste tous les outils enregistrés */ listRegisteredTools(): string[] { return Array.from(this.registeredTools); } /** * Réinitialise le registre (pour les tests) */ reset(): void { this.registeredTools.clear(); } } /** * Instance singleton du registre automatique */ export const autoRegistry = new AutoRegistry(); /** * Fonction utilitaire pour initialiser le registre automatique */ export async function initializeAutoRegistry(config?: Partial<RegistryConfig>): Promise<number> { const registry = new AutoRegistry(config); return await registry.autoRegister(); } /** * Fonction utilitaire pour obtenir la liste des outils attendus */ export function getExpectedTools(): string[] { return [ // Outils Graph 'create_entities', 'create_relations', 'add_observations', 'delete_entities', 'delete_observations', 'delete_relations', 'read_graph', 'search_nodes', 'open_nodes', // Outils RAG 'index_project', 'search_code', 'manage_projects', 'update_project' ]; } // Exécution automatique si ce fichier est exécuté directement if (import.meta.url === `file://${process.argv[1]}`) { console.log('🚀 Initialisation du registre automatique...'); initializeAutoRegistry({ verbose: true }).then(async (count) => { console.log(`\n📊 Résumé:`); console.log(`- Outils enregistrés: ${count}`); console.log(`- Total dans ToolRegistry: ${toolRegistry.size()}`); // Vérifier les outils attendus const expectedTools = getExpectedTools(); const registry = new AutoRegistry(); const allRegistered = registry.verifyRegistration(expectedTools); if (allRegistered) { console.log('🎉 Tous les outils sont correctement enregistrés !'); process.exit(0); } else { console.error('❌ Certains outils sont manquants'); process.exit(1); } }).catch(error => { console.error('❌ Erreur lors de l\'initialisation:', error); process.exit(1); }); }

Latest Blog Posts

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/ali-48/rag-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server