Skip to main content
Glama

ToolBox MCP Server

schedule_tool.ts11.7 kB
import fs from 'fs'; import { v4 as uuidv4 } from 'uuid'; import { tasksFilePath } from '../config.js'; import { callToolHandler } from '../handler/ToolHandler.js'; type ScheduledTask = { id: string; toolName: string; toolArgs: Record<string, unknown>; executed: boolean; creationTime: string; timerId?: NodeJS.Timeout; result?: any; lastExecutionTime?: string; } & ( | { type: 'once_absolute'; time: string; // ISO 8601 格式 } | { type: 'once_relative'; delaySeconds: number; } | { type: 'recurring'; interval: string; // every@格式 startTime?: string; // ISO 8601 格式 } ); let scheduledTasks: ScheduledTask[] = []; // 加载定时任务 function loadTasks() { try { const data = fs.readFileSync(tasksFilePath, 'utf8'); try { const parsedTasks = JSON.parse(data); scheduledTasks = parsedTasks.map((task: any) => { if (task.type === 'once_absolute' && !task.time) { return null; } else if (task.type === 'once_relative' && !task.delaySeconds) { return null; } else if (task.type === 'recurring' && !task.interval) { return null; } return task as ScheduledTask; }).filter(task => task !== null) as ScheduledTask[]; } catch (parseError) { scheduledTasks = []; } } catch (error) { scheduledTasks = []; } } // 保存定时任务 function saveTasks() { try { // 排除 timerId 属性,避免循环引用 const tasksToSave = scheduledTasks.map(task => { const { timerId, ...taskWithoutTimerId } = task; return taskWithoutTimerId; }); fs.writeFileSync(tasksFilePath, JSON.stringify(tasksToSave, null, 2), 'utf8'); } catch (error) { } } // 创建定时器 async function createTask(task: ScheduledTask) { scheduledTasks.push(task); saveTasks(); await scheduleTask(task); } // 取消定时器 function cancelTask(id: string) { const taskToCancel = scheduledTasks.find(task => task.id === id); if (taskToCancel && taskToCancel.timerId) { clearTimeout(taskToCancel.timerId); } scheduledTasks = scheduledTasks.filter(task => task.id !== id); saveTasks(); } // 执行定时器 async function executeTask(task: ScheduledTask) { await callToolHandler({ params: { name: task.toolName, arguments: task.toolArgs } }, 'schedule_tool_' + task.id); task.executed = true; task.lastExecutionTime = new Date().toISOString(); saveTasks(); } // 调度定时任务 async function scheduleTask(task: ScheduledTask) { switch (task.type) { case 'once_absolute': const targetTime = new Date(task.time); const delay = targetTime.getTime() - Date.now(); if (delay > 0) { task.timerId = setTimeout(async () => { await executeTask(task); }, delay); } break; case 'once_relative': const delaySeconds = task.delaySeconds; const delayMs = delaySeconds * 1000; task.timerId = setTimeout(async () => { await executeTask(task); }, delayMs); break; case 'recurring': const intervalStr = task.interval; const num = parseInt(intervalStr.substring(6)); const unit = intervalStr.slice(String(num).length + 6); let intervalMs = 0; if (unit === 's') { intervalMs = num * 1000; } else if (unit === 'm') { intervalMs = num * 60 * 1000; } else if (unit === 'h') { intervalMs = num * 60 * 60 * 1000; } else if (unit === 'd') { intervalMs = num * 24 * 60 * 60 * 1000; } if (intervalMs <= 0) { console.error(`Invalid interval for task ${task.id}: ${task.interval}`); return; } const scheduleRecurring = async () => { await executeTask(task); // Schedule next execution task.timerId = setTimeout(scheduleRecurring, intervalMs); }; let initialDelay = 0; if (task.startTime) { const startTime = new Date(task.startTime); initialDelay = startTime.getTime() - Date.now(); if (initialDelay < 0) { initialDelay = 0; } } task.timerId = setTimeout(scheduleRecurring, initialDelay); break; } } // 启动时加载所有定时任务 loadTasks(); scheduledTasks.forEach(task => scheduleTask(task)); export const schema = { name: "schedule_tool", description: "Manage scheduled tasks (create/cancel/list)", type: "object", properties: { action: { type: "string", enum: ["create", "cancel", "list", "cancel_all_once", "cancel_all_recurring"], description: "Action type (create/cancel/list)" }, time: { type: "string", description: "Absolute execution time (YYYY-MM-DD HH:mm:ss)" }, delaySeconds: { type: "number", description: "Delay execution by N seconds" }, interval: { type: "string", description: "Recurring interval pattern (e.g. 'every@5m')" }, toolName: { description: "Tool to execute (e.g. 'time_tool')" }, toolArgs: { type: "object", description: "Parameters for the target tool" }, id: { type: "string", description: "Task ID (required for cancel)" } }, required: ["action"] }; // Destroy function export async function destroy() { console.log("Destroy schedule_tool"); scheduledTasks.forEach(task => { if (task.timerId) { try { clearTimeout(task.timerId); } catch (error) { console.error(`Failed to clear timer for task ${task.id}:`, error); } } }); scheduledTasks = []; } export default async (request: any) => { const action = request.params.arguments?.action; const toolName = request.params.arguments?.toolName; const toolArgs = request.params.arguments?.toolArgs; const id = request.params.arguments?.id; const messages = { taskCreated: (id: string) => `Scheduled task created. ID: ${id}`, taskCanceled: (id: string) => `Task canceled. ID: ${id}`, allOnceTasksCleared: "All one-time tasks cleared", allRecurringCleared: "All recurring tasks cleared", invalidTime: "Scheduled time must be in the future", missingParams: "Missing required parameters: time/delaySeconds/interval", invalidAction: "Invalid action type" }; switch (action) { case "create": let newTask: ScheduledTask; const { time, delaySeconds, interval } = request.params.arguments; if (time && time.startsWith('every@')) { newTask = { id: uuidv4(), toolName: toolName, toolArgs: toolArgs, executed: false, creationTime: new Date().toISOString(), type: 'recurring', interval: time, }; } else if (time) { const targetTime = new Date(time); const timeDiff = targetTime.getTime() - Date.now(); if (timeDiff <= 0) { throw new Error("Scheduled time must be in the future"); } newTask = { id: uuidv4(), toolName: toolName, toolArgs: toolArgs, executed: false, creationTime: new Date().toISOString(), type: 'once_absolute', time: time, }; } else if (delaySeconds) { newTask = { id: uuidv4(), toolName: toolName, toolArgs: toolArgs, executed: false, creationTime: new Date().toISOString(), type: 'once_relative', delaySeconds: delaySeconds, }; } else if (interval) { newTask = { id: uuidv4(), toolName: toolName, toolArgs: toolArgs, executed: false, creationTime: new Date().toISOString(), type: 'recurring', interval: interval, }; } else { throw new Error("Missing required parameters: time/delaySeconds/interval"); } await createTask(newTask); return { content: [ { type: "text", text: JSON.stringify(messages.taskCreated(newTask.id), null, 2), }, ], }; case "cancel": if (!id) { throw new Error("Missing parameter: id"); } cancelTask(id); return { content: [ { type: "text", text: JSON.stringify(messages.taskCanceled(id), null, 2), }, ], }; case "list": const tasksWithoutTimerId = scheduledTasks.map(task => { const { timerId, ...taskWithoutTimerId } = task; return taskWithoutTimerId; }); return { content: [ { type: "text", text: JSON.stringify(tasksWithoutTimerId, null, 2), }, ], }; case "cancel_all_once": scheduledTasks.forEach(task => { if ((task.type === 'once_absolute' || task.type === 'once_relative') && task.timerId) { clearTimeout(task.timerId); } }); scheduledTasks = scheduledTasks.filter(task => task.type !== 'once_absolute' && task.type !== 'once_relative'); saveTasks(); return { content: [ { type: "text", text: JSON.stringify(messages.allOnceTasksCleared, null, 2), }, ], }; case "cancel_all_recurring": scheduledTasks.forEach(task => { if (task.type === 'recurring' && task.timerId) { clearTimeout(task.timerId); } }); scheduledTasks = scheduledTasks.filter(task => task.type !== 'recurring'); saveTasks(); return { content: [ { type: "text", text: JSON.stringify(messages.allRecurringCleared, null, 2), }, ], }; default: throw new Error(messages.invalidAction); } };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/xiaoguomeiyitian/ToolBox'

If you have feedback or need assistance with the MCP directory API, please join our Discord server