Skip to main content
Glama

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

TableJSON Schema
NameRequiredDescriptionDefault
project_idNoKanboard project id (overrides .kanboard.yaml).
project_identifierNoKanboard project identifier string (overrides .kanboard.yaml).
tasksYesArray of task creation inputs. 1..100 items. Non-atomic: partial failure possible. Inspect failed[] for per-task errors.

Implementation Reference

  • 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();
  • 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;
        };
      }[];
    }
  • 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;
  • 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;
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided; description fully covers non-atomic behavior, partial failure, fallback defaults, and return structure, leaving no hidden behaviors.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four concise sentences with front-loaded purpose. No redundancy; every sentence provides unique information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, description compensates by detailing return format {created, failed} and error handling. Covers batch size, atomicity, and fallbacks completely.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema covers all 3 parameters with 100% description coverage. Description adds value by noting fallback defaults for specific fields and batch size limits, exceeding baseline.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states bulk-creation of tasks in Kanboard via batch request, distinguishing from single-task 'create_task' sibling. Verb 'Bulk-create' and resource 'tasks' are specific.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Implies use for multiple tasks (1-100 per call), notes partial failure and fallback defaults. Lacks explicit 'when not to use' but sibling create_task provides clear alternative.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other 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/ErnestoCorona/kanboard-mcp'

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