bulk_update_categories
Add or remove categories from multiple tasks in one batch operation, reading existing categories first then patching updates, with per-item error handling.
Instructions
Add/remove categories on several tasks in one operation. 2-phase batch: GET to read existing categories, then PATCH with the updated set. Per-item errors, no global fail.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| refs | Yes | ||
| add | No | ||
| remove | No | ||
| verbose | No | If true: returns full JSON. Otherwise: compact text format (default, saves tokens). |
Implementation Reference
- src/graph.ts:901-968 (handler)The core handler function `bulkUpdateCategories` that performs a 2-phase batch: GET existing categories on tasks, then PATCH with the updated set (add/remove). Returns per-item results with error handling.
export async function bulkUpdateCategories( refs: Array<{ listId: string; taskId: string }>, changes: { add?: string[]; remove?: string[] } ): Promise<Array<BatchResultItem<TodoTask>>> { // Phase 1: GET (batch) to fetch current categories const getRequests: BatchRequest[] = refs.map((ref, idx) => ({ id: String(idx), method: "GET", // No $select: rejected by Graph on this endpoint for personal accounts. url: `/me/todo/lists/${enc(ref.listId)}/tasks/${enc(ref.taskId)}`, })); const getResponses = await graphBatch(getRequests); const final: Array<BatchResultItem<TodoTask>> = new Array(refs.length); // Phase 2: build PATCH requests for successful GETs const patchRequests: BatchRequest[] = []; for (const r of getResponses) { const idx = Number(r.id); if (r.status < 200 || r.status >= 300) { final[idx] = { index: idx, status: r.status, ok: false, error: `GET failed: HTTP ${r.status}`, }; continue; } const task = r.body as { categories?: string[] }; const current = new Set(task.categories ?? []); for (const c of changes.add ?? []) current.add(c); for (const c of changes.remove ?? []) current.delete(c); patchRequests.push({ id: String(idx), method: "PATCH", url: `/me/todo/lists/${enc(refs[idx].listId)}/tasks/${enc(refs[idx].taskId)}`, body: { categories: Array.from(current) }, }); } if (patchRequests.length > 0) { const patchResponses = await graphBatch(patchRequests); for (const r of patchResponses) { const idx = Number(r.id); const ok = r.status >= 200 && r.status < 300; let error: string | undefined; if (!ok) { const body = r.body as GraphErrorBody | string | undefined; if (typeof body === "object" && body && "error" in body && body.error) { error = body.error.code ? `${body.error.code}: ${body.error.message ?? "(no message)"}` : body.error.message ?? `HTTP ${r.status}`; } else if (typeof body === "string") { error = body; } else { error = `HTTP ${r.status}`; } } final[idx] = { index: idx, status: r.status, ok, result: ok ? (r.body as TodoTask) : undefined, error, }; } } return final; } - src/index.ts:1201-1210 (registration)Tool call handler case: parses validated args, maps them to the graph.ts function, and formats results using `formatBatchCompact`.
case "bulk_update_categories": { const a = schemas.bulk_update_categories.strict().parse(args); const results = await bulkUpdateCategories( a.refs.map((r) => ({ listId: r.list_id, taskId: r.task_id })), { add: a.add, remove: a.remove } ); return out(results, a.verbose, (rs) => formatBatchCompact(rs, formatTaskCompact) ); } - src/index.ts:332-340 (schema)Zod schema definition for `bulk_update_categories` input: refs array (list_id, task_id), optional add/remove string arrays, and verbose flag.
bulk_update_categories: z.object({ refs: z .array(z.object({ list_id: z.string(), task_id: z.string() })) .min(1) .max(100), add: z.array(z.string()).optional(), remove: z.array(z.string()).optional(), ...verboseField, }), - src/index.ts:914-938 (schema)JSON input schema advertised via ListTools for `bulk_update_categories`, with description of the 2-phase batch operation.
{ name: "bulk_update_categories", description: "Add/remove categories on several tasks in one operation. 2-phase batch: GET to read existing categories, then PATCH with the updated set. Per-item errors, no global fail.", inputSchema: { type: "object", properties: { refs: { type: "array", maxItems: 100, items: { type: "object", properties: { list_id: { type: "string" }, task_id: { type: "string" }, }, required: ["list_id", "task_id"], }, }, add: { type: "array", items: { type: "string" } }, remove: { type: "array", items: { type: "string" } }, ...verboseJsonProp, }, required: ["refs"], }, - src/index.ts:490-492 (helper)Annotation entry marking `bulk_update_categories` as WRITE_UPDATE (idempotent, non-destructive, open-world) with display title.
bulk_update_categories: { ...WRITE_UPDATE, title: "Bulk update task categories" }, export_tasks_ics: { ...READ, title: "Export tasks as iCalendar (.ics)" }, };