UNPKG

@tosin2013/mcp-shrimp-task-manager

Version:

Enhanced MCP Shrimp Task Manager with comprehensive LLM integration. A task management tool built for AI Agents, emphasizing chain-of-thought, reflection, and style consistency. Features real GPT-4 ↔ MCP tools communication, comprehensive testing pipeline

359 lines 19.4 kB
#!/usr/bin/env node import "dotenv/config"; import { loadPromptFromTemplate } from "./prompts/loader.js"; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { zodToJsonSchema } from "zod-to-json-schema"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import express from "express"; import getPort from "get-port"; import path from "path"; import fs from "fs"; import fsPromises from "fs/promises"; import { fileURLToPath } from "url"; // 導入所有工具函數和 schema import { planTask, planTaskSchema, analyzeTask, analyzeTaskSchema, reflectTask, reflectTaskSchema, splitTasksRaw, splitTasksRawSchema, listTasksSchema, listTasks, executeTask, executeTaskSchema, verifyTask, verifyTaskSchema, deleteTask, deleteTaskSchema, clearAllTasks, clearAllTasksSchema, updateTaskContent, updateTaskContentSchema, queryTask, queryTaskSchema, getTaskDetail, getTaskDetailSchema, processThought, processThoughtSchema, initProjectRules, initProjectRulesSchema, // Idea Honing Tool createSpec, createSpecSchema, interactSpec, interactSpecSchema, ideaHoningTools, researchMode, researchModeSchema, getSpec, getSpecSchema, } from "./tools/index.js"; async function main() { try { const ENABLE_GUI = process.env.ENABLE_GUI === "true"; if (ENABLE_GUI) { // 創建 Express 應用 const app = express(); // 儲存 SSE 客戶端的列表 let sseClients = []; // 發送 SSE 事件的輔助函數 function sendSseUpdate() { sseClients.forEach((client) => { // 檢查客戶端是否仍然連接 if (!client.writableEnded) { client.write(`event: update\ndata: ${JSON.stringify({ timestamp: Date.now(), })}\n\n`); } }); // 清理已斷開的客戶端 (可選,但建議) sseClients = sseClients.filter((client) => !client.writableEnded); } // 設置靜態文件目錄 const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const publicPath = path.join(__dirname, "public"); const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, "data"); const TASKS_FILE_PATH = path.join(DATA_DIR, "tasks.json"); // 提取檔案路徑 app.use(express.static(publicPath)); // 設置 API 路由 app.get("/api/tasks", async (req, res) => { try { // 使用 fsPromises 保持異步讀取 const tasksData = await fsPromises.readFile(TASKS_FILE_PATH, "utf-8"); res.json(JSON.parse(tasksData)); } catch (error) { // 確保檔案不存在時返回空任務列表 if (error.code === "ENOENT") { res.json({ tasks: [] }); } else { res.status(500).json({ error: "Failed to read tasks data" }); } } }); // 新增:SSE 端點 app.get("/api/tasks/stream", (req, res) => { res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive", // 可選: CORS 頭,如果前端和後端不在同一個 origin // "Access-Control-Allow-Origin": "*", }); // 發送一個初始事件或保持連接 res.write("data: connected\n\n"); // 將客戶端添加到列表 sseClients.push(res); // 當客戶端斷開連接時,將其從列表中移除 req.on("close", () => { sseClients = sseClients.filter((client) => client !== res); }); }); // 獲取可用埠 const port = await getPort(); // 啟動 HTTP 伺服器 const httpServer = app.listen(port, () => { // 在伺服器啟動後開始監聽檔案變化 try { // 檢查檔案是否存在,如果不存在則不監聽 (避免 watch 報錯) if (fs.existsSync(TASKS_FILE_PATH)) { fs.watch(TASKS_FILE_PATH, (eventType, filename) => { if (filename && (eventType === "change" || eventType === "rename")) { // 稍微延遲發送,以防短時間內多次觸發 (例如編輯器保存) // debounce sendSseUpdate if needed sendSseUpdate(); } }); } } catch (watchError) { } }); // 將 URL 寫入 WebGUI.md try { // 讀取 TEMPLATES_USE 環境變數並轉換為語言代碼 const templatesUse = process.env.TEMPLATES_USE || "en"; const getLanguageFromTemplate = (template) => { if (template === "zh") return "zh-TW"; if (template === "en") return "en"; // 自訂範本預設使用英文 return "en"; }; const language = getLanguageFromTemplate(templatesUse); const websiteUrl = `[Task Manager UI](http://localhost:${port}?lang=${language})`; const websiteFilePath = path.join(DATA_DIR, "WebGUI.md"); await fsPromises.writeFile(websiteFilePath, websiteUrl, "utf-8"); } catch (error) { } // 設置進程終止事件處理 (確保移除 watcher) const shutdownHandler = async () => { // 關閉所有 SSE 連接 sseClients.forEach((client) => client.end()); sseClients = []; // 關閉 HTTP 伺服器 await new Promise((resolve) => httpServer.close(() => resolve())); process.exit(0); }; process.on("SIGINT", shutdownHandler); process.on("SIGTERM", shutdownHandler); } // 創建MCP服務器 const server = new Server({ name: "Shrimp Task Manager", version: "1.0.0", }, { capabilities: { tools: {}, }, }); server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "plan_task", description: loadPromptFromTemplate("toolsDescription/planTask.md"), inputSchema: zodToJsonSchema(planTaskSchema), }, { name: "analyze_task", description: loadPromptFromTemplate("toolsDescription/analyzeTask.md"), inputSchema: zodToJsonSchema(analyzeTaskSchema), }, { name: "reflect_task", description: loadPromptFromTemplate("toolsDescription/reflectTask.md"), inputSchema: zodToJsonSchema(reflectTaskSchema), }, { name: "split_tasks", description: loadPromptFromTemplate("toolsDescription/splitTasks.md"), inputSchema: zodToJsonSchema(splitTasksRawSchema), }, { name: "list_tasks", description: loadPromptFromTemplate("toolsDescription/listTasks.md"), inputSchema: zodToJsonSchema(listTasksSchema), }, { name: "execute_task", description: loadPromptFromTemplate("toolsDescription/executeTask.md"), inputSchema: zodToJsonSchema(executeTaskSchema), }, { name: "verify_task", description: loadPromptFromTemplate("toolsDescription/verifyTask.md"), inputSchema: zodToJsonSchema(verifyTaskSchema), }, { name: "delete_task", description: loadPromptFromTemplate("toolsDescription/deleteTask.md"), inputSchema: zodToJsonSchema(deleteTaskSchema), }, { name: "clear_all_tasks", description: loadPromptFromTemplate("toolsDescription/clearAllTasks.md"), inputSchema: zodToJsonSchema(clearAllTasksSchema), }, { name: "update_task", description: loadPromptFromTemplate("toolsDescription/updateTask.md"), inputSchema: zodToJsonSchema(updateTaskContentSchema), }, { name: "query_task", description: loadPromptFromTemplate("toolsDescription/queryTask.md"), inputSchema: zodToJsonSchema(queryTaskSchema), }, { name: "get_task_detail", description: loadPromptFromTemplate("toolsDescription/getTaskDetail.md"), inputSchema: zodToJsonSchema(getTaskDetailSchema), }, { name: "process_thought", description: loadPromptFromTemplate("toolsDescription/processThought.md"), inputSchema: zodToJsonSchema(processThoughtSchema), }, { name: "init_project_rules", description: loadPromptFromTemplate("toolsDescription/initProjectRules.md"), inputSchema: zodToJsonSchema(initProjectRulesSchema), }, // Add Idea Honing Tool ...ideaHoningTools, { name: "research_mode", description: loadPromptFromTemplate("toolsDescription/researchMode.md"), inputSchema: zodToJsonSchema(researchModeSchema), }, ], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { try { if (!request.params.arguments) { throw new Error("No arguments provided"); } let parsedArgs; switch (request.params.name) { case "plan_task": parsedArgs = await planTaskSchema.safeParseAsync(request.params.arguments); if (!parsedArgs.success) { throw new Error(`Invalid arguments for tool ${request.params.name}: ${parsedArgs.error.message}`); } return await planTask(parsedArgs.data); case "analyze_task": parsedArgs = await analyzeTaskSchema.safeParseAsync(request.params.arguments); if (!parsedArgs.success) { throw new Error(`Invalid arguments for tool ${request.params.name}: ${parsedArgs.error.message}`); } return await analyzeTask(parsedArgs.data); case "reflect_task": parsedArgs = await reflectTaskSchema.safeParseAsync(request.params.arguments); if (!parsedArgs.success) { throw new Error(`Invalid arguments for tool ${request.params.name}: ${parsedArgs.error.message}`); } return await reflectTask(parsedArgs.data); case "split_tasks": parsedArgs = await splitTasksRawSchema.safeParseAsync(request.params.arguments); if (!parsedArgs.success) { throw new Error(`Invalid arguments for tool ${request.params.name}: ${parsedArgs.error.message}`); } return await splitTasksRaw(parsedArgs.data); case "list_tasks": parsedArgs = await listTasksSchema.safeParseAsync(request.params.arguments); if (!parsedArgs.success) { throw new Error(`Invalid arguments for tool ${request.params.name}: ${parsedArgs.error.message}`); } return await listTasks(parsedArgs.data); case "execute_task": parsedArgs = await executeTaskSchema.safeParseAsync(request.params.arguments); if (!parsedArgs.success) { throw new Error(`Invalid arguments for tool ${request.params.name}: ${parsedArgs.error.message}`); } return await executeTask(parsedArgs.data); case "verify_task": parsedArgs = await verifyTaskSchema.safeParseAsync(request.params.arguments); if (!parsedArgs.success) { throw new Error(`Invalid arguments for tool ${request.params.name}: ${parsedArgs.error.message}`); } return await verifyTask(parsedArgs.data); case "delete_task": parsedArgs = await deleteTaskSchema.safeParseAsync(request.params.arguments); if (!parsedArgs.success) { throw new Error(`Invalid arguments for tool ${request.params.name}: ${parsedArgs.error.message}`); } return await deleteTask(parsedArgs.data); case "clear_all_tasks": parsedArgs = await clearAllTasksSchema.safeParseAsync(request.params.arguments); if (!parsedArgs.success) { throw new Error(`Invalid arguments for tool ${request.params.name}: ${parsedArgs.error.message}`); } return await clearAllTasks(parsedArgs.data); case "update_task": parsedArgs = await updateTaskContentSchema.safeParseAsync(request.params.arguments); if (!parsedArgs.success) { throw new Error(`Invalid arguments for tool ${request.params.name}: ${parsedArgs.error.message}`); } return await updateTaskContent(parsedArgs.data); case "query_task": parsedArgs = await queryTaskSchema.safeParseAsync(request.params.arguments); if (!parsedArgs.success) { throw new Error(`Invalid arguments for tool ${request.params.name}: ${parsedArgs.error.message}`); } return await queryTask(parsedArgs.data); case "get_task_detail": parsedArgs = await getTaskDetailSchema.safeParseAsync(request.params.arguments); if (!parsedArgs.success) { throw new Error(`Invalid arguments for tool ${request.params.name}: ${parsedArgs.error.message}`); } return await getTaskDetail(parsedArgs.data); case "process_thought": parsedArgs = await processThoughtSchema.safeParseAsync(request.params.arguments); if (!parsedArgs.success) { throw new Error(`Invalid arguments for tool ${request.params.name}: ${parsedArgs.error.message}`); } return await processThought(parsedArgs.data); case "init_project_rules": return await initProjectRules(); case "create_spec": parsedArgs = await createSpecSchema.safeParseAsync(request.params.arguments); if (!parsedArgs.success) { throw new Error(`Invalid arguments for tool ${request.params.name}: ${parsedArgs.error.message}`); } return await createSpec(parsedArgs.data); case "research_mode": parsedArgs = await researchModeSchema.safeParseAsync(request.params.arguments); if (!parsedArgs.success) { throw new Error(`Invalid arguments for tool ${request.params.name}: ${parsedArgs.error.message}`); } return await researchMode(parsedArgs.data); case "interact_spec": parsedArgs = await interactSpecSchema.safeParseAsync(request.params.arguments); if (!parsedArgs.success) { throw new Error(`Invalid arguments for tool ${request.params.name}: ${parsedArgs.error.message}`); } return await interactSpec(parsedArgs.data); case "get_spec": parsedArgs = await getSpecSchema.safeParseAsync(request.params.arguments); if (!parsedArgs.success) { throw new Error(`Invalid arguments for tool ${request.params.name}: ${parsedArgs.error.message}`); } return await getSpec(parsedArgs.data); default: throw new Error(`Tool ${request.params.name} does not exist`); } } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); return { content: [ { type: "text", text: `Error occurred: ${errorMsg} \n Please try correcting the error and calling the tool again`, }, ], }; } }); // 建立連接 const transport = new StdioServerTransport(); await server.connect(transport); } catch (error) { process.exit(1); } } main().catch(console.error); //# sourceMappingURL=index.js.map