Skip to main content
Glama

SQL MCP Server

by polarisxb
MIT License
1
5
  • Apple
  • Linux
loader.ts14.7 kB
import * as path from 'path'; import * as fs from 'fs/promises'; import { ICache } from '../cache/interface.js'; import { PluginContext, IPlugin, PluginConstructor, PluginState, PluginInitOptions } from './interface.js'; import { PluginRegistry, PluginDependencyError, PluginRegistryEvent } from './registry.js'; import { ValidatedAppConfig } from '../config/schema.js'; /** * 插件发现方法 */ export type PluginDiscoveryMethod = (options: PluginLoaderOptions) => Promise<PluginConstructor[]>; /** * 插件加载器选项 */ export interface PluginLoaderOptions { /** * 插件目录路径 */ pluginsDir?: string; /** * 是否启用自动发现 */ autoDiscovery?: boolean; /** * 自定义插件发现方法 */ discoveryMethods?: PluginDiscoveryMethod[]; /** * 内置插件列表 */ builtinPlugins?: PluginConstructor[]; /** * 全局配置 */ config: ValidatedAppConfig; /** * 缓存实例 */ cache?: ICache; /** * 日志函数 */ logger?: { debug: (message: string, ...args: any[]) => void; info: (message: string, ...args: any[]) => void; warn: (message: string, ...args: any[]) => void; error: (message: string, ...args: any[]) => void; }; } /** * 插件加载结果 */ export interface PluginLoadResult { /** * 已加载的插件ID */ loadedPlugins: string[]; /** * 加载过程中发生的错误 */ errors: { pluginId: string; error: Error }[]; } /** * 插件加载器 * 负责发现、加载和初始化插件 */ export class PluginLoader { /** * 插件注册表 */ private registry: PluginRegistry; /** * 加载器选项 */ private options: Required<PluginLoaderOptions>; /** * 是否已初始化 */ private initialized = false; /** * 构造函数 * @param options 加载器选项 * @param registry 插件注册表实例 */ constructor( options: PluginLoaderOptions, registry: PluginRegistry = new PluginRegistry() ) { this.registry = registry; // 设置默认选项 this.options = { pluginsDir: path.join(process.cwd(), 'plugins'), autoDiscovery: true, discoveryMethods: [this.discoverFromDirectory], builtinPlugins: [], config: options.config, cache: options.cache as ICache, logger: options.logger || { debug: console.debug, info: console.info, warn: console.warn, error: console.error } }; // 覆盖默认选项 Object.assign(this.options, options); } /** * 初始化插件加载器 */ async initialize(): Promise<void> { if (this.initialized) { return; } // 注册内置插件 for (const pluginClass of this.options.builtinPlugins) { this.registry.registerPluginClass(pluginClass); } // 自动发现插件 if (this.options.autoDiscovery) { await this.discoverPlugins(); } this.initialized = true; } /** * 发现可用的插件 */ async discoverPlugins(): Promise<void> { let discoveredPluginClasses: PluginConstructor[] = []; // 使用所有注册的发现方法 for (const method of this.options.discoveryMethods) { try { const plugins = await method(this.options); discoveredPluginClasses = discoveredPluginClasses.concat(plugins); } catch (error) { this.options.logger.error(`Plugin discovery failed: ${(error as Error).message}`); } } // 注册发现的插件类 for (const pluginClass of discoveredPluginClasses) { try { this.registry.registerPluginClass(pluginClass); } catch (error) { this.options.logger.warn( `Failed to register discovered plugin: ${(error as Error).message}` ); } } } /** * 从目录加载插件 * @param options 加载器选项 * @returns 发现的插件类数组 */ private async discoverFromDirectory(options: PluginLoaderOptions): Promise<PluginConstructor[]> { const pluginsDir = options.pluginsDir; if (!pluginsDir) return []; try { // 检查目录是否存在 await fs.access(pluginsDir); } catch (error) { options.logger?.warn(`Plugins directory does not exist: ${pluginsDir}`); return []; } const discoveredPlugins: PluginConstructor[] = []; try { // 读取目录内容 const entries = await fs.readdir(pluginsDir, { withFileTypes: true }); // 筛选目录 const dirs = entries.filter(entry => entry.isDirectory()); for (const dir of dirs) { const pluginDir = path.join(pluginsDir, dir.name); try { // 尝试加载plugin.js或index.js const mainFiles = ['plugin.js', 'index.js']; let mainFile: string | undefined; for (const file of mainFiles) { const filePath = path.join(pluginDir, file); try { await fs.access(filePath); mainFile = filePath; break; } catch { // 文件不存在,继续检查下一个 } } if (!mainFile) { options.logger?.debug(`No plugin main file found in ${pluginDir}`); continue; } // 动态加载模块 const modulePath = path.resolve(mainFile); const module = await import(`file://${modulePath}`); // 查找插件类 const pluginClass = module.default || Object.values(module).find(value => { return typeof value === 'function' && value.prototype && ( value.prototype.onInitialize || value.prototype.onEnable || value.prototype.onDisable || value.prototype.onUninstall ); }); if (typeof pluginClass !== 'function') { options.logger?.debug(`No plugin class found in ${mainFile}`); continue; } // 添加到已发现的插件列表 discoveredPlugins.push(pluginClass as PluginConstructor); options.logger?.debug(`Discovered plugin in ${mainFile}`); } catch (error) { options.logger?.debug(`Error loading plugin from ${pluginDir}: ${(error as Error).message}`); } } } catch (error) { options.logger?.error(`Error reading plugins directory: ${(error as Error).message}`); } return discoveredPlugins; } /** * 创建插件上下文 * @param pluginId 插件ID * @returns 插件上下文对象 */ private createPluginContext(pluginId: string): PluginContext { const emitter = this.registry; return { getPlugin: <T extends IPlugin = IPlugin>(id: string): T | undefined => { return this.registry.getPlugin<T>(id); }, getConfig: <T = any>(key?: string): T => { const pluginConfig = this.options.config; if (!key) { return pluginConfig as unknown as T; } const parts = key.split('.'); let current: any = pluginConfig; for (const part of parts) { if (current === undefined || current === null) { return undefined as unknown as T; } current = current[part]; } return current as T; }, getLogger: (name?: string): any => { const loggerName = name || pluginId; const base = (this.options as any).logger // Prefer DI logger with child() if available if (base && typeof (base as any).child === 'function') { return (base as any).child(loggerName) } return { debug: (message: string, ...args: any[]) => this.options.logger.debug(`[${loggerName}] ${message}`, ...args), info: (message: string, ...args: any[]) => this.options.logger.info(`[${loggerName}] ${message}`, ...args), warn: (message: string, ...args: any[]) => this.options.logger.warn(`[${loggerName}] ${message}`, ...args), error: (message: string, ...args: any[]) => this.options.logger.error(`[${loggerName}] ${message}`, ...args) }; }, getCache: (namespace?: string): any => { const cacheNamespace = namespace || `plugin:${pluginId}`; if (!this.options.cache) { // 如果没有提供缓存,返回无操作缓存 return { get: async () => undefined, set: async () => {}, has: async () => false, delete: async () => false, clear: async () => {}, getStats: async () => ({ hits: 0, misses: 0, size: 0, keys: [] }) }; } return this.options.cache.withNamespace(cacheNamespace); }, on: <T = any>(event: string, handler: (data: T) => void): () => void => { (emitter as any).on(event as any, handler as any); return () => { (emitter as any).removeListener(event as any, handler as any); }; }, emit: <T = any>(event: string, data?: T): void => { emitter.emit(event, data); } }; } /** * 加载插件 * 初始化已注册的插件 * @param options 插件初始化选项 * @returns 加载结果 */ async loadPlugins(options?: Record<string, PluginInitOptions>): Promise<PluginLoadResult> { if (!this.initialized) { await this.initialize(); } const result: PluginLoadResult = { loadedPlugins: [], errors: [] }; // 按依赖顺序解析插件 let pluginIds: string[]; try { pluginIds = this.registry.resolveLoadOrder(); } catch (error) { if (error instanceof PluginDependencyError) { result.errors.push({ pluginId: error.pluginId, error }); // 过滤掉有问题的插件及其依赖 pluginIds = this.registry .getAllPluginMetadata() .filter(metadata => !metadata.dependencies?.includes(error.dependencyId)) .map(metadata => metadata.id); } else { throw error; } } // 逐个初始化插件 for (const pluginId of pluginIds) { // 检查是否已经实例化 let plugin = this.registry.getPlugin(pluginId); const pluginClass = !plugin ? await this.getPluginClass(pluginId) : null; if (!plugin && !pluginClass) { this.options.logger.warn(`Plugin ${pluginId} not found`); continue; } try { // 如果插件未实例化,则创建实例 if (!plugin && pluginClass) { plugin = new pluginClass(); this.registry.registerPlugin(plugin); } // 已经初始化的插件跳过 if (plugin!.state !== PluginState.REGISTERED) { continue; } // 创建插件上下文 const context = this.createPluginContext(pluginId); // 初始化插件 await plugin!.initialize(context, options?.[pluginId]); result.loadedPlugins.push(pluginId); this.registry.emit('initialized', plugin!.metadata); if ((plugin as IPlugin).state === PluginState.ENABLED) { this.registry.emit('enabled', plugin!.metadata); } } catch (error: any) { this.options.logger.error(`Failed to initialize plugin ${pluginId}: ${error.message}`); result.errors.push({ pluginId, error }); } } return result; } /** * 启用插件 * @param pluginId 插件ID */ async enablePlugin(pluginId: string): Promise<void> { const plugin = this.registry.getPlugin(pluginId); if (!plugin) { throw new Error(`Plugin ${pluginId} not found`); } // 已启用则直接返回,保持幂等 if (plugin.state === PluginState.ENABLED) { return; } if (plugin.state !== PluginState.INITIALIZED && plugin.state !== PluginState.DISABLED) { throw new Error(`Cannot enable plugin ${pluginId} in state ${plugin.state}`); } await plugin.enable(); this.registry.emit('enabled', plugin.metadata); } /** * 禁用插件 * @param pluginId 插件ID */ async disablePlugin(pluginId: string): Promise<void> { const plugin = this.registry.getPlugin(pluginId); if (!plugin) { throw new Error(`Plugin ${pluginId} not found`); } if (plugin.state !== PluginState.ENABLED) { throw new Error(`Cannot disable plugin ${pluginId} in state ${plugin.state}`); } // 检查是否有依赖此插件的启用中插件 const dependentIds = this.registry.getDependentPlugins(pluginId); for (const depId of dependentIds) { const depPlugin = this.registry.getPlugin(depId); if (depPlugin && depPlugin.state === PluginState.ENABLED) { throw new Error( `Cannot disable plugin ${pluginId} because it is required by ${depId}` ); } } await plugin.disable(); this.registry.emit('disabled', plugin.metadata); } /** * 卸载插件 * @param pluginId 插件ID */ async uninstallPlugin(pluginId: string): Promise<void> { const plugin = this.registry.getPlugin(pluginId); if (!plugin) { throw new Error(`Plugin ${pluginId} not found`); } // 如果插件已启用,先禁用它 if (plugin.state === PluginState.ENABLED) { await this.disablePlugin(pluginId); } // 卸载插件 await plugin.uninstall(); // 从注册表中移除 this.registry.unregisterPlugin(pluginId); } /** * 获取插件类 * @param pluginId 插件ID */ private async getPluginClass(pluginId: string): Promise<PluginConstructor | undefined> { const metadata = this.registry.getPluginMetadata(pluginId); if (!metadata) { return undefined; } // 通过显式的API获取构造函数 return this.registry.getPluginConstructor(pluginId); } /** * 获取插件注册表 * @returns 插件注册表实例 */ getRegistry(): PluginRegistry { return this.registry; } /** * 获取已启用的插件 * @returns 已启用的插件列表 */ getEnabledPlugins(): IPlugin[] { return this.registry .getAllPlugins() .filter(plugin => plugin.state === PluginState.ENABLED); } }

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/polarisxb/sql-mcp'

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