McpRPC.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. const API = require('../../../lib/API')
  2. const MCP = require('../../../lib/AIChat/AiAssistantMcp').AI_ASSISTANT_MCP
  3. const sender = {
  4. type: 'string',
  5. description: 'RunForge user uuid. Required. Only data owned by this user may be read or changed.'
  6. }
  7. const pageProps = {
  8. current: { type: 'integer', minimum: 1, description: 'Page number, default 1' },
  9. pagesize: { type: 'integer', minimum: 1, maximum: 20, description: 'Page size, default 10, max 20' }
  10. }
  11. const tools = [
  12. {
  13. name: 'list_lepao_accounts',
  14. description: 'Query the user owned Lepao account list with optional filters. Never returns internal auto-increment ids.',
  15. inputSchema: {
  16. type: 'object',
  17. properties: {
  18. sender,
  19. student_num: { type: 'string' },
  20. name: { type: 'string' },
  21. area: { type: 'string' },
  22. state: { type: 'integer', description: '-1 all, 0 needs login, 1 normal, 2 abnormal' },
  23. auto_run: { type: 'integer', enum: [0, 1] },
  24. ...pageProps
  25. },
  26. required: ['sender']
  27. }
  28. },
  29. {
  30. name: 'list_lepao_records',
  31. description: 'Query Lepao run records owned by the user with account, name, result and time filters.',
  32. inputSchema: {
  33. type: 'object',
  34. properties: {
  35. sender,
  36. lepao_account: { type: 'string' },
  37. name: { type: 'string' },
  38. result: { type: 'string' },
  39. start_time: { type: 'integer', description: 'Start timestamp in ms' },
  40. end_time: { type: 'integer', description: 'End timestamp in ms' },
  41. ...pageProps
  42. },
  43. required: ['sender']
  44. }
  45. },
  46. {
  47. name: 'list_lepao_count_ledger',
  48. description: 'Query Lepao count change ledger for the user. Never returns internal auto-increment ids.',
  49. inputSchema: {
  50. type: 'object',
  51. properties: {
  52. sender,
  53. biz_type: { type: 'string' },
  54. remark: { type: 'string' },
  55. start_time: { type: 'integer', description: 'Start timestamp in ms' },
  56. end_time: { type: 'integer', description: 'End timestamp in ms' },
  57. ...pageProps
  58. },
  59. required: ['sender']
  60. }
  61. },
  62. {
  63. name: 'list_goods',
  64. description: 'Query active goods list with optional keyword and category filters. Never returns internal auto-increment ids, icon paths, or other unnecessary media fields.',
  65. inputSchema: {
  66. type: 'object',
  67. properties: {
  68. keyword: { type: 'string' },
  69. category: { type: 'string' },
  70. ...pageProps
  71. }
  72. }
  73. },
  74. {
  75. name: 'list_my_orders',
  76. description: 'Query the user order list and order status. Uses order_no instead of internal ids.',
  77. inputSchema: {
  78. type: 'object',
  79. properties: {
  80. sender,
  81. state: { type: 'integer', description: '-1 all, 0 pending payment, 1 paid, 2 cancelled, 3 abnormal, 4 refunded' },
  82. order_id: { type: 'string', description: 'Public order number' },
  83. ...pageProps
  84. },
  85. required: ['sender']
  86. }
  87. },
  88. {
  89. name: 'save_lepao_account',
  90. 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.',
  91. inputSchema: {
  92. type: 'object',
  93. properties: {
  94. sender,
  95. student_num: { type: 'string' },
  96. email: { type: 'string' },
  97. area: { type: 'string', description: 'Supported examples: 兰花湖校区跑区, 主校区北跑区, 主校区南跑区, 重庆工商大学茶园校区, 随机分配.' },
  98. auto_time: { type: 'integer', minimum: -1, maximum: 23 },
  99. auto_run: { type: 'integer', enum: [0, 1] },
  100. target_count: { type: 'number' },
  101. auto_day: { type: 'array', items: { type: 'integer', minimum: 0, maximum: 6 } },
  102. notice_type: { type: 'string', enum: ['email', 'none', 'bot'] },
  103. notes: { type: 'string' }
  104. },
  105. required: ['sender', 'student_num']
  106. }
  107. },
  108. {
  109. name: 'save_power_task',
  110. 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.',
  111. inputSchema: {
  112. type: 'object',
  113. properties: {
  114. sender,
  115. task_id: { type: 'integer', description: 'Existing task id for compatibility. Prefer area + building + room when possible.' },
  116. area: { type: 'string' },
  117. building: { type: 'string' },
  118. room: { type: 'string' },
  119. email: { type: 'string' },
  120. lowest: { type: 'number' },
  121. notes: { type: 'string' }
  122. },
  123. required: ['sender']
  124. }
  125. },
  126. {
  127. name: 'list_power_bills',
  128. description: 'Query power tasks or one task bill/change records owned by the user. Results never expose internal task or record ids.',
  129. inputSchema: {
  130. type: 'object',
  131. properties: {
  132. sender,
  133. task_id: { type: 'integer', description: 'Compatibility id filter when caller already has it.' },
  134. area: { type: 'string' },
  135. building: { type: 'string' },
  136. room: { type: 'string' },
  137. ...pageProps
  138. },
  139. required: ['sender']
  140. }
  141. },
  142. {
  143. name: 'query_qxs_book_list',
  144. description: 'Query Quxuanshu textbook list with user provided Quxuanshu credentials.',
  145. inputSchema: {
  146. type: 'object',
  147. properties: {
  148. sender,
  149. username: { type: 'string' },
  150. password: { type: 'string' },
  151. ...pageProps
  152. },
  153. required: ['sender', 'username', 'password']
  154. }
  155. },
  156. {
  157. name: 'submit_work_order',
  158. description: 'Submit a support ticket for the current user. Returns no internal auto-increment id; the user can view progress in the ticket list.',
  159. inputSchema: {
  160. type: 'object',
  161. properties: {
  162. sender,
  163. title: { type: 'string', maxLength: 80 },
  164. content: { type: 'string', maxLength: 4000 },
  165. email: { type: 'string' },
  166. files: { type: 'array', items: { type: 'string' }, maxItems: 6 }
  167. },
  168. required: ['sender', 'title', 'content']
  169. }
  170. }
  171. ]
  172. class McpRpc extends API {
  173. constructor() {
  174. super()
  175. this.noEncrypt()
  176. this.setPath('/aiAssistantMcp')
  177. this.setMethod('POST')
  178. }
  179. async onRequest(req, res) {
  180. const { method, params = {}, id: reqId } = req.body || {}
  181. try {
  182. let result
  183. if (method === 'initialize') {
  184. result = {
  185. protocolVersion: '2024-11-05',
  186. capabilities: { tools: {} },
  187. serverInfo: { name: 'runforgeAiAssistant', version: '1.0' }
  188. }
  189. } else if (method === 'tools/list') {
  190. result = { tools }
  191. } else if (method === 'tools/call') {
  192. const { name, arguments: args = {} } = params
  193. if (!MCP[name] || typeof MCP[name] !== 'function') {
  194. result = { content: [{ type: 'text', text: '未知工具' }] }
  195. } else {
  196. const output = await MCP[name](args)
  197. result = { content: [{ type: 'text', text: output }] }
  198. }
  199. } else {
  200. result = { error: `未知方法: ${method}` }
  201. }
  202. return res.json({ jsonrpc: '2.0', id: reqId ?? null, result })
  203. } catch (err) {
  204. return res.json({
  205. jsonrpc: '2.0',
  206. id: reqId ?? null,
  207. error: { code: -32000, message: err.message }
  208. })
  209. }
  210. }
  211. }
  212. module.exports.McpRpc = McpRpc