const API = require('../../../lib/API') const MCP = require('../../../lib/AIChat/AiAssistantMcp').AI_ASSISTANT_MCP const sender = { type: 'string', description: 'RunForge user uuid. Required. Only data owned by this user may be read or changed.' } const pageProps = { current: { type: 'integer', minimum: 1, description: 'Page number, default 1' }, pagesize: { type: 'integer', minimum: 1, maximum: 20, description: 'Page size, default 10, max 20' } } const tools = [ { name: 'list_lepao_accounts', description: 'Query the user owned Lepao account list with optional filters. Never returns internal auto-increment ids.', inputSchema: { type: 'object', properties: { sender, student_num: { type: 'string' }, name: { type: 'string' }, area: { type: 'string' }, state: { type: 'integer', description: '-1 all, 0 needs login, 1 normal, 2 abnormal' }, auto_run: { type: 'integer', enum: [0, 1] }, ...pageProps }, required: ['sender'] } }, { name: 'list_lepao_records', description: 'Query Lepao run records owned by the user with account, name, result and time filters.', inputSchema: { type: 'object', properties: { sender, lepao_account: { type: 'string' }, name: { type: 'string' }, result: { type: 'string' }, start_time: { type: 'integer', description: 'Start timestamp in ms' }, end_time: { type: 'integer', description: 'End timestamp in ms' }, ...pageProps }, required: ['sender'] } }, { name: 'list_lepao_count_ledger', description: 'Query Lepao count change ledger for the user. Never returns internal auto-increment ids.', inputSchema: { type: 'object', properties: { sender, biz_type: { type: 'string' }, remark: { type: 'string' }, start_time: { type: 'integer', description: 'Start timestamp in ms' }, end_time: { type: 'integer', description: 'End timestamp in ms' }, ...pageProps }, required: ['sender'] } }, { name: 'list_goods', description: 'Query active goods list with optional keyword and category filters. Never returns internal auto-increment ids, icon paths, or other unnecessary media fields.', inputSchema: { type: 'object', properties: { keyword: { type: 'string' }, category: { type: 'string' }, ...pageProps } } }, { name: 'list_my_orders', description: 'Query the user order list and order status. Uses order_no instead of internal ids.', inputSchema: { type: 'object', properties: { sender, state: { type: 'integer', description: '-1 all, 0 pending payment, 1 paid, 2 cancelled, 3 abnormal, 4 refunded' }, order_id: { type: 'string', description: 'Public order number' }, ...pageProps }, required: ['sender'] } }, { name: 'save_lepao_account', description: 'Add or partially update a Lepao account owned by the user. student_num identifies the account; provide only fields that should change. Example: to change run area, provide sender, student_num, area only.', inputSchema: { type: 'object', properties: { sender, student_num: { type: 'string' }, email: { type: 'string' }, area: { type: 'string', description: 'Supported examples: 兰花湖校区跑区, 主校区北跑区, 主校区南跑区, 重庆工商大学茶园校区, 随机分配.' }, auto_time: { type: 'integer', minimum: -1, maximum: 23 }, auto_run: { type: 'integer', enum: [0, 1] }, target_count: { type: 'number' }, auto_day: { type: 'array', items: { type: 'integer', minimum: 0, maximum: 6 } }, notice_type: { type: 'string', enum: ['email', 'none', 'bot'] }, notes: { type: 'string' } }, required: ['sender', 'student_num'] } }, { name: 'save_power_task', description: 'Add or partially update a power query task owned by the user. Existing tasks can be identified by task_id or by area + building + room; provide only fields that should change.', inputSchema: { type: 'object', properties: { sender, task_id: { type: 'integer', description: 'Existing task id for compatibility. Prefer area + building + room when possible.' }, area: { type: 'string' }, building: { type: 'string' }, room: { type: 'string' }, email: { type: 'string' }, lowest: { type: 'number' }, notes: { type: 'string' } }, required: ['sender'] } }, { name: 'list_power_bills', description: 'Query power tasks or one task bill/change records owned by the user. Results never expose internal task or record ids.', inputSchema: { type: 'object', properties: { sender, task_id: { type: 'integer', description: 'Compatibility id filter when caller already has it.' }, area: { type: 'string' }, building: { type: 'string' }, room: { type: 'string' }, ...pageProps }, required: ['sender'] } }, { name: 'query_qxs_book_list', description: 'Query Quxuanshu textbook list with user provided Quxuanshu credentials.', inputSchema: { type: 'object', properties: { sender, username: { type: 'string' }, password: { type: 'string' }, ...pageProps }, required: ['sender', 'username', 'password'] } }, { name: 'submit_work_order', description: 'Submit a support ticket for the current user. Returns no internal auto-increment id; the user can view progress in the ticket list.', inputSchema: { type: 'object', properties: { sender, title: { type: 'string', maxLength: 80 }, content: { type: 'string', maxLength: 4000 }, email: { type: 'string' }, files: { type: 'array', items: { type: 'string' }, maxItems: 6 } }, required: ['sender', 'title', 'content'] } } ] class McpRpc extends API { constructor() { super() this.noEncrypt() this.setPath('/aiAssistantMcp') this.setMethod('POST') } async onRequest(req, res) { const { method, params = {}, id: reqId } = req.body || {} try { let result if (method === 'initialize') { result = { protocolVersion: '2024-11-05', capabilities: { tools: {} }, serverInfo: { name: 'runforgeAiAssistant', version: '1.0' } } } else if (method === 'tools/list') { result = { tools } } else if (method === 'tools/call') { const { name, arguments: args = {} } = params if (!MCP[name] || typeof MCP[name] !== 'function') { result = { content: [{ type: 'text', text: '未知工具' }] } } else { const output = await MCP[name](args) result = { content: [{ type: 'text', text: output }] } } } else { result = { error: `未知方法: ${method}` } } return res.json({ jsonrpc: '2.0', id: reqId ?? null, result }) } catch (err) { return res.json({ jsonrpc: '2.0', id: reqId ?? null, error: { code: -32000, message: err.message } }) } } } module.exports.McpRpc = McpRpc