index.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. #!/usr/bin/env node
  2. import { Server } from '@modelcontextprotocol/sdk/server/index.js';
  3. import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
  4. import {
  5. CallToolRequestSchema,
  6. ListToolsRequestSchema,
  7. } from '@modelcontextprotocol/sdk/types.js';
  8. import {
  9. getPendingTasks,
  10. getTaskById,
  11. updateTaskStatus,
  12. createTask,
  13. getAllTasks,
  14. closePool,
  15. } from './database.js';
  16. import { processTask } from './task-processor.js';
  17. import { info, error, setMcpServer } from './logger.js';
  18. /**
  19. * Task Manager MCP Server
  20. *
  21. * 提供任务管理功能,从PostgreSQL数据库读取任务,
  22. * 通过MCP协议与Cursor交互执行代码开发任务。
  23. */
  24. class TaskManagerServer {
  25. constructor() {
  26. this.server = new Server(
  27. {
  28. name: 'task-manager',
  29. version: '1.0.0',
  30. },
  31. {
  32. capabilities: {
  33. tools: {},
  34. logging: {}, // 启用日志通知功能
  35. },
  36. }
  37. );
  38. this.setupHandlers();
  39. this.pollingInterval = null;
  40. this.isProcessing = false;
  41. // 将 Server 实例传递给 logger,以便通过 MCP 协议发送日志
  42. setMcpServer(this.server);
  43. }
  44. setupHandlers() {
  45. // 列出所有可用工具
  46. this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
  47. tools: [
  48. {
  49. name: 'get_pending_tasks',
  50. description: '获取所有待处理的任务列表',
  51. inputSchema: {
  52. type: 'object',
  53. properties: {},
  54. },
  55. },
  56. {
  57. name: 'get_task_by_id',
  58. description: '根据任务ID获取任务详情',
  59. inputSchema: {
  60. type: 'object',
  61. properties: {
  62. task_id: {
  63. type: 'number',
  64. description: '任务ID',
  65. },
  66. },
  67. required: ['task_id'],
  68. },
  69. },
  70. {
  71. name: 'execute_task',
  72. description: '执行单个任务,将任务描述发送给Cursor进行代码开发',
  73. inputSchema: {
  74. type: 'object',
  75. properties: {
  76. task_id: {
  77. type: 'number',
  78. description: '要执行的任务ID',
  79. },
  80. },
  81. required: ['task_id'],
  82. },
  83. },
  84. {
  85. name: 'update_task_status',
  86. description: '更新任务状态',
  87. inputSchema: {
  88. type: 'object',
  89. properties: {
  90. task_id: {
  91. type: 'number',
  92. description: '任务ID',
  93. },
  94. status: {
  95. type: 'string',
  96. enum: ['pending', 'processing', 'completed', 'failed'],
  97. description: '新状态',
  98. },
  99. code_name: {
  100. type: 'string',
  101. description: '生成的代码文件名(可选)',
  102. },
  103. code_path: {
  104. type: 'string',
  105. description: '代码文件路径(可选)',
  106. },
  107. },
  108. required: ['task_id', 'status'],
  109. },
  110. },
  111. {
  112. name: 'process_all_tasks',
  113. description: '批量处理所有待处理的任务',
  114. inputSchema: {
  115. type: 'object',
  116. properties: {
  117. auto_poll: {
  118. type: 'boolean',
  119. description: '是否启用自动轮询(每5分钟检查一次新任务)',
  120. default: false,
  121. },
  122. },
  123. },
  124. },
  125. {
  126. name: 'create_task',
  127. description: '创建新任务',
  128. inputSchema: {
  129. type: 'object',
  130. properties: {
  131. task_name: {
  132. type: 'string',
  133. description: '任务名称',
  134. },
  135. task_description: {
  136. type: 'string',
  137. description: '任务描述(markdown格式)',
  138. },
  139. create_by: {
  140. type: 'string',
  141. description: '创建者',
  142. },
  143. },
  144. required: ['task_name', 'task_description', 'create_by'],
  145. },
  146. },
  147. {
  148. name: 'get_all_tasks',
  149. description: '获取所有任务(用于调试)',
  150. inputSchema: {
  151. type: 'object',
  152. properties: {
  153. limit: {
  154. type: 'number',
  155. description: '返回的任务数量限制',
  156. default: 100,
  157. },
  158. },
  159. },
  160. },
  161. ],
  162. }));
  163. // 处理工具调用
  164. this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
  165. const { name, arguments: args } = request.params;
  166. try {
  167. switch (name) {
  168. case 'get_pending_tasks': {
  169. const tasks = await getPendingTasks();
  170. return {
  171. content: [
  172. {
  173. type: 'text',
  174. text: JSON.stringify(tasks, null, 2),
  175. },
  176. ],
  177. };
  178. }
  179. case 'get_task_by_id': {
  180. const task = await getTaskById(args.task_id);
  181. if (!task) {
  182. return {
  183. content: [
  184. {
  185. type: 'text',
  186. text: `Task with ID ${args.task_id} not found`,
  187. },
  188. ],
  189. isError: true,
  190. };
  191. }
  192. return {
  193. content: [
  194. {
  195. type: 'text',
  196. text: JSON.stringify(task, null, 2),
  197. },
  198. ],
  199. };
  200. }
  201. case 'execute_task': {
  202. info(`[Task Manager] Executing task ${args.task_id}...`);
  203. const task = await getTaskById(args.task_id);
  204. if (!task) {
  205. error(`[Task Manager] Task ${args.task_id} not found`);
  206. return {
  207. content: [
  208. {
  209. type: 'text',
  210. text: `Task with ID ${args.task_id} not found`,
  211. },
  212. ],
  213. isError: true,
  214. };
  215. }
  216. if (task.status !== 'pending') {
  217. error(`[Task Manager] Task ${args.task_id} is not in pending status. Current status: ${task.status}`);
  218. return {
  219. content: [
  220. {
  221. type: 'text',
  222. text: `Task ${args.task_id} is not in pending status. Current status: ${task.status}`,
  223. },
  224. ],
  225. isError: true,
  226. };
  227. }
  228. // 更新状态为处理中
  229. await updateTaskStatus(args.task_id, 'processing');
  230. info(`[Task Manager] Task ${args.task_id} status updated to 'processing'`);
  231. // 处理任务
  232. const result = await processTask(task);
  233. if (result.success) {
  234. info(`[Task Manager] Task ${args.task_id} executed successfully. Code: ${result.code_name || 'N/A'}, Path: ${result.code_path || 'N/A'}`);
  235. } else {
  236. error(`[Task Manager] Task ${args.task_id} execution failed: ${result.message || 'Unknown error'}`);
  237. }
  238. return {
  239. content: [
  240. {
  241. type: 'text',
  242. text: JSON.stringify(result, null, 2),
  243. },
  244. ],
  245. };
  246. }
  247. case 'update_task_status': {
  248. const updated = await updateTaskStatus(
  249. args.task_id,
  250. args.status,
  251. args.code_name || null,
  252. args.code_path || null
  253. );
  254. return {
  255. content: [
  256. {
  257. type: 'text',
  258. text: JSON.stringify(updated, null, 2),
  259. },
  260. ],
  261. };
  262. }
  263. case 'process_all_tasks': {
  264. const result = await this.processAllTasks(args.auto_poll || false);
  265. return {
  266. content: [
  267. {
  268. type: 'text',
  269. text: JSON.stringify(result, null, 2),
  270. },
  271. ],
  272. };
  273. }
  274. case 'create_task': {
  275. const task = await createTask(
  276. args.task_name,
  277. args.task_description,
  278. args.create_by
  279. );
  280. return {
  281. content: [
  282. {
  283. type: 'text',
  284. text: JSON.stringify(task, null, 2),
  285. },
  286. ],
  287. };
  288. }
  289. case 'get_all_tasks': {
  290. const tasks = await getAllTasks(args.limit || 100);
  291. return {
  292. content: [
  293. {
  294. type: 'text',
  295. text: JSON.stringify(tasks, null, 2),
  296. },
  297. ],
  298. };
  299. }
  300. default:
  301. return {
  302. content: [
  303. {
  304. type: 'text',
  305. text: `Unknown tool: ${name}`,
  306. },
  307. ],
  308. isError: true,
  309. };
  310. }
  311. } catch (error) {
  312. return {
  313. content: [
  314. {
  315. type: 'text',
  316. text: `Error executing tool ${name}: ${error.message}\n${error.stack}`,
  317. },
  318. ],
  319. isError: true,
  320. };
  321. }
  322. });
  323. }
  324. /**
  325. * 处理所有待处理的任务
  326. */
  327. async processAllTasks(autoPoll = false) {
  328. if (this.isProcessing) {
  329. info('[Task Manager] Task processing is already in progress, skipping...');
  330. return {
  331. success: false,
  332. message: 'Task processing is already in progress',
  333. };
  334. }
  335. info('[Task Manager] Starting to process all pending tasks...');
  336. const processStartTime = Date.now();
  337. this.isProcessing = true;
  338. const results = {
  339. processed: 0,
  340. succeeded: 0,
  341. failed: 0,
  342. tasks: [],
  343. };
  344. try {
  345. const tasks = await getPendingTasks();
  346. if (tasks.length === 0) {
  347. info('[Task Manager] No pending tasks found, skipping processing.');
  348. return {
  349. success: true,
  350. message: 'No pending tasks to process',
  351. ...results,
  352. };
  353. }
  354. info(`[Task Manager] Processing ${tasks.length} task(s)...`);
  355. for (const task of tasks) {
  356. try {
  357. info(`[Task Manager] Processing task ${task.task_id}: ${task.task_name}`);
  358. // 更新状态为处理中
  359. await updateTaskStatus(task.task_id, 'processing');
  360. // 处理任务
  361. const result = await processTask(task);
  362. results.processed++;
  363. if (result.success) {
  364. results.succeeded++;
  365. await updateTaskStatus(
  366. task.task_id,
  367. 'completed',
  368. result.code_name,
  369. result.code_path
  370. );
  371. info(`[Task Manager] Task ${task.task_id} completed successfully. Code: ${result.code_name || 'N/A'}, Path: ${result.code_path || 'N/A'}`);
  372. } else {
  373. results.failed++;
  374. await updateTaskStatus(task.task_id, 'failed');
  375. error(`[Task Manager] Task ${task.task_id} failed: ${result.message || 'Unknown error'}`);
  376. }
  377. results.tasks.push({
  378. task_id: task.task_id,
  379. task_name: task.task_name,
  380. success: result.success,
  381. message: result.message,
  382. });
  383. } catch (err) {
  384. results.failed++;
  385. await updateTaskStatus(task.task_id, 'failed');
  386. error(`[Task Manager] Task ${task.task_id} failed with error: ${err.message}`);
  387. results.tasks.push({
  388. task_id: task.task_id,
  389. task_name: task.task_name,
  390. success: false,
  391. error: err.message,
  392. });
  393. }
  394. }
  395. const processDuration = Date.now() - processStartTime;
  396. info(`[Task Manager] Task processing completed. Processed: ${results.processed}, Succeeded: ${results.succeeded}, Failed: ${results.failed}, Duration: ${processDuration}ms`);
  397. // 如果启用自动轮询,启动轮询机制
  398. if (autoPoll && !this.pollingInterval) {
  399. this.startPolling();
  400. }
  401. return {
  402. success: true,
  403. message: `Processed ${results.processed} tasks. Succeeded: ${results.succeeded}, Failed: ${results.failed}`,
  404. ...results,
  405. };
  406. } finally {
  407. this.isProcessing = false;
  408. }
  409. }
  410. /**
  411. * 启动轮询机制
  412. */
  413. startPolling() {
  414. const pollInterval = parseInt(process.env.POLL_INTERVAL || '300000', 10); // 默认5分钟
  415. if (this.pollingInterval) {
  416. clearInterval(this.pollingInterval);
  417. }
  418. this.pollingInterval = setInterval(async () => {
  419. if (!this.isProcessing) {
  420. try {
  421. const tasks = await getPendingTasks();
  422. if (tasks.length > 0) {
  423. info(`Found ${tasks.length} new pending tasks, processing...`);
  424. await this.processAllTasks(false);
  425. }
  426. } catch (err) {
  427. error('Error during polling: ' + err.message);
  428. }
  429. }
  430. }, pollInterval);
  431. info(`Polling started with interval: ${pollInterval}ms (${pollInterval / 1000}s)`);
  432. }
  433. /**
  434. * 停止轮询机制
  435. */
  436. stopPolling() {
  437. if (this.pollingInterval) {
  438. clearInterval(this.pollingInterval);
  439. this.pollingInterval = null;
  440. info('Polling stopped');
  441. }
  442. }
  443. /**
  444. * 启动服务器
  445. */
  446. async start() {
  447. const transport = new StdioServerTransport();
  448. await this.server.connect(transport);
  449. info('Task Manager MCP Server started');
  450. // 自动启动轮询机制(如果环境变量 AUTO_START_POLLING 为 true 或未设置)
  451. const autoStartPolling = process.env.AUTO_START_POLLING !== 'false';
  452. if (autoStartPolling) {
  453. // 延迟启动,等待数据库连接就绪
  454. setTimeout(() => {
  455. this.startPolling();
  456. // 立即检查一次待处理任务
  457. this.processAllTasks(false).catch((err) => {
  458. error('Error during initial task processing: ' + err.message);
  459. });
  460. }, 2000); // 2秒后启动
  461. }
  462. }
  463. /**
  464. * 关闭服务器
  465. */
  466. async close() {
  467. this.stopPolling();
  468. await closePool();
  469. // MCP SDK会自动处理连接关闭
  470. }
  471. }
  472. // 启动服务器
  473. const server = new TaskManagerServer();
  474. // 处理进程退出
  475. process.on('SIGINT', async () => {
  476. await server.close();
  477. process.exit(0);
  478. });
  479. process.on('SIGTERM', async () => {
  480. await server.close();
  481. process.exit(0);
  482. });
  483. server.start().catch((err) => {
  484. error('Failed to start server: ' + err.message);
  485. process.exit(1);
  486. });