todoist_tasks_bulk_complete
Mark multiple Todoist tasks as completed simultaneously using filters like project, priority, due dates, or content search to clear task lists efficiently.
Instructions
Complete multiple tasks at once based on search criteria. Efficiently mark many tasks as done.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| project_id | No | Complete tasks from this project ID (optional) | |
| priority | No | Complete tasks with this priority level 1 (highest) to 4 (lowest) (optional) | |
| due_before | No | Complete tasks due before this date (YYYY-MM-DD) (optional) | |
| due_after | No | Complete tasks due after this date (YYYY-MM-DD) (optional) | |
| content_contains | No | Complete tasks containing this text in content (optional) |
Implementation Reference
- src/handlers/task-handlers.ts:857-913 (handler)The primary handler function that fetches all tasks, filters them based on the provided criteria (project_id, priority, due dates, content_contains), marks matching tasks as complete using Todoist API's closeTask method, and returns a summary of successes and failures.export async function handleBulkCompleteTasks( todoistClient: TodoistApi, args: BulkTaskFilterArgs ): Promise<string> { try { // Clear cache since we're completing taskCache.clear(); validateBulkSearchCriteria(args.search_criteria); const result = await todoistClient.getTasks(); const allTasks = extractArrayFromResponse<TodoistTask>(result); const matchingTasks = filterTasksByCriteria(allTasks, args.search_criteria); if (matchingTasks.length === 0) { return "No tasks found matching the search criteria."; } const completedTasks: string[] = []; const errors: string[] = []; for (const task of matchingTasks) { try { await todoistClient.closeTask(task.id); completedTasks.push(task.content); } catch (error) { errors.push( `Failed to complete task "${task.content}": ${(error as Error).message}` ); } } const successCount = completedTasks.length; const errorCount = errors.length; // Check if we're in dry-run mode const isDryRun = process.env.DRYRUN === "true"; const prefix = isDryRun ? "[DRY-RUN] " : ""; let response = `${prefix}Bulk complete completed: ${successCount} completed, ${errorCount} failed.\n\n`; if (successCount > 0) { response += "Completed tasks:\n"; response += completedTasks.map((content) => `- ${content}`).join("\n"); response += "\n\n"; } if (errorCount > 0) { response += "Errors:\n"; response += errors.join("\n"); } return response.trim(); } catch (error) { ErrorHandler.handleAPIError("bulk complete tasks", error); } }
- src/tools/task-tools.ts:399-434 (schema)Defines the Tool object for todoist_tasks_bulk_complete, including name, description, and detailed inputSchema for filtering criteria.export const BULK_COMPLETE_TASKS_TOOL: Tool = { name: "todoist_tasks_bulk_complete", description: "Complete multiple tasks at once based on search criteria. Efficiently mark many tasks as done.", inputSchema: { type: "object", properties: { project_id: { type: "string", description: "Complete tasks from this project ID (optional)", }, priority: { type: "number", description: "Complete tasks with this priority level 1 (highest) to 4 (lowest) (optional)", enum: [1, 2, 3, 4], }, due_before: { type: "string", description: "Complete tasks due before this date (YYYY-MM-DD) (optional)", }, due_after: { type: "string", description: "Complete tasks due after this date (YYYY-MM-DD) (optional)", }, content_contains: { type: "string", description: "Complete tasks containing this text in content (optional)", }, }, required: [], }, };
- src/index.ts:233-238 (registration)Registers the tool handler in the main CallToolRequestSchema switch statement, validating arguments with isBulkTaskFilterArgs and calling handleBulkCompleteTasks.case "todoist_tasks_bulk_complete": if (!isBulkTaskFilterArgs(args)) { throw new Error("Invalid arguments for todoist_tasks_bulk_complete"); } result = await handleBulkCompleteTasks(apiClient, args); break;
- src/tools/task-tools.ts:436-445 (registration)Includes the BULK_COMPLETE_TASKS_TOOL in the TASK_TOOLS array, which is exported and combined into ALL_TOOLS for server listing.export const TASK_TOOLS = [ CREATE_TASK_TOOL, GET_TASKS_TOOL, UPDATE_TASK_TOOL, DELETE_TASK_TOOL, COMPLETE_TASK_TOOL, BULK_CREATE_TASKS_TOOL, BULK_UPDATE_TASKS_TOOL, BULK_DELETE_TASKS_TOOL, BULK_COMPLETE_TASKS_TOOL,
- Helper function used by bulk task operations to filter tasks based on project, priority, due dates, and content search criteria.function filterTasksByCriteria( tasks: TodoistTask[], criteria: BulkTaskFilterArgs["search_criteria"] ): TodoistTask[] { return tasks.filter((task) => { if (criteria.project_id && task.projectId !== criteria.project_id) return false; const apiPriorityFilter = toApiPriority(criteria.priority); if (apiPriorityFilter !== undefined && task.priority !== apiPriorityFilter) return false; // Fix for issue #34: Handle empty string in content_contains if (criteria.content_contains !== undefined) { // Treat empty or whitespace-only strings as "no match" const searchTerm = criteria.content_contains.trim(); if (searchTerm === "") { // Empty search should match nothing, not everything return false; } if (!task.content.toLowerCase().includes(searchTerm.toLowerCase())) { return false; } } if (criteria.due_before || criteria.due_after) { const taskDate = getDueDateOnly(task.due); if (!taskDate) { return false; } const isBeforeThreshold = !criteria.due_before || taskDate < criteria.due_before; const isAfterThreshold = !criteria.due_after || taskDate > criteria.due_after; if (!isBeforeThreshold || !isAfterThreshold) { return false; } } return true; }); }