Skip to main content
Glama

batch_add_items

Add multiple tasks or projects to OmniFocus in one operation. Specify item details like name, notes, due date, tags, and more with a structured input for efficient task management.

Instructions

Add multiple tasks or projects to OmniFocus in a single operation

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
itemsYesArray of items (tasks or projects) to add

Implementation Reference

  • src/server.ts:60-65 (registration)
    Registration of the 'batch_add_items' tool, specifying name, description, schema, and handler.
    server.tool( "batch_add_items", "Add multiple tasks or projects to OmniFocus in a single operation", batchAddItemsTool.schema.shape, batchAddItemsTool.handler );
  • Zod schema defining the input parameters for the batch_add_items tool, including items array with task/project properties.
    export const schema = z.object({ items: z.array(z.object({ type: z.enum(['task', 'project']).describe("Type of item to add ('task' or 'project')"), name: z.string().describe("The name of the item"), note: z.string().optional().describe("Additional notes for the item"), dueDate: z.string().optional().describe("The due date in ISO format (YYYY-MM-DD or full ISO date)"), deferDate: z.string().optional().describe("The defer date in ISO format (YYYY-MM-DD or full ISO date)"), flagged: z.boolean().optional().describe("Whether the item is flagged or not"), estimatedMinutes: z.number().optional().describe("Estimated time to complete the item, in minutes"), tags: z.array(z.string()).optional().describe("Tags to assign to the item"), // Task-specific properties projectName: z.string().optional().describe("For tasks: The name of the project to add the task to"), parentTaskId: z.string().optional().describe("For tasks: ID of the parent task"), parentTaskName: z.string().optional().describe("For tasks: Name of the parent task (scoped to project when provided)"), tempId: z.string().optional().describe("For tasks: Temporary ID for within-batch references"), parentTempId: z.string().optional().describe("For tasks: Reference to parent's tempId within the batch"), hierarchyLevel: z.number().int().min(0).optional().describe("Optional ordering hint (0=root, 1=child, ...)"), // Project-specific properties folderName: z.string().optional().describe("For projects: The name of the folder to add the project to"), sequential: z.boolean().optional().describe("For projects: Whether tasks in the project should be sequential") })).describe("Array of items (tasks or projects) to add") , createSequentially: z.boolean().optional().describe("Process parents before children; when false, best-effort order will still try to resolve parents first") });
  • The MCP tool handler for batch_add_items, which calls the primitive batchAddItems function and formats the response with success/failure details.
    export async function handler(args: z.infer<typeof schema>, extra: RequestHandlerExtra) { try { // Call the batchAddItems function const result = await batchAddItems(args.items as BatchAddItemsParams[]); if (result.success) { const successCount = result.results.filter(r => r.success).length; const failureCount = result.results.filter(r => !r.success).length; let message = `✅ Successfully added ${successCount} items.`; if (failureCount > 0) { message += ` ⚠️ Failed to add ${failureCount} items.`; } // Include details about added items const details = result.results.map((item, index) => { if (item.success) { const itemType = args.items[index].type; const itemName = args.items[index].name; return `- ✅ ${itemType}: "${itemName}"`; } else { const itemType = args.items[index].type; const itemName = args.items[index].name; return `- ❌ ${itemType}: "${itemName}" - Error: ${item.error}`; } }).join('\n'); return { content: [{ type: "text" as const, text: `${message}\n\n${details}` }] }; } else { console.error('[batch_add_items] failure result:', JSON.stringify(result)); // Batch operation failed completely or no items succeeded. const failureDetails = (result.results && result.results.length > 0) ? result.results.map((r, index) => { const itemType = args.items[index].type; const itemName = args.items[index].name; return r.success ? `- ✅ ${itemType}: \"${itemName}\"` : `- ❌ ${itemType}: \"${itemName}\" - Error: ${r?.error || 'Unknown error'}`; }).join('\\n') : `No items processed. ${result.error || ''}`; return { content: [{ type: "text" as const, text: `Failed to process batch operation.\\n\\n${failureDetails}` }], isError: true }; } } catch (err: unknown) { const error = err as Error; console.error(`Tool execution error: ${error.message}`); return { content: [{ type: "text" as const, text: `Error processing batch operation: ${error.message}` }], isError: true }; } }
  • Core batchAddItems primitive function implementing the batch logic: cycle detection, topological processing of hierarchical items, calling addProject/addOmniFocusTask primitives.
    export async function batchAddItems(items: BatchAddItemsParams[]): Promise<BatchResult> { try { const results: ItemResult[] = new Array(items.length); const processed: boolean[] = new Array(items.length).fill(false); const tempToRealId = new Map<string, string>(); // Pre-validate cycles in tempId -> parentTempId references const tempIndex = new Map<string, number>(); items.forEach((it, idx) => { if (it.tempId) tempIndex.set(it.tempId, idx); }); // Detect cycles using DFS and capture cycle paths const visiting = new Set<string>(); const visited = new Set<string>(); const inCycle = new Set<string>(); const cycleMessageByTempId = new Map<string, string>(); const stack: string[] = []; function dfs(tempId: string) { if (visited.has(tempId) || inCycle.has(tempId)) return; if (visiting.has(tempId)) return; // already on stack, handled by caller visiting.add(tempId); stack.push(tempId); const idx = tempIndex.get(tempId)!; const parentTemp = items[idx].parentTempId; if (parentTemp && tempIndex.has(parentTemp)) { if (visiting.has(parentTemp)) { // Found a cycle; construct path const startIdx = stack.indexOf(parentTemp); const cycleIds = stack.slice(startIdx).concat(parentTemp); const cycleNames = cycleIds.map(tid => { const i = tempIndex.get(tid)!; return items[i].name || tid; }); const pathText = `${cycleNames.join(' -> ')}`; for (const tid of cycleIds) { inCycle.add(tid); cycleMessageByTempId.set(tid, `Cycle detected: ${pathText}`); } } else { dfs(parentTemp); } } stack.pop(); visiting.delete(tempId); visited.add(tempId); } for (const tid of tempIndex.keys()) dfs(tid); // Mark items that participate in cycles as failed early for (const tid of inCycle) { const idx = tempIndex.get(tid)!; const msg = cycleMessageByTempId.get(tid) || `Cycle detected involving tempId: ${tid}`; results[idx] = { success: false, error: msg }; processed[idx] = true; } // Mark items with unknown parentTempId (and no explicit parentTaskId) as invalid early items.forEach((it, idx) => { if (processed[idx]) return; if (it.parentTempId && !tempIndex.has(it.parentTempId) && !it.parentTaskId) { results[idx] = { success: false, error: `Unknown parentTempId: ${it.parentTempId}` }; processed[idx] = true; } }); // Stable order: sort by hierarchyLevel (undefined -> 0), then original index const indexed = items.map((it, idx) => ({ ...it, __index: idx })); indexed.sort((a, b) => (a.hierarchyLevel ?? 0) - (b.hierarchyLevel ?? 0) || a.__index - b.__index); let madeProgress = true; while (processed.some(p => !p) && madeProgress) { madeProgress = false; for (const item of indexed) { const i = item.__index; if (processed[i]) continue; try { if (item.type === 'project') { const projectParams: AddProjectParams = { name: item.name, note: item.note, dueDate: item.dueDate, deferDate: item.deferDate, flagged: item.flagged, estimatedMinutes: item.estimatedMinutes, tags: item.tags, folderName: item.folderName, sequential: item.sequential }; const projectResult = await addProject(projectParams); results[i] = { success: projectResult.success, id: projectResult.projectId, error: projectResult.error }; processed[i] = true; madeProgress = true; continue; } // task let parentTaskId = item.parentTaskId; if (!parentTaskId && item.parentTempId) { parentTaskId = tempToRealId.get(item.parentTempId); if (!parentTaskId) { // Parent not created yet; skip this round continue; } } const taskParams: AddOmniFocusTaskParams = { name: item.name, note: item.note, dueDate: item.dueDate, deferDate: item.deferDate, flagged: item.flagged, estimatedMinutes: item.estimatedMinutes, tags: item.tags, projectName: item.projectName, parentTaskId, parentTaskName: item.parentTaskName, hierarchyLevel: item.hierarchyLevel }; const taskResult = await addOmniFocusTask(taskParams); results[i] = { success: taskResult.success, id: taskResult.taskId, error: taskResult.error }; if (item.tempId && taskResult.taskId && taskResult.success) { tempToRealId.set(item.tempId, taskResult.taskId); } processed[i] = true; madeProgress = true; } catch (itemError: any) { results[i] = { success: false, error: itemError?.message || 'Unknown error processing item' }; processed[i] = true; // avoid infinite loop on thrown errors madeProgress = true; } } } // Any unprocessed due to dependencies/cycles -> fail with message for (const item of indexed) { const i = item.__index; if (!processed[i]) { const reason = item.parentTempId && !tempToRealId.has(item.parentTempId) ? `Unresolved parentTempId: ${item.parentTempId}` : 'Unresolved dependency or cycle'; results[i] = { success: false, error: reason }; processed[i] = true; } } const overallSuccess = results.some(r => r?.success); return { success: overallSuccess, results }; } catch (error: any) { console.error('Error in batchAddItems:', error); return { success: false, results: [], error: error?.message || 'Unknown error in batchAddItems' }; } }
  • TypeScript type definition for BatchAddItemsParams used by the primitive function, matching the schema structure.
    export type BatchAddItemsParams = { type: 'task' | 'project'; name: string; note?: string; dueDate?: string; deferDate?: string; flagged?: boolean; estimatedMinutes?: number; tags?: string[]; projectName?: string; // For tasks // Hierarchy for tasks parentTaskId?: string; parentTaskName?: string; tempId?: string; parentTempId?: string; hierarchyLevel?: number; folderName?: string; // For projects sequential?: boolean; // For projects };

Other Tools

Related Tools

Latest Blog Posts

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/themotionmachine/OmniFocus-MCP'

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