fofstudio-bbsdata
Version:
fofstudio
377 lines (355 loc) • 11.8 kB
JavaScript
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服务();