Browse Source

✨ feat: 增加MCP服务器

Pchen. 1 month ago
parent
commit
fefe511d25
3 changed files with 305 additions and 0 deletions
  1. 17 0
      apis/MCP/GetMcp.js
  2. 115 0
      apis/MCP/McpRPC.js
  3. 173 0
      lib/Lepao/Mcp.js

+ 17 - 0
apis/MCP/GetMcp.js

@@ -0,0 +1,17 @@
+const API = require("../../lib/API")
+
+class GetMcp extends API {
+    constructor() {
+        super()
+
+        this.noEncrypt()
+        this.setPath('/mcp')
+        this.setMethod('GET')
+    }
+
+    async onRequest(req, res) {
+        res.json({"status": "ok"})
+    }
+}
+
+module.exports.GetMcp = GetMcp

+ 115 - 0
apis/MCP/McpRPC.js

@@ -0,0 +1,115 @@
+const API = require("../../lib/API")
+const MCP = require("../../lib/Lepao/Mcp.js").MCP
+
+class McpRpc extends API {
+    constructor() {
+        super()
+
+        this.noEncrypt()
+        this.setPath('/mcp')
+        this.setMethod('POST')
+    }
+
+    async onRequest(req, res) {
+        const { method, params = {}, id: req_id } = req.body
+
+        try {
+            let result
+
+            if (method === 'initialize') {
+                result = {
+                    protocolVersion: "2026-03-25",
+                    capabilities: { "tools": {} },
+                    serverInfo: { name: "runforge-mcp", version: "1.0" }
+                }
+            } else if (method === 'tools/list') {
+                result = {
+                    "tools": [
+                        {
+                            "name": "bind_account",
+                            "description": "绑定账号",
+                            "inputSchema": {
+                                "type": "object",
+                                "properties": {
+                                    "sender": { "type": "string" },
+                                    "bind_code": { "type": "string" }
+                                },
+                                "required": ["sender", "bind_code"]
+                            }
+                        },
+                        {
+                            "name": "get_account_info",
+                            "description": "获取账号信息",
+                            "inputSchema": {
+                                "type": "object",
+                                "properties": { "sender": { "type": "string" } },
+                                "required": ["sender"]
+                            }
+                        },
+                        {
+                            "name": "unbind_account",
+                            "description": "解绑账号",
+                            "inputSchema": {
+                                "type": "object",
+                                "properties": { "sender": { "type": "string" } },
+                                "required": ["sender"]
+                            }
+                        },
+                        {
+                            "name": "set_notification",
+                            "description": "消息通知设置,参数:bot:智能机器人通知,email:邮件通知,none:关闭通知",
+                            "inputSchema": {
+                                "type": "object",
+                                "properties": {
+                                    "sender": { "type": "string" },
+                                    "type": { "type": "string" }
+                                },
+                                "required": ["sender", "type"]
+                            }
+                        }
+                    ]
+                }
+            } else if (method === 'tools/call') {
+                const { name, arguments: args = {} } = params
+
+                let output
+
+                switch (name) {
+                    case "bind_account":
+                        output = await MCP.bind_account(args)
+                        break
+                    case "get_account_info":
+                        output = await MCP.get_account_info(args)
+                        break
+                    case "unbind_account":
+                        output = await MCP.unbind_account(args)
+                        break
+                    case "set_notification":
+                        output = await MCP.set_notification(args)
+                        break
+                    default:
+                        output = "未知工具"
+                }
+
+                result = { content: [{ type: "text", text: output }] }
+            } else {
+                result = { error: `未知方法: ${method}` }
+            }
+
+            // 返回标准 JSON-RPC 响应
+            return res.json({
+                jsonrpc: "2.0",
+                id: req_id,
+                result
+            })
+        } catch (e) {
+            return res.json({
+                jsonrpc: "2.0",
+                id: req_id,
+                error: { code: -32000, message: e.message }
+            })
+        }
+    }
+}
+
+module.exports.McpRpc = McpRpc

+ 173 - 0
lib/Lepao/Mcp.js

@@ -0,0 +1,173 @@
+const db = require('../../plugin/DataBase/db')
+const Logger = require('../Logger')
+
+class Mcp {
+    constructor() {
+        this.logger = new Logger(path.join(__dirname, '../logs/MCP.log'), 'INFO')
+
+        this.auto_day = [
+            { label: '周一', value: 1 },
+            { label: '周二', value: 2 },
+            { label: '周三', value: 3 },
+            { label: '周四', value: 4 },
+            { label: '周五', value: 5 },
+            { label: '周六', value: 6 },
+            { label: '周日', value: 0 }
+        ]
+
+        this.autoTimeLabel = (record) => {
+            if (record.auto_time === -1) {
+                if (record.today_auto_time)
+                    return `随机-今日${record.today_auto_time}时`
+                return '随机-待分配'
+            }
+            const match = auto_time.find(item => item.value === record.auto_time)
+            return match ? match.label : '-'
+        }
+    }
+
+    async bind_account({ sender, bind_code }) {
+        try {
+            this.logger.info(`MCP接收绑定账号请求:${sender},绑定码:${bind_code}`)
+            let sql = `
+                SELECT 
+                    f.student_num, f.name, f.bot_account, a.name
+                FROM 
+                    lepao_extra f
+                LEFT JOIN 
+                    lepao_account a
+                ON 
+                    f.student_num = a.student_num
+                WHERE 
+                    f.bind_code = ?
+            `
+            const rows = await db.query(sql, [bind_code])
+            if (!rows) return '系统出错,请稍后再试'
+            if (rows.length !== 0) {
+                if (rows[0].bot_account !== sender) return '该账号已被他人绑定,请联系客服解绑'
+                return '该账号您已绑定'
+            }
+            let insertSql = `
+                UPDATE lepao_extra SET bot_account = ? WHERE bind_code = ?
+            `
+            let insertRows = await db.query(insertSql, [sender, bind_code])
+            if (!insertRows || insertRows.affectedRows !== 1)
+                return '系统出错,请稍后再试'
+            return `绑定成功,姓名:${rows[0].name ?? '未更新,请使用乐跑登录器更新账号信息'},学号:${rows[0].student_num}`
+        } catch (error) {
+            this.logger.error(`MCP绑定账号出错:${e}`)
+            return '系统出错,请稍后再试'
+        }
+    }
+
+    async get_account_info({ sender }) {
+        try {
+            this.logger.info(`MCP接收获取账号信息请求:${sender}`)
+            let sql = `
+                SELECT 
+                    l.name,
+                    l.student_num,
+                    l.state,
+                    l.area,
+                    l.auto_time,
+                    l.total_num,
+                    l.term_num,
+                    l.academy_name,
+                    l.sex,
+                    l.grade_id,
+                    l.email,
+                    l.auto_run,
+                    l.today_auto_time,
+                    l.target_count,
+                    l.auto_day,
+                    l.notice_type
+                FROM 
+                    lepao_account l
+                LEFT JOIN 
+                    lepao_extra f 
+                ON
+                    l.student_num = f.student_num
+                WHERE 
+                    f.bot_account = ?
+            `
+            const rows = await db.query(sql, [sender])
+            if (!rows) return '系统出错,请稍后再试'
+            if (rows.length == 0) return '您尚未绑定乐跑账号,请先绑定'
+
+            let data = rows[0]
+            let returnMsg = `
+                姓名:${data.name ?? '未更新,请使用乐跑登录器更新账号信息'}
+                学号:${data.student_num}
+                账号状态:${data.state === 1 ? '正常' : '需使用乐跑登录器更新账号信息'}
+                乐跑跑区:${data.area ?? "随机分配"}
+                自动乐跑状态:${data.state === 1 ? '开启' : '关闭'}
+                `
+            if (data.auto_run === 1) {
+                returnMsg += `自动乐跑时间:${this.autoTimeLabel(data)}`
+                returnMsg += `自动乐跑星期:${data.auto_day.slice().sort((a, b) => {
+                    if (a === 0) return 1; if (b === 0) return -1; return a - b;
+                }).map(day => this.auto_day.find(item => item.value === day)?.label).join(',').replace(/周/g, '')}`
+            }
+            if (data.sex) returnMsg += `性别:${data.sex === 1 ? '男' : '女'}`
+            if (data.email) returnMsg += `邮箱:${data.email}`
+            if (data.grade) returnMsg += `邮箱:${data.grade}`
+            if (data.academy_name) returnMsg += `学院:${data.academy_name}`
+            if (data.grade_id) returnMsg += `年级:${data.grade_id}级`
+            if (data.target_count) returnMsg += `目标乐跑次数:${data.target_count}`
+            if (data.total_num) returnMsg += `累计乐跑次数:${data.total_num}`
+
+            return returnMsg
+        } catch (error) {
+            this.logger.error(`MCP查询账号信息出错:${e}`)
+            return '系统出错,请稍后再试'
+        }
+    }
+
+    async unbind_account({ sender }) {
+        try {
+            this.logger.info(`MCP接收解绑账号请求:${sender}`)
+            let insertSql = `
+                UPDATE lepao_extra SET bot_account = NULL, bot_nmo = NULL WHERE bot_account = ?
+            `
+            let insertRows = await db.query(insertSql, [sender])
+            if (!insertRows || insertRows.affectedRows !== 1)
+                return '系统出错,请稍后再试'
+            return `解绑成功`
+        } catch (error) {
+            this.logger.error(`MCP解绑账号出错:${e}`)
+            return '系统出错,请稍后再试'
+        }
+    }
+
+    async set_notification({ sender, type }) {
+        try {
+            this.logger.info(`MCP接收设置通知请求:${sender},type:${type}`)
+            if(type !== 'email' || type !== 'bot' || type !== 'none') return '通知type不合法,仅支持 email, bot, none三种模式'
+
+            let insertSql = `
+                UPDATE 
+                    lepao_account la
+                JOIN 
+                    lepao_extra le 
+                ON 
+                    la.student_num = le.student_num
+                SET 
+                    la.notice_type = ?
+                WHERE 
+                    le.bot_account = ?
+            `
+            let insertRows = await db.query(insertSql, [type, sender])
+            if (!insertRows || insertRows.affectedRows !== 1)
+                return '系统出错,请稍后再试'
+            return `操作成功`
+        } catch (error) {
+            this.logger.error(`MCP更换通知方式出错:${e}`)
+            return '系统出错,请稍后再试'
+        }
+    }
+    
+
+}
+
+const MCP = new Mcp()
+module.exports.MCP = MCP