UNPKG

fofstudio-bbsdata

Version:

fofstudio

377 lines (355 loc) 11.8 kB
#!/usr/bin/env node const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js"); const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js"); const { z } = require("zod"); const { Index } = require('flexsearch'); const { Segment, useDefault } = require('segmentit'); const segmentit = useDefault(new Segment()); const Fuse = require('fuse.js'); function initSelect(data) { const processedData = data.map(item => { const titleWords = segmentit.doSegment(item.名称, { simple: true }); const ID = segmentit.doSegment(item.ID, { simple: true }); const allKeywords = [...titleWords, , ...ID].join(' '); return { ...item, searchableContent: allKeywords }; }); const options = { keys: ['searchableContent'], threshold: 0.3, includeScore: true }; const fuse = new Fuse(processedData, options); return fuse; } function getData() { const fs = require('fs'); const path = require('path'); let data = JSON.parse(fs.readFileSync(path.join(__dirname, 'text.json'), 'utf8')); data = data["数据"] let data_ = []; for (let index = 0; index < data.length; index++) { data_[index] = { "名称": data[index]["帖子名称"], "描述": data[index]["简易描述"], "ID": data[index]["帖子编号"] + "", "类型": 1 + "", } } data = JSON.parse(fs.readFileSync(path.join(__dirname, 'code.json'), 'utf8')); data = data["数据"] for (let index = 0; index < data.length; index++) { data_[data_.length] = { "名称": data[index]["资源名称"], "描述": data[index]["简易描述"], "ID": data[index]["资源编号"] + "", "类型": 2 + "" } } return data_; } let forumData = getData(); const searchIndex = new Index({ tokenize: "forward",// 分词方式: "forward", "reverse", "strict", "full" resolution: 9,// 分辨率,影响索引大小和搜索精度 (1-9, 9最精确) context: true// 启用上下文搜索 (支持短语搜索) }); forumData.forEach((post, index) => { const searchableContent = `${post.名称} ${post.描述} ${post.ID + ""}`; searchIndex.add(index, searchableContent); }); function searchForum(query, limit = 5) { if (!query || typeof query !== 'string' || query.trim() === '') { return { query: query, results: [], took: 0, error: '查询关键词不能为空' }; } const startTime = Date.now(); try { const matchedIndices = searchIndex.search(query.trim()).flat(); const searchResults = matchedIndices .map(idx => forumData[idx]) // 映射回原始对象 .slice(0, limit); // 限制返回结果数量 const took = Date.now() - startTime; return { query: query.trim(), results: searchResults, count: searchResults.length, took: took // 毫秒 }; } catch (error) { console.error('搜索过程中发生错误:', error); return { query: query, results: [], took: 0, error: '搜索执行失败' }; } } /** * 某个类的介绍 */ class MCP服务 { constructor(name = "weather-mcp") { this.server = new McpServer({ name: name, version: "1.0.0" }); } /** * 方法注册一个MCP通用接口 * @param {string} MCP标识 - 注册接口标识,例如:query_weather * @param {string} MCP接口描述 - MCP的接口描述,例如这个接口是干嘛的,AI应该怎么输入使用 * @param {object} 回调信息 - AI触发的回调接口,该回调必须返回数据;并且回调必须是异步函数 * @explain 注册MCP接口("query_xxxx", "一段MCP接口描述", 异步 (AI参数) => {返回 {返回信息: [{ 状态: "text", 返回信息: "" }], 是否错误: false};}) * @value 注册MCP接口("query_xxxx", "一段MCP接口描述", 异步 (AI参数) => {返回 {返回信息: [{ 状态: "text", 返回信息: "" }], 是否错误: false};}) * @returns {MCP接口对象} 返回MCP接口对象 */ 注册MCP接口(MCP标识, AI接口描述, 回调信息) { let data = this.server.tool( MCP标识, { data_: z.string().describe(AI接口描述) }, async ({ data_ }) => { try { let data = await 回调信息({ "状态": "成功", "参数列表": data_ }) data["content"] = data["返回信息"] data["isError"] = data["是否错误"] for (let index = 0; index < data["content"].length; index++) { data["content"][index]["type"] = data["content"][index]["状态"] data["content"][index]["text"] = data["content"][index]["返回信息"] } return data; } catch (error) { let data = await 回调信息({ "状态": "失败", "原因": error }) data["content"] = data["返回信息"] data["isError"] = data["是否错误"] for (let index = 0; index < data["content"].length; index++) { data["content"][index]["type"] = data["content"][index]["状态"] data["content"][index]["text"] = data["content"][index]["返回信息"] } return data; } } ); return new MCP接口对象(data) } /** * 注册监听事件 * @param {object} 回调信息 - 监听到事件后的回调函数,回调拥有一个参数为对象类 * @explain 注册监听事件((事件对象)=>{调试输出(事件对象)}) * @value 注册监听事件((事件对象)=>{调试输出(事件对象)}) */ 注册监听事件(回调信息) { this.server.on('connect', (client) => { 回调信息({ "状态": "客户端已连接", "整体对象": client, "id": client.id }) }); this.server.on('disconnect', (client) => { 回调信息({ "状态": "客户端已断开", "整体对象": client, "id": client.id }) }); this.server.on('request', (client) => { 回调信息({ "状态": "收到任何请求前", "整体对象": client, "id": client.id }) }); this.server.on("response", (response) => { 回调信息({ "状态": "发送相应", "整体对象": response, "id": response.id }) }); } /** * 方法注册一个提示词接口;提示器主要告诉AI应该使用什么方式进行回复【prompt】 * @param {string} 提示器标识 - 提示器的标识,例如:prompt_xxxx * @param {object} 回调信息 - AI触发的回调接口,该回调必须返回数据;并且回调必须是异步函数 * @explain 注册提示词接口("prompt_xxxx", 异步 (提示参数) => {返回 {返回信息: [{ 状态: "text", 返回信息: "请用简洁的语言描述今天的天气状况。" }]};}) * @value 注册提示词接口("prompt_xxxx", 异步 (提示参数) => {返回 {返回信息: [{ 状态: "text", 返回信息: "请用简洁的语言描述今天的天气状况。" }]};}) * @returns {MCP接口对象} 返回MCP接口对象 */ 注册提示词接口(提示器标识, 回调信息) { let data = this.server.prompt(提示器标识, async (data_) => { let data = await 回调信息({ "对象": data_, "标识": 提示器标识 }) data["content"] = data["返回信息"] for (let index = 0; index < data["content"].length; index++) { data["content"][index]["type"] = data["content"][index]["状态"] data["content"][index]["text"] = data["content"][index]["返回信息"] } return data; }); return new MCP接口对象(data) } /** * 方法注册一个补全处理器,允许客户端通过 本接口请求内容生成服务,比如:AI 回答问题、生成代码、写邮件、写文案【completion】 * @param {object} 回调信息 - AI触发的回调接口,该回调必须返回数据;并且回调必须是异步函数 * @explain 注册补全处理器(异步 (AI参数) => {返回 {返回信息: [{ 状态: "text", 返回信息: `AI生成的回答:您输入了 "${AI参数["用户输入"]}"。模型: ${AI参数["模型"] || 'default'}` }]};}) * @value 注册补全处理器(异步 (AI参数) => {返回 {返回信息: [{ 状态: "text", 返回信息: `AI生成的回答:您输入了 "${AI参数["用户输入"]}"。模型: ${AI参数["模型"] || 'default'}` }]};}) * @returns {MCP接口对象} 返回MCP接口对象 */ 注册补全处理器(回调信息) { let data = this.server.completion(async ({ prompt, model }) => { let data = await 回调信息({ "状态": "成功", "用户输入": prompt, "模型": model }) data["content"] = data["返回信息"] for (let index = 0; index < data["content"].length; index++) { data["content"][index]["type"] = data["content"][index]["状态"] data["content"][index]["text"] = data["content"][index]["返回信息"] } return data; }); return new MCP接口对象(data) } /** * 服务端主动向客户端推送数据 * @param {string} 通知事件名 - 例如:weather_update * @param {object} 通知数据 - 通知的数据,可以是任何的数据 * @explain 主动推送数据("", {}) * @value 主动推送数据("", {}) */ 主动推送数据(通知事件名 = "weather_update", 通知数据 = {}) { this.server.notify(通知事件名, 通知数据); } /** * 方法启动MCP服务 * @explain 启动MCP服务() * @value 启动MCP服务() */ async 启动MCP服务() { try { // 使用标准输入/输出作为传输方式 const transport = new StdioServerTransport(); await this.server.connect(transport); } catch (error) { process.exit(1); } } } /** * 某个类的介绍 */ class MCP接口对象 { constructor(obj) { this.obj = obj; } /** * 禁用该对象的接口 * @explain 禁用() * @value 禁用() */ 禁用() { this.obj.disable(); } /** * 恢复该对象的接口 * @explain 恢复() * @value 恢复() */ 恢复() { this.obj.enable(); } /** * 移除注册的接口 * @explain 移除() * @value 移除() */ 移除() { this.obj.remove(); } /** * 返回当前对象的状态,启用还是禁用 * @explain 取状态() * @value 取状态() */ 取状态() { return this.obj.enabled; } } let MCP服务对象 = new MCP服务("我的服务"); MCP服务对象.注册MCP接口("query_bbs", "要查询的论坛数据", async (AI参数) => { let data = []; try { const result3 = searchForum(AI参数["参数列表"], 20); if (result3.count >= 1) { for (let index = 0; index < result3.results.length; index++) { data[data.length] = result3.results[index] if (data.length >= 20) { break; } } for (let index = 0; index < data.length; index++) { if (data[index]["描述"].length >= 1000) { data[index]["描述"] = data[index]["描述"].slice(0, 1000); } } } if (data.length <= 0) { try { let data2 = getData(); let data2_ = []; for (let index = 0; index < data2.length; index++) { data2_[data2_.length] = { "ID": data2[index]["ID"], "名称": data2[index]["名称"] } } const fuse = initSelect(data2_); const results = fuse.search(AI参数["参数列表"]); for (let index = 0; index < results.length; index++) { data[data.length] = { "ID": results[index]["item"]["ID"], "名称": data2[index]["item"]["名称"] } if (data.length >= 20) { break; } } for (let index = 0; index < data.length; index++) { for (let index2 = 0; index2 < data2.length; index2++) { if (data[index]["ID"] == data2[index2]["ID"]) { data[index]["类型"] = data2[index2]["类型"] data[index]["描述"] = data2[index2]["描述"] if (data[index]["描述"].length >= 1000) { data[index]["描述"] = data[index]["描述"].slice(0, 1000); } } } } } catch (error) { } } data = JSON.stringify(data) } catch (err) { console.error(err); data = "[]" } return { 返回信息: [ { 状态: "text", 返回信息: data } ], 是否错误: false }; }); MCP服务对象.启动MCP服务();