Skip to main content
Glama
registry.ts9.07 kB
import { readdir } from "node:fs/promises"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import type { Command } from "commander"; import type { DomainDiscoveryResult, DomainModule, DomainRegistryEntry, } from "../shared/types/domain.types.js"; import { Logger } from "../shared/utils/logger.util.js"; /** * Domain Registry class for managing domain discovery and registration */ export class DomainRegistry { private logger: ReturnType<typeof Logger.forContext> | null = null; private getLogger() { if (!this.logger) { this.logger = Logger.forContext("domains/registry.ts"); } return this.logger; } private readonly domainsPath: string; private readonly registryMap = new Map<string, DomainRegistryEntry>(); private discoveredDomains: DomainDiscoveryResult[] = []; constructor() { // Get the current file's directory and resolve domains path const currentDir = fileURLToPath(new URL(".", import.meta.url)); this.domainsPath = currentDir; // We're already in the domains directory } /** * Discover all domains in the domains directory */ async discoverDomains(): Promise<DomainDiscoveryResult[]> { const methodLogger = this.getLogger().forMethod("discoverDomains"); methodLogger.debug("Starting domain discovery", { domainsPath: this.domainsPath, }); try { const entries = await readdir(this.domainsPath, { withFileTypes: true }); const domainDirs = entries.filter( (entry) => entry.isDirectory() && !entry.name.startsWith("."), ); const results: DomainDiscoveryResult[] = []; for (const dir of domainDirs) { const domainPath = join(this.domainsPath, dir.name); const result = await this.analyzeDomain(dir.name, domainPath); results.push(result); } this.discoveredDomains = results; methodLogger.info(`Discovered ${results.length} domains`, { domains: results.map((r) => r.name), }); return results; } catch (error) { methodLogger.error("Error discovering domains", { error }); throw error; } } /** * Analyze a domain directory to check if it's a valid domain */ private async analyzeDomain( name: string, domainPath: string, ): Promise<DomainDiscoveryResult> { const methodLogger = this.getLogger().forMethod("analyzeDomain"); methodLogger.debug(`Analyzing domain: ${name}`, { domainPath }); try { const files = await readdir(domainPath); const hasTools = files.some( (file) => file.endsWith(".tool.ts") || file.endsWith(".tool.js"), ); const hasCli = files.some( (file) => file.endsWith(".cli.ts") || file.endsWith(".cli.js"), ); const hasResources = files.some( (file) => file.endsWith(".resource.ts") || file.endsWith(".resource.js"), ); const hasIndex = files.includes("index.ts") || files.includes("index.js"); const isValid = hasIndex && (hasTools || hasCli || hasResources); return { name, path: domainPath, hasTools, hasCli, hasResources, isValid, }; } catch (error) { methodLogger.error(`Error analyzing domain ${name}`, { error }); return { name, path: domainPath, hasTools: false, hasCli: false, hasResources: false, isValid: false, error: error instanceof Error ? error.message : String(error), }; } } /** * Load a specific domain module */ async loadDomain(name: string): Promise<DomainModule | null> { const methodLogger = this.getLogger().forMethod("loadDomain"); methodLogger.debug(`Loading domain: ${name}`); try { const domainPath = join(this.domainsPath, name); const indexPath = join(domainPath, "index.js"); // Check if the domain exists in our discovered domains const discoveredDomain = this.discoveredDomains.find( (d) => d.name === name, ); if (!discoveredDomain || !discoveredDomain.isValid) { methodLogger.warn(`Domain ${name} not found or invalid`); return null; } // Dynamic import of the domain module const domainModule = await import(indexPath); // Extract tool, CLI, and resource exports based on naming convention const tool = domainModule[`${name}Tool`]; const cli = domainModule[`${name}Cli`]; const resource = domainModule[`${name}Resource`]; const module: DomainModule = { tool, cli, resource, meta: { name, description: `${name.charAt(0).toUpperCase() + name.slice(1)} domain`, version: "1.0.0", }, }; // Update registry this.registryMap.set(name, { name, path: domainPath, module, loaded: true, }); methodLogger.info(`Successfully loaded domain: ${name}`, { hasTools: !!tool, hasCli: !!cli, hasResources: !!resource, }); return module; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); const errorStack = error instanceof Error ? error.stack : undefined; methodLogger.error(`Failed to load domain ${name}`, { error: errorMessage, stack: errorStack, indexPath: join(this.domainsPath, name, "index.js"), name, }); // Update registry with error this.registryMap.set(name, { name, path: join(this.domainsPath, name), module: {}, loaded: false, error: errorMessage, }); return null; } } /** * Load all valid domains */ async loadAllDomains(): Promise<DomainModule[]> { const methodLogger = this.getLogger().forMethod("loadAllDomains"); methodLogger.debug("Loading all domains"); if (this.discoveredDomains.length === 0) { await this.discoverDomains(); } const validDomains = this.discoveredDomains.filter((d) => d.isValid); const loadedModules: DomainModule[] = []; for (const domain of validDomains) { const module = await this.loadDomain(domain.name); if (module) { loadedModules.push(module); } } methodLogger.info(`Loaded ${loadedModules.length} domains successfully`); return loadedModules; } /** * Register all domain tools with the MCP server */ async registerAllTools(server: McpServer): Promise<void> { const methodLogger = this.getLogger().forMethod("registerAllTools"); methodLogger.debug("Registering all domain tools"); const domains = await this.loadAllDomains(); let registeredCount = 0; for (const domain of domains) { if (domain.tool && typeof domain.tool.registerTools === "function") { try { domain.tool.registerTools(server); registeredCount++; methodLogger.debug( `Registered tools for domain: ${domain.meta?.name}`, ); } catch (error) { methodLogger.error( `Failed to register tools for domain: ${domain.meta?.name}`, { error }, ); } } } methodLogger.info(`Registered tools for ${registeredCount} domains`); } /** * Register all domain CLI commands with the Commander program */ async registerAllCli(program: Command): Promise<void> { const methodLogger = this.getLogger().forMethod("registerAllCli"); methodLogger.debug("Registering all domain CLI commands"); const domains = await this.loadAllDomains(); let registeredCount = 0; for (const domain of domains) { if (domain.cli && typeof domain.cli.register === "function") { try { domain.cli.register(program); registeredCount++; methodLogger.debug( `Registered CLI commands for domain: ${domain.meta?.name}`, ); } catch (error) { methodLogger.error( `Failed to register CLI commands for domain: ${domain.meta?.name}`, { error }, ); } } } methodLogger.info(`Registered CLI commands for ${registeredCount} domains`); } /** * Register all domain resources with the MCP server */ async registerAllResources(server: McpServer): Promise<void> { const methodLogger = this.getLogger().forMethod("registerAllResources"); methodLogger.debug("Registering all domain resources"); const domains = await this.loadAllDomains(); let registeredCount = 0; for (const domain of domains) { if ( domain.resource && typeof domain.resource.registerResources === "function" ) { try { domain.resource.registerResources(server); registeredCount++; methodLogger.debug( `Registered resources for domain: ${domain.meta?.name}`, ); } catch (error) { methodLogger.error( `Failed to register resources for domain: ${domain.meta?.name}`, { error }, ); } } } methodLogger.info(`Registered resources for ${registeredCount} domains`); } /** * Get registry status and health information */ getRegistryStatus(): { discovered: number; loaded: number; failed: number; entries: DomainRegistryEntry[]; } { const entries = Array.from(this.registryMap.values()); return { discovered: this.discoveredDomains.length, loaded: entries.filter((e) => e.loaded).length, failed: entries.filter((e) => !e.loaded).length, entries, }; } } // Export singleton instance export const domainRegistry = new DomainRegistry();

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/AbdallahAHO/lokalise-mcp'

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