Skip to main content
Glama

SQL MCP Server

by polarisxb
MIT License
1
5
  • Apple
  • Linux
registry.ts10 kB
import { EventEmitter } from 'events'; import { IPlugin, PluginConstructor, PluginMetadata, PluginState } from './interface.js'; /** * 插件注册表事件类型 */ export type PluginRegistryEvent = | 'registered' // 插件注册事件 | 'unregistered' // 插件注销事件 | 'initialized' // 插件初始化事件 | 'enabled' // 插件启用事件 | 'disabled' // 插件禁用事件 | 'error'; // 插件错误事件 /** * 插件注册表错误类 */ export class PluginRegistryError extends Error { /** * 构造函数 * @param message 错误信息 * @param pluginId 相关的插件ID */ constructor( message: string, public readonly pluginId?: string ) { super(message); this.name = 'PluginRegistryError'; } } /** * 插件依赖错误类 */ export class PluginDependencyError extends PluginRegistryError { /** * 构造函数 * @param message 错误信息 * @param pluginId 插件ID * @param dependencyId 依赖的插件ID */ constructor( message: string, public readonly pluginId: string, public readonly dependencyId: string ) { super(message, pluginId); this.name = 'PluginDependencyError'; } } /** * 插件注册表 * 管理插件的注册、查找和生命周期 */ export class PluginRegistry extends EventEmitter { /** * 存储已注册的插件实例 * 键是插件ID,值是插件实例 */ private plugins = new Map<string, IPlugin>(); /** * 存储插件类构造函数 * 键是插件ID,值是插件类 */ private pluginConstructors = new Map<string, PluginConstructor>(); /** * 存储插件元数据 * 键是插件ID,值是元数据 */ private pluginMetadata = new Map<string, PluginMetadata>(); /** * 存储插件依赖图 * 键是插件ID,值是依赖它的插件ID数组 */ private dependencyGraph = new Map<string, string[]>(); /** * 构造函数 */ constructor() { super(); // 设置最大监听器数量,避免警告 this.setMaxListeners(100); } /** * 注册插件类 * @param pluginClass 插件类 * @returns 注册后的插件元数据 * @throws 如果插件已注册或ID无效 */ registerPluginClass(pluginClass: PluginConstructor): PluginMetadata { // 创建临时实例获取元数据 const tempInstance = new pluginClass(); const metadata = tempInstance.metadata; if (!metadata.id) { throw new PluginRegistryError('Plugin ID is required'); } if (this.hasPlugin(metadata.id)) { throw new PluginRegistryError(`Plugin ${metadata.id} is already registered`, metadata.id); } // 保存构造函数和元数据 this.pluginConstructors.set(metadata.id, pluginClass); this.pluginMetadata.set(metadata.id, metadata); // 更新依赖图 if (metadata.dependencies?.length) { for (const depId of metadata.dependencies) { if (!this.dependencyGraph.has(depId)) { this.dependencyGraph.set(depId, []); } this.dependencyGraph.get(depId)!.push(metadata.id); } } // 触发注册事件 this.emit('registered', metadata); return metadata; } /** * 注册插件实例 * @param plugin 插件实例 * @returns 注册的插件实例 * @throws 如果插件已注册或ID无效 */ registerPlugin(plugin: IPlugin): IPlugin { const metadata = plugin.metadata; if (!metadata.id) { throw new PluginRegistryError('Plugin ID is required'); } // 仅当同名实例已存在时阻止注册 if (this.plugins.has(metadata.id)) { throw new PluginRegistryError(`Plugin ${metadata.id} is already registered`, metadata.id); } // 保存插件实例和元数据 this.plugins.set(metadata.id, plugin); this.pluginMetadata.set(metadata.id, metadata); // 更新依赖图 if (metadata.dependencies?.length) { for (const depId of metadata.dependencies) { if (!this.dependencyGraph.has(depId)) { this.dependencyGraph.set(depId, []); } this.dependencyGraph.get(depId)!.push(metadata.id); } } // 触发注册事件 this.emit('registered', metadata); return plugin; } /** * 注销插件 * @param id 插件ID * @returns 是否成功注销 */ unregisterPlugin(id: string): boolean { if (!this.hasPlugin(id)) { return false; } // 检查是否有其他插件依赖此插件 const dependents = this.dependencyGraph.get(id) || []; if (dependents.length > 0) { const activeDependent = dependents.find(depId => { const plugin = this.plugins.get(depId); return plugin && plugin.state === PluginState.ENABLED; }); if (activeDependent) { throw new PluginRegistryError( `Cannot unregister plugin ${id} because it is required by ${activeDependent}`, id ); } } // 从注册表中移除 const metadata = this.pluginMetadata.get(id); this.plugins.delete(id); this.pluginConstructors.delete(id); this.pluginMetadata.delete(id); // 从依赖图中移除 this.dependencyGraph.delete(id); for (const [depId, deps] of this.dependencyGraph.entries()) { this.dependencyGraph.set( depId, deps.filter(pid => pid !== id) ); } // 触发注销事件 if (metadata) { this.emit('unregistered', metadata); } return true; } /** * 获取插件实例 * @param id 插件ID * @returns 插件实例或undefined */ getPlugin<T extends IPlugin = IPlugin>(id: string): T | undefined { return this.plugins.get(id) as T | undefined; } /** * 获取插件元数据 * @param id 插件ID * @returns 插件元数据或undefined */ getPluginMetadata(id: string): PluginMetadata | undefined { return this.pluginMetadata.get(id); } /** * 获取插件构造函数 * @param id 插件ID * @returns 插件构造函数或undefined */ getPluginConstructor(id: string): PluginConstructor | undefined { return this.pluginConstructors.get(id); } /** * 检查插件是否已注册 * @param id 插件ID * @returns 是否已注册 */ hasPlugin(id: string): boolean { return this.plugins.has(id) || this.pluginConstructors.has(id); } /** * 获取所有已注册插件的元数据 * @returns 插件元数据数组 */ getAllPluginMetadata(): PluginMetadata[] { return Array.from(this.pluginMetadata.values()); } /** * 获取所有已注册的插件实例 * @returns 插件实例数组 */ getAllPlugins(): IPlugin[] { return Array.from(this.plugins.values()); } /** * 获取依赖于指定插件的插件ID * @param id 插件ID * @returns 依赖于此插件的插件ID数组 */ getDependentPlugins(id: string): string[] { return this.dependencyGraph.get(id) || []; } /** * 计算插件的依赖顺序 * 按照依赖关系和loadOrder排序 * @returns 按正确顺序排列的插件ID数组 */ resolveLoadOrder(): string[] { const result: string[] = []; const visited = new Set<string>(); const visiting = new Set<string>(); // 获取所有注册的插件ID const pluginIds = Array.from(this.pluginMetadata.keys()); // 按loadOrder排序 const sortedIds = pluginIds.sort((a, b) => { const metadataA = this.pluginMetadata.get(a); const metadataB = this.pluginMetadata.get(b); const orderA = metadataA?.loadOrder ?? 100; const orderB = metadataB?.loadOrder ?? 100; return orderA - orderB; }); // 深度优先搜索解决依赖顺序 const visit = (id: string): void => { // 已处理过的节点 if (visited.has(id)) { return; } // 检测循环依赖 if (visiting.has(id)) { throw new PluginRegistryError(`Circular dependency detected for plugin ${id}`, id); } // 标记为正在访问 visiting.add(id); // 先访问依赖 const metadata = this.pluginMetadata.get(id); if (metadata?.dependencies?.length) { for (const depId of metadata.dependencies) { // 确保依赖存在 if (!this.hasPlugin(depId)) { throw new PluginDependencyError( `Plugin ${id} depends on ${depId}, which is not registered`, id, depId ); } visit(depId); } } // 标记为已访问并添加到结果 visiting.delete(id); visited.add(id); result.push(id); }; // 访问所有插件 for (const id of sortedIds) { visit(id); } return result; } /** * 监听插件注册表事件 * @param event 事件类型 * @param listener 监听器函数 * @returns this,支持链式调用 */ on(event: PluginRegistryEvent, listener: (...args: any[]) => void): this { return super.on(event, listener); } /** * 注册事件监听器 * @param event 事件类型 * @param listener 监听器函数 * @returns this,支持链式调用 */ addListener(event: PluginRegistryEvent, listener: (...args: any[]) => void): this { return super.addListener(event, listener); } /** * 移除事件监听器 * @param event 事件类型 * @param listener 监听器函数 * @returns this,支持链式调用 */ removeListener(event: PluginRegistryEvent, listener: (...args: any[]) => void): this { return super.removeListener(event, listener); } /** * 清空注册表 * 移除所有插件 */ clear(): void { this.plugins.clear(); this.pluginConstructors.clear(); this.pluginMetadata.clear(); this.dependencyGraph.clear(); } }

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