ai-query对接新版freesun-agent接口的分支
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

162 lines
4.8 KiB

/**
* Created by liujiangyong on 2025/08/20.
*/
'use strict';
const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
const { StreamableHTTPServerTransport } = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
const { SSEServerTransport } = require("@modelcontextprotocol/sdk/server/sse.js");
const { isInitializeRequest } = require("@modelcontextprotocol/sdk/types.js");
const { randomUUID } = require("node:crypto");
const { z } = require("zod");
module.exports = function (app, router) {
// 创建 MCP server
function createServer() {
const server = new McpServer({
name: "demo-server",
version: "1.0.0",
});
// 注册一个加法工具
server.registerTool(
"add",
{
title: "Addition Tool",
description: "Add two numbers",
inputSchema: { a: z.number(), b: z.number() },
},
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
})
);
// 注册一个获取 AI Query 记录的工具
server.registerTool("getAiQueryRecord",
{
title: "getAiQueryRecord",
description: "获取 AI Query 记录",
inputSchema: {
page: z.number().default(1),
pageSize: z.number().default(10)
}
},
async ({ page, pageSize }) => {
const models = app.fs.dc.models;
const records = await models.AiQueryRecord.findAll({
limit: pageSize,
offset: (page - 1) * pageSize,
order: [['id', 'DESC']],
raw: true,
});
return {
content: [
{
type: "text", text: JSON.stringify({ data: records })
},
],
structuredContent: { data: records }
}
}
);
return server;
}
// 用于存储每个会话的 transport 实例
const transports = { streamable: {}, sse: {} };
//
// ---- Streamable HTTP 端点 ----
//
router.post("/mcp", async (ctx) => {
const sessionId = ctx.get("mcp-session-id");
let transport;
if (sessionId && transports.streamable[sessionId]) {
// 已有会话
transport = transports.streamable[sessionId];
} else if (!sessionId && isInitializeRequest(ctx.request.body)) {
// 新会话
const server = createServer();
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (sid) => {
transports.streamable[sid] = transport;
},
});
transport.onclose = () => {
if (transport.sessionId) delete transports.streamable[transport.sessionId];
};
await server.connect(transport);
} else {
ctx.status = 400;
ctx.body = { error: "Bad request: invalid session" };
return;
}
await transport.handleRequest(ctx.req, ctx.res, ctx.request.body);
ctx.respond = false;
});
router.get("/mcp", async (ctx) => {
const sessionId = ctx.get("mcp-session-id");
const transport = transports.streamable[sessionId];
if (!transport) {
ctx.status = 400;
ctx.body = "Invalid session";
return;
}
await transport.handleRequest(ctx.req, ctx.res);
ctx.respond = false;
});
router.delete("/mcp", async (ctx) => {
const sessionId = ctx.get("mcp-session-id");
const transport = transports.streamable[sessionId];
if (transport) {
await transport.close();
delete transports.streamable[sessionId];
ctx.body = { ok: true };
} else {
ctx.status = 400;
ctx.body = "Invalid session";
}
});
//
// ---- SSE 端点 ----
//
// router.get("/sse", async (ctx) => {
// const server = createServer();
// const transport = new SSEServerTransport("/messages", ctx.res);
// transports.sse[transport.sessionId] = transport;
// ctx.res.on("close", () => {
// delete transports.sse[transport.sessionId];
// });
// await server.connect(transport);
// ctx.respond = false;
// });
// router.post("/messages", async (ctx) => {
// const sessionId = ctx.query.sessionId;
// const transport = transports.sse[sessionId];
// if (transport) {
// await transport.handlePostMessage(ctx.req, ctx.res, ctx.request.body);
// ctx.respond = false;
// } else {
// ctx.status = 400;
// ctx.body = "Invalid session";
// }
// });
// 将路由中间件挂载到应用上
app.use(router.routes()).use(router.allowedMethods());
console.log('✅ MCP server is set up /mcp (Streamable) endpoints');
}