UNPKG

prompt-discovery-mcp

Version:

基于Nacos的动态提示词MCP服务器,支持智能推荐和实时热更新

391 lines (390 loc) 13.6 kB
import { NacosConfigClient } from 'nacos-config'; import * as fs from 'fs'; import * as path from 'path'; import { EventEmitter } from 'events'; import JSON5 from 'json5'; /** * Nacos服务类,处理与Nacos的连接和配置管理 */ export class NacosService extends EventEmitter { options; client; cacheFilePath; configData = null; refreshTimer = null; reconnectTimer = null; isConnected = false; reconnectAttempts = 0; maxReconnectAttempts = 10; /** * 构造函数 * @param options Nacos服务配置选项 */ constructor(options) { super(); this.options = { cachePath: path.join(process.cwd(), '.cache'), refreshInterval: 30000, // 默认30秒刷新一次 ...options }; this.client = new NacosConfigClient({ serverAddr: this.options.serverAddr, namespace: this.options.namespace, username: this.options.username, password: this.options.password, requestTimeout: this.options.requestTimeout || 6000, }); // 确保缓存目录存在 if (!fs.existsSync(this.options.cachePath)) { fs.mkdirSync(this.options.cachePath, { recursive: true }); } this.cacheFilePath = path.join(this.options.cachePath, `${this.options.configId}.json`); } /** * 初始化服务 * 连接Nacos并开始配置监听 */ async init() { try { // 尝试从Nacos获取配置 await this.refreshConfig(); // 设置定时刷新 this.startConfigRefresh(); // 注册Nacos配置变更监听 await this.registerConfigListener(); this.isConnected = true; this.reconnectAttempts = 0; // 重置重连尝试次数 if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; } this.emit('ready', true); } catch (error) { // 连接失败时尝试加载本地缓存 this.isConnected = false; this.loadFromCache(); const err = error; const errorResponse = { code: 'NACOS_CONNECTION_ERROR', message: '无法连接到Nacos配置中心', details: err.message }; console.error('Nacos连接失败:', err.message); this.emit('error', errorResponse); // 启动重连机制 this.scheduleReconnect(); // 即使连接失败,也设置定时尝试重连 this.startConfigRefresh(); } } /** * 开始配置定时刷新 */ startConfigRefresh() { if (this.refreshTimer) { clearInterval(this.refreshTimer); } this.refreshTimer = setInterval(async () => { try { await this.refreshConfig(); // 如果之前断开连接,现在恢复了,触发重新连接事件 if (!this.isConnected) { this.isConnected = true; this.reconnectAttempts = 0; // 重置重连尝试次数 this.emit('connected'); } } catch (error) { const err = error; console.error('刷新配置失败:', err.message); if (this.isConnected) { this.isConnected = false; this.emit('disconnected', { code: 'NACOS_REFRESH_ERROR', message: '刷新Nacos配置失败', details: err.message }); // 启动重连机制 this.scheduleReconnect(); } // 连接失败时使用缓存 this.loadFromCache(); } }, this.options.refreshInterval); } /** * 注册Nacos配置变更监听 */ async registerConfigListener() { this.client.subscribe({ dataId: this.options.configId, group: this.options.group }, (content) => { try { const newConfig = JSON5.parse(content); this.configData = newConfig; this.saveToCache(newConfig); this.emit('configChanged', newConfig); } catch (error) { const err = error; this.emit('error', { code: 'CONFIG_PARSE_ERROR', message: '解析配置数据失败', details: err.message }); } }); } /** * 刷新配置 */ async refreshConfig() { try { const content = await this.client.getConfig(this.options.configId, this.options.group); if (!content) { throw new Error('从Nacos获取的配置为空'); } try { const config = JSON5.parse(content); this.configData = config; this.saveToCache(config); if (!this.isConnected) { this.isConnected = true; this.emit('connected'); } return; } catch (error) { const err = error; throw new Error(`解析配置失败: ${content} ${err.message}`); } } catch (error) { // 重新抛出异常,让上层处理 throw error; } } /** * 将配置保存到本地缓存 */ saveToCache(config) { try { fs.writeFileSync(this.cacheFilePath, JSON.stringify(config, null, 2)); } catch (error) { const err = error; console.error('保存缓存失败:', err.message); } } /** * 从本地缓存加载配置 */ loadFromCache() { try { if (fs.existsSync(this.cacheFilePath)) { const content = fs.readFileSync(this.cacheFilePath, 'utf-8'); // 尝试解析缓存内容 const parsed = JSON5.parse(content); // 处理不同的缓存格式 if (Array.isArray(parsed)) { // 如果缓存是数组格式,确保每个提示词对象都有 title 字段 const prompts = parsed.map((p) => { if (!p.title) { p.title = p.description || p.id || '未命名提示词'; } if (!p.tags) { p.tags = []; } return p; }); this.configData = prompts; } else if (parsed && typeof parsed === 'object') { if (Array.isArray(parsed.prompts)) { // 如果缓存是旧的 PromptConfig 对象格式 // 同样确保每个提示词对象都有 title 字段 const prompts = parsed.prompts.map((p) => { if (!p.title) { p.title = p.description || p.id || '未命名提示词'; } if (!p.tags) { p.tags = []; } return p; }); this.configData = prompts; } else { // 如果缓存是其他对象格式,尝试转换 const prompt = parsed; if (!prompt.title) { prompt.title = prompt.description || prompt.id || '未命名提示词'; } if (!prompt.tags) { prompt.tags = []; } this.configData = [prompt]; } } else { throw new Error('无法识别的缓存格式'); } // 确保 configData 存在 if (!this.configData) { this.configData = []; } this.emit('usingCache', this.configData); } else { // 如果缓存文件不存在,初始化为空数组 this.configData = []; } } catch (error) { const err = error; console.error('加载缓存失败:', err.message); // 确保即使在错误情况下也有有效的配置数据 this.configData = []; this.emit('error', { code: 'CACHE_LOAD_ERROR', message: '加载本地缓存失败', details: err.message }); } } /** * 获取所有提示词 */ getAllPrompts() { if (!this.configData) { return []; } // 确保所有提示词都有必要的字段 return this.configData.map((p) => { if (!p.title) { p.title = p.description || p.id || '未命名提示词'; } if (!p.tags) { p.tags = []; } return p; }); } /** * 根据标签获取提示词 */ getPromptsByTags(tags) { const prompts = this.getAllPrompts(); if (tags.length === 0) { return prompts; } return prompts.filter(prompt => tags.some(tag => prompt.tags.includes(tag))); } /** * 根据ID获取提示词 */ getPromptById(id) { const prompts = this.getAllPrompts(); return prompts.find(prompt => prompt.id === id); } /** * 搜索提示词 * @param keyword 搜索关键词 */ searchPrompts(keyword) { if (!keyword) { return this.getAllPrompts(); } const lowerKeyword = keyword.toLowerCase(); return this.getAllPrompts().filter(prompt => prompt.title.toLowerCase().includes(lowerKeyword) || prompt.description.toLowerCase().includes(lowerKeyword) || prompt.content.toLowerCase().includes(lowerKeyword) || prompt.tags.some(tag => tag.toLowerCase().includes(lowerKeyword))); } /** * 记录提示词使用情况 * @param promptId 提示词ID * @returns 更新后的提示词对象,如果未找到则返回undefined */ trackPromptUsage(promptId) { if (!this.configData) { return undefined; } const promptIndex = this.configData.findIndex(p => p.id === promptId); if (promptIndex === -1) { return undefined; } // 更新使用统计 const prompt = this.configData[promptIndex]; prompt.usageCount = (prompt.usageCount || 0) + 1; prompt.lastUsed = new Date().toISOString(); // 更新配置数据 this.configData[promptIndex] = prompt; // 保存到缓存 this.saveToCache(this.configData); return prompt; } /** * 安排重新连接 */ scheduleReconnect() { if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); } // 如果超过最大重连次数,停止重连 if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.error(`已达到最大重连尝试次数(${this.maxReconnectAttempts}),停止重连`); return; } // 使用指数退避策略计算下一次重连时间 const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000); this.reconnectAttempts++; console.log(`计划在 ${delay}ms 后进行第 ${this.reconnectAttempts} 次重连尝试`); this.reconnectTimer = setTimeout(async () => { try { console.log(`尝试第 ${this.reconnectAttempts} 次重新连接到 Nacos...`); await this.refreshConfig(); this.isConnected = true; this.reconnectAttempts = 0; // 重置重连计数 console.log('重新连接到 Nacos 成功'); this.emit('connected'); } catch (error) { const err = error; console.error(`第 ${this.reconnectAttempts} 次重连失败:`, err.message); // 安排下一次重连 this.scheduleReconnect(); } }, delay); } /** * 关闭服务 */ async close() { if (this.refreshTimer) { clearInterval(this.refreshTimer); this.refreshTimer = null; } if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; } try { this.client.unSubscribe({ dataId: this.options.configId, group: this.options.group }, null); } catch (error) { console.error('取消订阅失败:', error); } } /** * 获取连接状态 */ getConnectionStatus() { return this.isConnected; } }