UNPKG

prompt-discovery-mcp

Version:

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

274 lines (273 loc) 10.7 kB
#!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { NacosService } from "./services/NacosService.js"; import { readFileSync } from "fs"; import { join } from "path"; // 不再需要扩展 McpServer 类型,使用标准的 tools.register 方法 // 获取包版本 let version = '1.0.2'; // 默认版本 try { // 尝试从当前工作目录读取 package.json const packageJson = JSON.parse(readFileSync(join(process.cwd(), 'package.json'), 'utf-8')); version = packageJson.version || version; } catch (error) { // 如果无法读取 package.json,使用默认版本 console.error('警告:无法读取 package.json,使用默认版本', version); } // 创建服务器实例 const server = new McpServer({ name: "PromptDiscovery", version, }); if (process.env.NACOS_USERNAME && !process.env.NACOS_PASSWORD) { console.error('错误:缺少必要的环境变量 NACOS_PASSWORD'); process.exit(1); } if (!process.env.NACOS_DATA_ID) { console.error('错误:缺少必要的环境变量 NACOS_DATA_ID'); process.exit(1); } // 从环境变量获取Nacos配置 const nacosService = new NacosService({ serverAddr: process.env.NACOS_ADDR || '127.0.0.1:8848', username: process.env.NACOS_USERNAME || 'nacos', password: process.env.NACOS_PASSWORD, group: process.env.NACOS_GROUP || 'DEFAULT_GROUP', configId: process.env.NACOS_DATA_ID, refreshInterval: parseInt(process.env.NACOS_REFRESH_INTERVAL || '30000') // 默认30秒刷新一次 }); // 注册工具:获取所有提示词 server.tool("getAllPrompts", "获取所有可用的提示词", {}, async () => { const prompts = nacosService.getAllPrompts(); return { content: [{ type: "text", text: JSON.stringify({ prompts, count: prompts.length }) }] }; }); // 注册工具:按标签获取提示词 server.tool("getPromptsByTags", "根据标签获取提示词", { tags: z.array(z.string()).describe("标签列表") }, async (params) => { const prompts = nacosService.getPromptsByTags(params.tags); return { content: [{ type: "text", text: JSON.stringify({ prompts }) }] }; }); // 注册工具:按ID获取提示词 server.tool("getPromptById", "根据ID获取特定提示词", { id: z.string().describe("提示词ID"), trackUsage: z.boolean().optional().describe("是否记录使用统计") }, async (params) => { const prompt = nacosService.getPromptById(params.id); // 如果找到提示词并且需要记录使用统计 if (prompt && params.trackUsage) { nacosService.trackPromptUsage(params.id); } return { content: [{ type: "text", text: JSON.stringify({ prompt: prompt || null }) }] }; }); // 注册工具:搜索提示词 server.tool("searchPrompts", "根据关键词搜索提示词", { keyword: z.string().describe("搜索关键词"), limit: z.number().optional().describe("返回结果数量限制"), trackUsage: z.boolean().optional().describe("是否记录使用统计") }, async (params) => { let results = nacosService.searchPrompts(params.keyword); if (params.limit && params.limit > 0) { results = results.slice(0, params.limit); } // 如果需要记录使用统计并且有结果 if (params.trackUsage && results.length > 0) { // 记录第一个结果的使用统计 nacosService.trackPromptUsage(results[0].id); } return { content: [{ type: "text", text: JSON.stringify({ prompts: results, totalMatches: results.length }) }] }; }); // 注册工具:根据当前上下文推荐相关提示词 server.tool("suggestPrompts", "根据当前上下文推荐相关提示词", { context: z.string().describe("当前编码上下文"), limit: z.number().optional().describe("返回结果数量限制") }, async (params) => { // 改进的上下文匹配逻辑 // 1. 提取关键词 const context = params.context.toLowerCase(); // 按不同分隔符拆分上下文 const contextWords = new Set(); // 按空格分词 context.split(/\s+/).forEach(word => { if (word.length > 2) contextWords.add(word); }); // 按常见编程符号分词 context.split(/[\.,\(\)\{\}\[\]\:\;\-\_\=\+\/\*]/) .forEach(word => { word = word.trim(); if (word.length > 2) contextWords.add(word); }); // 提取驼峰命名法单词 const camelCaseWords = context.match(/[a-z]+(?=[A-Z])|[A-Z][a-z]+/g) || []; camelCaseWords.forEach(word => { if (word.length > 2) contextWords.add(word.toLowerCase()); }); // 提取常见编程关键词 const programmingKeywords = [ 'function', 'class', 'method', 'test', 'api', 'component', 'service', 'model', 'view', 'controller', 'database', 'query', 'error', 'exception', 'debug', 'performance', 'optimization', 'security', 'authentication', 'authorization', 'validation' ]; programmingKeywords.forEach(keyword => { if (context.includes(keyword)) { contextWords.add(keyword); } }); const allPrompts = nacosService.getAllPrompts(); // 为每个提示词计算相关性分数 const scoredPrompts = allPrompts.map(prompt => { let score = 0; const keywordMatches = []; // 标签匹配 (权重最高) for (const tag of prompt.tags) { const tagLower = tag.toLowerCase(); for (const word of contextWords) { if (tagLower.includes(word) || word.includes(tagLower)) { score += 3; keywordMatches.push(tag); break; } } } // 标题匹配 (权重次之) const titleLower = prompt.title.toLowerCase(); for (const word of contextWords) { if (titleLower.includes(word)) { score += 2; keywordMatches.push(word); } } // 描述匹配 const descLower = prompt.description.toLowerCase(); for (const word of contextWords) { if (descLower.includes(word)) { score += 1; if (!keywordMatches.includes(word)) { keywordMatches.push(word); } } } // 内容匹配 (权重最低但仍然重要) const contentLower = prompt.content.toLowerCase(); for (const word of contextWords) { if (contentLower.includes(word)) { score += 0.5; } } // 考虑使用频率因素 (如果有使用统计) if (prompt.usageCount && prompt.usageCount > 0) { // 使用过的提示词得到轻微加分 score += Math.min(prompt.usageCount / 10, 1); // 最多加1分 } // 如果有最近使用时间,给最近使用的提示词一些优先级 if (prompt.lastUsed) { const lastUsedDate = new Date(prompt.lastUsed); const now = new Date(); const daysDiff = (now.getTime() - lastUsedDate.getTime()) / (1000 * 3600 * 24); if (daysDiff < 7) { // 一周内使用过 score += Math.max(0, (7 - daysDiff) / 7); // 最近使用的得分更高 } } return { prompt, score, keywordMatches }; }); // 按分数排序并限制结果数量 const sortedPrompts = scoredPrompts .filter(item => item.score > 0) .sort((a, b) => b.score - a.score); // 转换为结果格式,包含匹配原因 const results = sortedPrompts.slice(0, params.limit || 5).map(item => { return { ...item.prompt, matchScore: item.score, matchReasons: item.keywordMatches }; }); // 如果有结果,记录使用统计 if (results.length > 0) { // 记录第一个推荐提示词的使用统计(假设它是最相关的) nacosService.trackPromptUsage(results[0].id); } return { content: [{ type: "text", text: JSON.stringify({ prompts: results, totalMatches: sortedPrompts.length }) }] }; }); // 启动服务器 async function main() { try { // 初始化 NacosService await nacosService.init(); // 监听事件 nacosService.on('error', (error) => { console.error('Nacos 错误:', error); }); nacosService.on('configChanged', (config) => { console.error(`提示词配置已更新,当前共有 ${config.length} 个提示词`); }); nacosService.on('usingCache', (config) => { console.error(`使用本地缓存的提示词配置,共有 ${config.length} 个提示词`); }); nacosService.on('connected', () => { console.error('已成功连接到 Nacos 服务器'); }); nacosService.on('disconnected', (error) => { console.error('与 Nacos 服务器的连接已断开:', error.message); }); // 注册工具:记录提示词使用情况 server.tool("trackPromptUsage", "记录提示词使用情况", { promptId: z.string().describe("提示词ID") }, async (params) => { const updatedPrompt = nacosService.trackPromptUsage(params.promptId); return { content: [{ type: "text", text: JSON.stringify({ success: !!updatedPrompt, prompt: updatedPrompt || null }) }] }; }); // 启动 MCP 服务器 const transport = new StdioServerTransport(); await server.connect(transport); console.error("PromptDiscovery MCP 服务已启动"); // console.log(`已注册 ${Object.keys(server['_registeredTools'] || {}).length} 个工具`); } catch (error) { console.error("初始化失败:", error instanceof Error ? error.message : error); process.exit(1); } } // 处理进程退出 process.on('SIGINT', async () => { console.log('关闭服务...'); await nacosService.close(); process.exit(0); }); process.on('SIGTERM', async () => { console.log('关闭服务...'); await nacosService.close(); process.exit(0); }); main().catch((error) => { console.error("Fatal error in main():", error instanceof Error ? error.message : error); process.exit(1); });