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
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');
|
|
}
|