add_tasks
Add multiple tasks to a goal with hierarchical structure in a single transactional operation, ensuring all tasks succeed or none are added.
Instructions
Add multiple tasks to a goal. Tasks can be provided in a hierarchical structure. For tasks that are children of existing tasks, use the parentId field. The operation is transactional: either all tasks in the batch succeed, or the entire operation fails.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| goalId | Yes | ID of the goal to add tasks to (number) | |
| tasks | Yes | An array of task objects to be added. Each task can define nested subtasks. |
Implementation Reference
- src/index.ts:215-281 (handler)Main handler for the 'add_tasks' tool. Parses AddTasksInput, recursively processes hierarchical task inputs (validating existing parentIds, creating new tasks via storage.addTask), builds HierarchicalTaskResponse tree, and returns JSON-formatted content.case 'add_tasks': { const { goalId, tasks: taskInputs } = request.params.arguments as AddTasksInput; const createdTasks: HierarchicalTaskResponse[] = []; // Helper function to recursively process tasks const processTaskInputRecursively = async ( taskInput: TaskInput, currentGoalId: number, parentTaskId: string | null ): Promise<HierarchicalTaskResponse> => { // Validate parentId if it refers to an existing task if (taskInput.parentId !== undefined && taskInput.parentId !== null) { // Use getTasks to check for existing parent, passing the parentId as an array const existingTasks = await storage.getTasks(currentGoalId, [taskInput.parentId]); if (!existingTasks || existingTasks.length === 0) { throw new McpError(ErrorCode.InvalidParams, `Parent task with ID "${taskInput.parentId}" not found for goal ${currentGoalId}.`); } } // Add the current task const newTask = await storage.addTask(currentGoalId, { title: taskInput.title, description: taskInput.description, parentId: parentTaskId, // Use the parentTaskId from recursion, not taskInput.parentId deleted: false, }); const hierarchicalTaskResponse: HierarchicalTaskResponse = { id: newTask.id, goalId: newTask.goalId, title: newTask.title, description: newTask.description, isComplete: newTask.isComplete, deleted: newTask.deleted, }; // Recursively add subtasks if (taskInput.subtasks && taskInput.subtasks.length > 0) { hierarchicalTaskResponse.subtasks = []; for (const subtaskInput of taskInput.subtasks) { const subtaskResult = await processTaskInputRecursively( subtaskInput, currentGoalId, newTask.id // New task's ID becomes the parent for its subtasks ); hierarchicalTaskResponse.subtasks.push(subtaskResult); } } return hierarchicalTaskResponse; }; // Process top-level tasks for (const taskInput of taskInputs) { const createdTask = await processTaskInputRecursively(taskInput, goalId, taskInput.parentId || null); createdTasks.push(createdTask); } return { content: [ { type: 'text', text: JSON.stringify(createdTasks, null, 2), }, ], }; }
- src/storage.ts:151-203 (helper)Core helper function that adds a single task to a goal's plan in LokiDB. Generates dot-notation task ID (e.g., '1.2'), validates parent task exists, tracks next sequence numbers per parent, inserts task record, updates plan timestamp.async addTask( goalId: number, { title, description, parentId }: Omit<Task, 'id' | 'goalId' | 'isComplete' | 'createdAt' | 'updatedAt'> ): Promise<TaskResponse> { const plan = await this.getPlan(goalId); if (!plan) { throw new Error(`No plan found for goal ${goalId}`); } const metadataCollection = this.db.getCollection('metadata'); const metadata = metadataCollection.findOne({}); if (!metadata) { throw new Error('Metadata collection not found or empty.'); } // Ensure nextTaskId for this goal exists if (!metadata.nextTaskId[goalId]) { metadata.nextTaskId[goalId] = { root: 0 }; // Initialize if not present } let effectiveParentId: string | null = parentId; if (parentId !== null) { const existingParent = this.tasks.findOne({ goalId, id: parentId }); if (!existingParent) { throw new McpError(ErrorCode.InvalidParams, `Parent task with ID "${parentId}" not found for goal ${goalId}.`); } } const parentKey = effectiveParentId === null ? 'root' : effectiveParentId; const nextSequence = (metadata.nextTaskId[goalId][parentKey] || 0) + 1; const newTaskId = effectiveParentId === null ? String(nextSequence) : `${effectiveParentId}.${nextSequence}`; metadata.nextTaskId[goalId][parentKey] = nextSequence; metadataCollection.update(metadata); const task: Task = { id: newTaskId, goalId, parentId: effectiveParentId, // Use effectiveParentId here title, description, isComplete: false, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), deleted: false, // Initialize as not deleted }; this.tasks.insert(task as LokiTask); plan.updatedAt = new Date().toISOString(); await this.save(); const { createdAt, updatedAt, parentId: _, $loki, meta, ...taskResponse } = task as LokiTask; return taskResponse; }
- src/index.ts:58-104 (registration)Tool registration in ListToolsRequestHandler, defining 'add_tasks' name, description, and JSON inputSchema with TaskInput definition for hierarchical inputs.{ name: 'add_tasks', description: 'Add multiple tasks to a goal. Tasks can be provided in a hierarchical structure. For tasks that are children of *existing* tasks, use the `parentId` field. The operation is transactional: either all tasks in the batch succeed, or the entire operation fails.', inputSchema: { type: 'object', properties: { goalId: { type: 'number', description: 'ID of the goal to add tasks to (number)', }, tasks: { type: 'array', description: 'An array of task objects to be added. Each task can define nested subtasks.', items: { $ref: '#/definitions/TaskInput' } } }, required: ['goalId', 'tasks'], definitions: { TaskInput: { type: 'object', properties: { title: { type: 'string', description: 'Title of the task (string)' }, description: { type: 'string', description: 'Detailed description of the task (string)' }, parentId: { type: ['string', 'null'], description: 'Optional parent task ID for tasks that are children of *existing* tasks. Do not use for new subtasks defined hierarchically within this batch.' }, subtasks: { type: 'array', description: 'An array of nested subtask objects to be created under this task.', items: { $ref: '#/definitions/TaskInput' } } }, required: ['title', 'description'] } } }
- src/types.ts:42-58 (schema)TypeScript type definitions supporting add_tasks: TaskInput for inputs, AddTasksInput wrapper, HierarchicalTaskResponse for recursive output structures.// New interfaces for add_tasks input and output export interface TaskInput { title: string; description: string; parentId?: string | null; // For linking to existing tasks subtasks?: TaskInput[]; // For hierarchical new tasks } export interface AddTasksInput { goalId: number; tasks: TaskInput[]; } // For recursive output export interface HierarchicalTaskResponse extends TaskResponse { subtasks?: HierarchicalTaskResponse[]; }