@taazkareem/clickup-mcp-server 0.4.70 → 0.4.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,116 +1,147 @@
1
- export class BulkProcessor {
2
- async processBulk(items, processor, options) {
3
- const opts = {
4
- batchSize: options?.batchSize ?? 10,
5
- concurrency: options?.concurrency ?? 3,
6
- continueOnError: options?.continueOnError ?? false,
7
- retryCount: options?.retryCount ?? 3,
8
- retryDelay: options?.retryDelay ?? 1000,
9
- exponentialBackoff: options?.exponentialBackoff ?? true,
10
- onProgress: options?.onProgress ?? (() => { })
11
- };
12
- const result = {
13
- success: true,
14
- successfulItems: [],
15
- failedItems: [],
16
- totalItems: items.length,
17
- successCount: 0,
18
- failureCount: 0
19
- };
20
- if (items.length === 0)
21
- return result;
1
+ /**
2
+ * ClickUp Bulk Service
3
+ *
4
+ * Enhanced implementation for bulk operations that leverages the existing single-operation methods.
5
+ * This approach reduces code duplication while offering powerful concurrency management.
6
+ */
7
+ import { ClickUpServiceError, ErrorCode } from './base.js';
8
+ import { processBatch } from '../../utils/concurrency-utils.js';
9
+ import { Logger } from '../../logger.js';
10
+ // Create logger instance
11
+ const logger = new Logger('BulkService');
12
+ /**
13
+ * Service for handling bulk operations in ClickUp
14
+ */
15
+ export class BulkService {
16
+ constructor(taskService) {
17
+ this.taskService = taskService;
18
+ logger.info('BulkService initialized');
19
+ }
20
+ /**
21
+ * Create multiple tasks in a list efficiently
22
+ *
23
+ * @param listId ID of the list to create tasks in
24
+ * @param tasks Array of task data
25
+ * @param options Batch processing options
26
+ * @returns Results containing successful and failed tasks
27
+ */
28
+ async createTasks(listId, tasks, options) {
29
+ logger.info(`Creating ${tasks.length} tasks in list ${listId}`, {
30
+ batchSize: options?.batchSize,
31
+ concurrency: options?.concurrency
32
+ });
22
33
  try {
23
- const totalBatches = Math.ceil(items.length / opts.batchSize);
24
- let processedItems = 0;
25
- for (let batchIndex = 0; batchIndex < totalBatches; batchIndex++) {
26
- const startIdx = batchIndex * opts.batchSize;
27
- const endIdx = Math.min(startIdx + opts.batchSize, items.length);
28
- const batch = items.slice(startIdx, endIdx);
29
- const batchResults = await this.processBatch(batch, processor, startIdx, opts);
30
- result.successfulItems.push(...batchResults.successfulItems);
31
- result.failedItems.push(...batchResults.failedItems);
32
- result.successCount += batchResults.successCount;
33
- result.failureCount += batchResults.failureCount;
34
- if (batchResults.failureCount > 0 && !opts.continueOnError) {
35
- result.success = false;
36
- return result;
37
- }
38
- processedItems += batch.length;
39
- opts.onProgress(processedItems, items.length, result.successCount, result.failureCount);
40
- }
41
- result.success = result.failedItems.length === 0;
42
- return result;
34
+ // First validate that the list exists - do this once for all tasks
35
+ await this.taskService.validateListExists(listId);
36
+ // Process the tasks in batches
37
+ return await processBatch(tasks, (task, index) => {
38
+ logger.debug(`Creating task ${index + 1}/${tasks.length}`, {
39
+ taskName: task.name
40
+ });
41
+ // Reuse the single-task creation method
42
+ return this.taskService.createTask(listId, task);
43
+ }, options);
43
44
  }
44
45
  catch (error) {
45
- const err = error;
46
- console.error('Failed to process bulk operation:', err.message || String(error));
47
- result.success = false;
48
- return result;
46
+ logger.error(`Failed to create tasks in bulk`, {
47
+ listId,
48
+ taskCount: tasks.length,
49
+ error: error instanceof Error ? error.message : String(error)
50
+ });
51
+ throw new ClickUpServiceError(`Failed to create tasks in bulk: ${error instanceof Error ? error.message : String(error)}`, error instanceof ClickUpServiceError ? error.code : ErrorCode.UNKNOWN, { listId, taskCount: tasks.length });
49
52
  }
50
53
  }
51
- async processBatch(batch, processor, startIndex, opts) {
52
- const result = {
53
- success: true,
54
- successfulItems: [],
55
- failedItems: [],
56
- totalItems: batch.length,
57
- successCount: 0,
58
- failureCount: 0
59
- };
54
+ /**
55
+ * Update multiple tasks efficiently
56
+ *
57
+ * @param tasks Array of task IDs and update data
58
+ * @param options Batch processing options
59
+ * @returns Results containing successful and failed task updates
60
+ */
61
+ async updateTasks(tasks, options) {
62
+ logger.info(`Updating ${tasks.length} tasks`, {
63
+ batchSize: options?.batchSize,
64
+ concurrency: options?.concurrency
65
+ });
60
66
  try {
61
- for (let i = 0; i < batch.length; i += opts.concurrency) {
62
- const concurrentBatch = batch.slice(i, Math.min(i + opts.concurrency, batch.length));
63
- const promises = concurrentBatch.map((item, idx) => {
64
- const index = startIndex + i + idx;
65
- return this.processWithRetry(() => processor(item, index), index, item, opts);
67
+ return await processBatch(tasks, ({ id, data }, index) => {
68
+ logger.debug(`Updating task ${index + 1}/${tasks.length}`, {
69
+ taskId: id
66
70
  });
67
- const results = await Promise.allSettled(promises);
68
- results.forEach((promiseResult, idx) => {
69
- const index = startIndex + i + idx;
70
- if (promiseResult.status === 'fulfilled') {
71
- result.successfulItems.push(promiseResult.value);
72
- result.successCount++;
73
- }
74
- else {
75
- const error = promiseResult.reason;
76
- result.failedItems.push({ item: batch[i + idx], index, error });
77
- result.failureCount++;
78
- if (!opts.continueOnError) {
79
- result.success = false;
80
- throw new Error(`Bulk operation failed at index ${index}: ${error.message || String(error)}`);
81
- }
82
- }
71
+ // Reuse the single-task update method
72
+ return this.taskService.updateTask(id, data);
73
+ }, options);
74
+ }
75
+ catch (error) {
76
+ logger.error(`Failed to update tasks in bulk`, {
77
+ taskCount: tasks.length,
78
+ error: error instanceof Error ? error.message : String(error)
79
+ });
80
+ throw new ClickUpServiceError(`Failed to update tasks in bulk: ${error instanceof Error ? error.message : String(error)}`, error instanceof ClickUpServiceError ? error.code : ErrorCode.UNKNOWN, { taskCount: tasks.length });
81
+ }
82
+ }
83
+ /**
84
+ * Move multiple tasks to a different list efficiently
85
+ *
86
+ * @param taskIds Array of task IDs to move
87
+ * @param targetListId ID of the list to move tasks to
88
+ * @param options Batch processing options
89
+ * @returns Results containing successful and failed moves
90
+ */
91
+ async moveTasks(taskIds, targetListId, options) {
92
+ logger.info(`Moving ${taskIds.length} tasks to list ${targetListId}`, {
93
+ batchSize: options?.batchSize,
94
+ concurrency: options?.concurrency
95
+ });
96
+ try {
97
+ // First validate that the target list exists - do this once for all tasks
98
+ await this.taskService.validateListExists(targetListId);
99
+ return await processBatch(taskIds, (taskId, index) => {
100
+ logger.debug(`Moving task ${index + 1}/${taskIds.length}`, {
101
+ taskId,
102
+ targetListId
83
103
  });
84
- }
85
- return result;
104
+ // Reuse the single-task move method
105
+ return this.taskService.moveTask(taskId, targetListId);
106
+ }, options);
86
107
  }
87
108
  catch (error) {
88
- const err = error;
89
- console.error(`Bulk operation failed: ${err.message || String(error)}`, error);
90
- result.success = false;
91
- return result;
109
+ logger.error(`Failed to move tasks in bulk`, {
110
+ targetListId,
111
+ taskCount: taskIds.length,
112
+ error: error instanceof Error ? error.message : String(error)
113
+ });
114
+ throw new ClickUpServiceError(`Failed to move tasks in bulk: ${error instanceof Error ? error.message : String(error)}`, error instanceof ClickUpServiceError ? error.code : ErrorCode.UNKNOWN, { targetListId, taskCount: taskIds.length });
92
115
  }
93
116
  }
94
- async processWithRetry(operation, index, item, options) {
95
- let attempts = 1;
96
- let lastError = new Error('Unknown error');
97
- while (attempts <= options.retryCount) {
98
- try {
99
- return await operation();
100
- }
101
- catch (error) {
102
- const err = error;
103
- console.warn(`Operation failed for item at index ${index}, attempt ${attempts}/${options.retryCount}: ${err.message || String(error)}`);
104
- lastError = err;
105
- if (attempts >= options.retryCount)
106
- break;
107
- const delay = options.exponentialBackoff
108
- ? options.retryDelay * Math.pow(2, attempts) + Math.random() * 1000
109
- : options.retryDelay * Math.pow(1.5, attempts - 1);
110
- await new Promise(resolve => setTimeout(resolve, delay));
111
- attempts++;
112
- }
117
+ /**
118
+ * Delete multiple tasks efficiently
119
+ *
120
+ * @param taskIds Array of task IDs to delete
121
+ * @param options Batch processing options
122
+ * @returns Results containing successful and failed deletions
123
+ */
124
+ async deleteTasks(taskIds, options) {
125
+ logger.info(`Deleting ${taskIds.length} tasks`, {
126
+ batchSize: options?.batchSize,
127
+ concurrency: options?.concurrency
128
+ });
129
+ try {
130
+ return await processBatch(taskIds, async (taskId, index) => {
131
+ logger.debug(`Deleting task ${index + 1}/${taskIds.length}`, {
132
+ taskId
133
+ });
134
+ // Reuse the single-task delete method
135
+ await this.taskService.deleteTask(taskId);
136
+ return taskId; // Return the ID for successful deletions
137
+ }, options);
138
+ }
139
+ catch (error) {
140
+ logger.error(`Failed to delete tasks in bulk`, {
141
+ taskCount: taskIds.length,
142
+ error: error instanceof Error ? error.message : String(error)
143
+ });
144
+ throw new ClickUpServiceError(`Failed to delete tasks in bulk: ${error instanceof Error ? error.message : String(error)}`, error instanceof ClickUpServiceError ? error.code : ErrorCode.UNKNOWN, { taskCount: taskIds.length });
113
145
  }
114
- throw new Error(`Operation failed after ${attempts} attempts for item at index ${index}: ${lastError?.message || 'Unknown error'}`);
115
146
  }
116
147
  }
@@ -9,15 +9,19 @@
9
9
  * - Finding tasks by name
10
10
  */
11
11
  import { BaseClickUpService, ErrorCode, ClickUpServiceError } from './base.js';
12
- import { BulkProcessor } from './bulk.js';
13
12
  import { ListService } from './list.js';
14
13
  export class TaskService extends BaseClickUpService {
15
14
  constructor(apiKey, teamId, baseUrl, workspaceService) {
16
15
  super(apiKey, teamId, baseUrl);
17
16
  this.workspaceService = null;
18
- this.bulkProcessor = new BulkProcessor();
19
- this.listService = new ListService(apiKey, teamId);
20
- this.workspaceService = workspaceService || null;
17
+ // Cache workspace service if provided
18
+ if (workspaceService) {
19
+ this.workspaceService = workspaceService;
20
+ this.logOperation('constructor', { usingSharedWorkspaceService: true });
21
+ }
22
+ // Initialize list service for list lookups
23
+ this.listService = new ListService(apiKey, teamId, baseUrl, this.workspaceService);
24
+ this.logOperation('constructor', { initialized: true });
21
25
  }
22
26
  /**
23
27
  * Helper method to handle errors consistently
@@ -263,26 +267,14 @@ export class TaskService extends BaseClickUpService {
263
267
  async duplicateTask(taskId, listId) {
264
268
  this.logOperation('duplicateTask', { taskId, listId });
265
269
  try {
266
- // First, get the original task
270
+ // Get the original task to duplicate
267
271
  const originalTask = await this.getTask(taskId);
268
- // Priority mapping: convert string priority to numeric value if needed
269
- let priorityValue = null;
270
- if (originalTask.priority) {
271
- // If priority.id exists and is numeric, use that
272
- if (originalTask.priority.id) {
273
- priorityValue = parseInt(originalTask.priority.id);
274
- // Ensure it's in the valid range 1-4
275
- if (isNaN(priorityValue) || priorityValue < 1 || priorityValue > 4) {
276
- priorityValue = null;
277
- }
278
- }
279
- }
280
- // Prepare data for the new task
272
+ // Create a copy of the task data
281
273
  const newTaskData = {
282
- name: `${originalTask.name} (Copy)`,
283
- description: originalTask.description,
274
+ name: `${originalTask.name} (copy)`,
275
+ description: originalTask.description || '',
284
276
  status: originalTask.status?.status,
285
- priority: priorityValue,
277
+ priority: originalTask.priority?.id ? parseInt(originalTask.priority.id) : null,
286
278
  due_date: originalTask.due_date ? new Date(originalTask.due_date).getTime() : undefined,
287
279
  assignees: originalTask.assignees?.map(a => a.id) || []
288
280
  };
@@ -295,242 +287,51 @@ export class TaskService extends BaseClickUpService {
295
287
  }
296
288
  }
297
289
  /**
298
- * Create multiple tasks in a list with advanced batching options
299
- *
300
- * @param listId The ID of the list to create tasks in
301
- * @param data Object containing array of tasks to create
302
- * @param options Configuration options for the bulk operation
303
- * @param progressCallback Optional callback for tracking progress
304
- * @returns Result containing both successful and failed operations
305
- */
306
- async createBulkTasks(listId, data, options, progressCallback) {
307
- this.logOperation('createBulkTasks', {
308
- listId,
309
- taskCount: data.tasks.length,
310
- batchSize: options?.batchSize,
311
- concurrency: options?.concurrency
312
- });
313
- try {
314
- // Validate list exists before proceeding
315
- const list = await this.listService.getList(listId).catch(() => null);
316
- if (!list) {
317
- throw new ClickUpServiceError(`List not found with ID: ${listId}`, ErrorCode.NOT_FOUND);
318
- }
319
- // Set default options for better performance
320
- const bulkOptions = {
321
- batchSize: options?.batchSize ?? 5, // Smaller batch size for better rate limit handling
322
- concurrency: options?.concurrency ?? 2, // Lower concurrency to avoid rate limits
323
- continueOnError: options?.continueOnError ?? true, // Continue on individual task failures
324
- retryCount: options?.retryCount ?? 3, // Retry failed operations
325
- ...options
326
- };
327
- // Add list validation to progress tracking
328
- const wrappedCallback = progressCallback ?
329
- (progress) => {
330
- progress.context = { listId, listName: list.name };
331
- progressCallback(progress);
332
- } : undefined;
333
- return await this.bulkProcessor.processBulk(data.tasks, async (taskData) => {
334
- try {
335
- // Ensure task data is properly formatted
336
- const sanitizedData = {
337
- ...taskData,
338
- // Remove any accidentally included list IDs in task data
339
- list: undefined,
340
- // Ensure name has emoji if missing
341
- name: taskData.name.match(/^\p{Emoji}/u) ?
342
- taskData.name :
343
- '📋 ' + taskData.name
344
- };
345
- return await this.createTask(listId, sanitizedData);
346
- }
347
- catch (error) {
348
- // Enhance error context for better debugging
349
- if (error instanceof ClickUpServiceError) {
350
- error.context = {
351
- ...error.context,
352
- taskName: taskData.name,
353
- listId,
354
- listName: list.name
355
- };
356
- }
357
- throw error;
358
- }
359
- }, bulkOptions);
360
- }
361
- catch (error) {
362
- const errorMessage = error instanceof ClickUpServiceError ?
363
- error.message :
364
- `Failed to create tasks in bulk: ${error.message}`;
365
- throw new ClickUpServiceError(errorMessage, error instanceof ClickUpServiceError ? error.code : ErrorCode.UNKNOWN, {
366
- listId,
367
- taskCount: data.tasks.length,
368
- error: error instanceof Error ? error.message : String(error)
369
- });
370
- }
371
- }
372
- /**
373
- * Update multiple tasks in bulk with advanced batching options
290
+ * Get all comments for a task
374
291
  *
375
- * @param tasks Array of task IDs and update data
376
- * @param options Configuration options for the bulk operation
377
- * @param progressCallback Optional callback for tracking progress
378
- * @returns Result containing both successful and failed operations
292
+ * @param taskId ID of the task to get comments for
293
+ * @param start Optional pagination start
294
+ * @param startId Optional comment ID to start from
295
+ * @returns Array of task comments
379
296
  */
380
- async updateBulkTasks(tasks, options, progressCallback) {
381
- this.logOperation('updateBulkTasks', {
382
- taskCount: tasks.length,
383
- batchSize: options?.batchSize,
384
- concurrency: options?.concurrency
385
- });
297
+ async getTaskComments(taskId, start, startId) {
298
+ this.logOperation('getTaskComments', { taskId, start, startId });
386
299
  try {
387
- return await this.bulkProcessor.processBulk(tasks, ({ id, data }) => this.updateTask(id, data), options);
388
- }
389
- catch (error) {
390
- if (error instanceof ClickUpServiceError) {
391
- throw error;
300
+ // Build query parameters for pagination
301
+ const queryParams = new URLSearchParams();
302
+ if (start !== undefined) {
303
+ queryParams.append('start', start.toString());
392
304
  }
393
- throw new ClickUpServiceError(`Failed to update tasks in bulk: ${error.message}`, ErrorCode.UNKNOWN, error);
394
- }
395
- }
396
- /**
397
- * Move multiple tasks to a different list in bulk
398
- *
399
- * @param tasks Array of task IDs to move
400
- * @param targetListId ID of the list to move tasks to
401
- * @param options Configuration options for the bulk operation
402
- * @param progressCallback Optional callback for tracking progress
403
- * @returns Result containing both successful and failed operations
404
- */
405
- async moveBulkTasks(tasks, targetListId, options, progressCallback) {
406
- this.logOperation('moveBulkTasks', {
407
- taskCount: tasks.length,
408
- targetListId,
409
- batchSize: options?.batchSize,
410
- concurrency: options?.concurrency
411
- });
412
- try {
413
- // First verify destination list exists
414
- const destinationList = await this.listService.getList(targetListId);
415
- if (!destinationList) {
416
- throw new ClickUpServiceError(`Destination list not found with ID: ${targetListId}`, ErrorCode.NOT_FOUND);
305
+ if (startId) {
306
+ queryParams.append('start_id', startId);
417
307
  }
418
- // Set default options for better performance
419
- const bulkOptions = {
420
- batchSize: options?.batchSize ?? 5, // Smaller batch size for better rate limit handling
421
- concurrency: options?.concurrency ?? 2, // Lower concurrency to avoid rate limits
422
- continueOnError: options?.continueOnError ?? true, // Continue on individual task failures
423
- retryCount: options?.retryCount ?? 3, // Retry failed operations
424
- ...options
425
- };
426
- return await this.bulkProcessor.processBulk(tasks, async (taskId) => {
427
- try {
428
- // Get the original task
429
- const originalTask = await this.getTask(taskId);
430
- const currentStatus = originalTask.status?.status;
431
- const availableStatuses = destinationList.statuses?.map(s => s.status) || [];
432
- // Determine the appropriate status for the destination list
433
- let newStatus = availableStatuses.includes(currentStatus || '')
434
- ? currentStatus // Keep the same status if available in destination list
435
- : destinationList.statuses?.[0]?.status; // Otherwise use the default (first) status
436
- // Prepare the task data for the new list
437
- const taskData = {
438
- name: originalTask.name,
439
- description: originalTask.description,
440
- status: newStatus,
441
- priority: originalTask.priority?.priority,
442
- due_date: originalTask.due_date ? Number(originalTask.due_date) : undefined,
443
- assignees: originalTask.assignees?.map(a => a.id) || []
444
- };
445
- // Create new task and delete old one in a single makeRequest call
446
- return await this.makeRequest(async () => {
447
- // First create the new task
448
- const response = await this.client.post(`/list/${targetListId}/task`, taskData);
449
- // Then delete the original task
450
- await this.client.delete(`/task/${taskId}`);
451
- // Add a property to indicate the task was moved
452
- const newTask = {
453
- ...response.data,
454
- moved: true,
455
- originalId: taskId
456
- };
457
- return newTask;
458
- });
459
- }
460
- catch (error) {
461
- // Enhance error context for better debugging
462
- if (error instanceof ClickUpServiceError) {
463
- error.context = {
464
- ...error.context,
465
- taskId,
466
- targetListId,
467
- targetListName: destinationList.name
468
- };
469
- }
470
- throw error;
471
- }
472
- }, bulkOptions);
308
+ const queryString = queryParams.toString() ? `?${queryParams.toString()}` : '';
309
+ const response = await this.client.get(`/task/${taskId}/comment${queryString}`);
310
+ return response.data.comments || [];
473
311
  }
474
312
  catch (error) {
475
- const errorMessage = error instanceof ClickUpServiceError ?
476
- error.message :
477
- `Failed to move tasks in bulk: ${error.message}`;
478
- throw new ClickUpServiceError(errorMessage, error instanceof ClickUpServiceError ? error.code : ErrorCode.UNKNOWN, {
479
- targetListId,
480
- taskCount: tasks.length,
481
- error: error instanceof Error ? error.message : String(error)
482
- });
313
+ throw this.handleError(error, 'Failed to get task comments');
483
314
  }
484
315
  }
485
316
  /**
486
- * Delete multiple tasks in bulk with advanced batching options
317
+ * Validate that a list exists
487
318
  *
488
- * @param taskIds Array of task IDs to delete
489
- * @param options Configuration options for the bulk operation
490
- * @returns Result containing both successful and failed operations
319
+ * @param listId ID of the list to validate
320
+ * @throws ClickUpServiceError if the list doesn't exist
491
321
  */
492
- async deleteBulkTasks(taskIds, options) {
493
- this.logOperation('deleteBulkTasks', {
494
- taskCount: taskIds.length,
495
- batchSize: options?.batchSize,
496
- concurrency: options?.concurrency
497
- });
322
+ async validateListExists(listId) {
323
+ this.logOperation('validateListExists', { listId });
498
324
  try {
499
- return await this.bulkProcessor.processBulk(taskIds, async (taskId) => {
500
- await this.deleteTask(taskId);
501
- return taskId;
502
- }, options);
325
+ const list = await this.listService.getList(listId);
326
+ if (!list) {
327
+ throw new ClickUpServiceError(`List not found: ${listId}`, ErrorCode.NOT_FOUND, { listId });
328
+ }
503
329
  }
504
330
  catch (error) {
505
331
  if (error instanceof ClickUpServiceError) {
506
332
  throw error;
507
333
  }
508
- throw new ClickUpServiceError(`Failed to delete tasks in bulk: ${error.message}`, ErrorCode.UNKNOWN, error);
509
- }
510
- }
511
- /**
512
- * Get comments for a specific task
513
- * @param taskId The ID of the task to retrieve comments for
514
- * @param start Optional parameter for pagination, timestamp from which to start fetching comments
515
- * @param startId Optional parameter for pagination, comment ID from which to start fetching
516
- * @returns Array of task comments
517
- */
518
- async getTaskComments(taskId, start, startId) {
519
- this.logOperation('getTaskComments', { taskId, start, startId });
520
- try {
521
- return await this.makeRequest(async () => {
522
- const params = new URLSearchParams();
523
- // Add pagination parameters if provided
524
- if (start)
525
- params.append('start', String(start));
526
- if (startId)
527
- params.append('start_id', startId);
528
- const response = await this.client.get(`/task/${taskId}/comment`, { params });
529
- return response.data.comments;
530
- });
531
- }
532
- catch (error) {
533
- throw this.handleError(error, `Failed to retrieve comments for task ${taskId}`);
334
+ throw new ClickUpServiceError(`Failed to validate list existence: ${error instanceof Error ? error.message : String(error)}`, ErrorCode.UNKNOWN, { listId });
534
335
  }
535
336
  }
536
337
  }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Alternative implementation for bulk tasks creation
3
+ */
4
+ import { handleCreateBulkTasks } from './task.js';
5
+ /**
6
+ * Alternative tool definition for bulk task creation to work around MCP validation issues
7
+ */
8
+ export const createTasksBulkTool = {
9
+ name: "create_tasks_bulk",
10
+ description: "Create multiple tasks in a list efficiently. You MUST provide:\n1. An array of tasks with required properties\n2. Either listId or listName to specify the target list",
11
+ inputSchema: {
12
+ type: "object",
13
+ properties: {
14
+ listId: {
15
+ type: "string",
16
+ description: "ID of list for new tasks (preferred). Use this instead of listName if you have it."
17
+ },
18
+ listName: {
19
+ type: "string",
20
+ description: "Name of list for new tasks. Only use if you don't have listId."
21
+ },
22
+ tasks: {
23
+ // Define minimally to avoid validation issues
24
+ description: "Array of tasks to create. Each task must have at least a name."
25
+ }
26
+ },
27
+ required: ["tasks"]
28
+ }
29
+ };
30
+ /**
31
+ * Handler for create_tasks_bulk tool
32
+ */
33
+ export async function handleCreateTasksBulk(parameters) {
34
+ // Use the same implementation as handleCreateBulkTasks
35
+ return handleCreateBulkTasks(parameters);
36
+ }