create_tasks_batch
Bulk-create tasks in a Kanboard project with a single batch request. Supports 1-100 tasks, optional field defaults, and returns per-task success or failure.
Instructions
Bulk-create tasks in a Kanboard project using a single JSON-RPC batch request. Accepts 1–100 tasks per call. Non-atomic: partial failure is possible — check failed[] for per-task errors. Optional fields (column_id, owner_id, category_id, swimlane_id) fall back to .kanboard.yaml defaults when not provided. Returns { created: [...], failed: [...] } — never throws on partial failure.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| project_id | No | Kanboard project id (overrides .kanboard.yaml). | |
| project_identifier | No | Kanboard project identifier string (overrides .kanboard.yaml). | |
| tasks | Yes | Array of task creation inputs. 1..100 items. Non-atomic: partial failure possible. Inspect failed[] for per-task errors. |
Implementation Reference
- src/handler/kanboard.ts:536-631 (handler)The KanboardHandler.createTasksBatch method executes the actual batch logic: builds JSON-RPC 'createTask' calls, invokes them via apiClient.batch(), and parses results into created[]/failed[] envelopes (lines 536-631).
public async createTasksBatch( projectId: number, items: BatchCreateTasksItem[], ): Promise<BatchCreateTasksResult> { if (items.length === 0) { return { created: [], failed: [] }; } if (items.length > BATCH_TASK_CAP) { throw new ValidationError( "createTasksBatch", `createTasksBatch: items count ${String(items.length)} exceeds cap ${String(BATCH_TASK_CAP)}`, ); } // Build batch calls — use index as the id for alignment. // Map id → original input index (index IS the id here, but we build // the map explicitly for clarity and future-proofing). const idToIndex = new Map<number, number>(); const calls: BatchCall[] = items.map((item, index) => { idToIndex.set(index, index); return { method: "createTask", params: { project_id: projectId, ...item }, id: index, }; }); this.#logger.debug({ method: "createTasksBatch", count: items.length }, "batch starting"); const results = await this.#apiClient.batch(calls); const created: BatchCreateTasksResult["created"] = []; const failed: BatchCreateTasksResult["failed"] = []; for (const batchResult of results) { const originalIndex = idToIndex.get(batchResult.index) ?? batchResult.index; const item = items[originalIndex]; const title = item?.title ?? ""; if (batchResult.ok) { const raw = batchResult.result; // Result can be an integer task_id or false (Kanboard returns false in // the result field for some failure cases even in batch mode) if (raw === false) { failed.push({ index: originalIndex, title, error: { code: "API_ERROR", message: "createTask returned false (pre-validate inputs)", }, }); } else if (typeof raw === "number" && raw > 0) { created.push({ index: originalIndex, task_id: raw, title }); } else if (typeof raw === "string") { const n = Number(raw); if (!isNaN(n) && n > 0) { created.push({ index: originalIndex, task_id: n, title }); } else { failed.push({ index: originalIndex, title, error: { code: "API_ERROR", message: `Unexpected result: ${raw}` }, }); } } else { failed.push({ index: originalIndex, title, error: { code: "API_ERROR", message: `Unexpected result type: ${typeof raw}` }, }); } } else { const err = batchResult.error; failed.push({ index: originalIndex, title, error: { code: err.code < 0 ? "RPC_ERROR" : "API_ERROR", message: err.message, }, }); } } // Sort by original index for deterministic output created.sort((a, b) => a.index - b.index); failed.sort((a, b) => a.index - b.index); this.#logger.debug( { method: "createTasksBatch", createdCount: created.length, failedCount: failed.length }, "batch complete", ); return { created, failed }; - Zod input schemas: BatchTaskItemSchema (per-task fields) and CreateTasksBatchInput (project_id, project_identifier, tasks array 1..BATCH_TASK_CAP).
const BatchTaskItemSchema = z .object({ title: z.string().min(1).describe("Task title (required, non-empty)."), description: z.string().optional().describe("Task description (optional)."), column_id: z .number() .int() .positive() .optional() .describe("Column id. Falls back to .kanboard.yaml default_column_id."), owner_id: z .number() .int() .positive() .optional() .describe("Owner user id. Falls back to .kanboard.yaml default_owner_id."), color_id: z.string().optional().describe("Color identifier (e.g. 'blue', 'red')."), date_due: z.union([z.string(), z.number().int(), z.null()]).optional().describe("Due date as ISO 8601 string, Unix epoch seconds (integer), or null to clear."), category_id: z .number() .int() .positive() .optional() .describe("Category id. Falls back to .kanboard.yaml default_category_id."), swimlane_id: z .number() .int() .positive() .optional() .describe("Swimlane id. Falls back to .kanboard.yaml default_swimlane_id."), priority: z.number().optional().describe("Task priority."), creator_id: z .number() .int() .positive() .optional() .describe("Creator user id."), score: z.number().int().optional().describe("Task complexity score."), date_started: z .union([z.string(), z.number().int(), z.null()]) .optional() .describe( "Start date as ISO 8601 string, Unix epoch seconds (integer), or null to clear.", ), tags: z.array(z.string()).optional().describe("Array of tag strings."), reference: z.string().optional().describe("External reference (e.g. issue URL)."), }) .strict(); export const CreateTasksBatchInput = z .object({ project_id: z .number() .int() .positive() .optional() .describe("Kanboard project id (overrides .kanboard.yaml)."), project_identifier: z .string() .optional() .describe("Kanboard project identifier string (overrides .kanboard.yaml)."), tasks: z .array(BatchTaskItemSchema) .min(1) .max(BATCH_TASK_CAP) .describe( `Array of task creation inputs. 1..${String(BATCH_TASK_CAP)} items. ` + "Non-atomic: partial failure possible. Inspect failed[] for per-task errors.", ), }) .strict(); - src/shared/types.ts:84-121 (schema)TypeScript interfaces BatchCreateTasksItem and BatchCreateTasksResult defining the input/output shapes for create_tasks_batch.
export interface BatchCreateTasksItem { title: string; description?: string | undefined; column_id?: number | undefined; owner_id?: number | undefined; category_id?: number | undefined; swimlane_id?: number | undefined; color_id?: string | undefined; /** Unix epoch seconds — already converted from ISO 8601 input by the tool layer. */ date_due?: number | undefined; score?: number | undefined; priority?: number | undefined; reference?: string | undefined; tags?: string[] | undefined; /** Unix epoch seconds — already converted from ISO 8601 input by the tool layer. */ date_started?: number | undefined; creator_id?: number | undefined; } /** * Output shape returned by `create_tasks_batch`. * Never throws on partial failure — all outcomes are in created[] or failed[]. */ export interface BatchCreateTasksResult { created: { index: number; task_id: number; title: string; }[]; failed: { index: number; title: string; error: { code: string; message: string; }; }[]; } - src/tools/index.ts:159-197 (registration)createTasksBatchTool is registered in allTools array (line 168) and mounted onto an McpServer via registerTools (lines 215-233). It is imported at line 45 and re-exported at line 87.
export const allTools: readonly ToolDef[] = [ addProjectUserTool, attachFileToTaskTool, createColumnTool, createCommentTool, createProjectTool, createSubtaskTool, createSwimlaneTool, createTaskTool, createTasksBatchTool, deleteColumnTool, deleteCommentTool, deleteProjectTool, deleteSubtaskTool, deleteSwimlaneTool, deleteTaskTool, deleteTaskFileTool, getProjectTool, getTaskTool, listCategoriesTool, listColumnsTool, listMyTasksTool, listOverdueTasksTool, listProjectsTool, listSubtasksTool, listSwimlanesTool, listTasksTool, listProjectUsersTool, moveColumnTool, moveSwimlaneTool, moveTaskPositionTool, removeProjectUserTool, updateColumnTool, updateCommentTool, updateProjectTool, updateSubtaskTool, updateSwimlaneTool, updateTaskTool, ] as const; - src/shared/constants.ts:46-53 (helper)BATCH_TASK_CAP constant = 100, the max number of tasks allowed in a single create_tasks_batch call. Used by both the Zod schema and the handler.
/** * Maximum number of items allowed in a single `create_tasks_batch` call. * * SPEC CONTRACT (OQ-04 resolution): this is 100 per spec FR-14 (1..100). * The design artifact §1f originally had 50 — the spec wins. * If you change this value, the constants.test.ts assertion will fail loudly. */ export const BATCH_TASK_CAP = 100;