@taazkareem/clickup-mcp-server 0.4.70 → 0.4.72
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.
- package/README.md +35 -18
- package/build/config.js +7 -4
- package/build/index.js +1 -1
- package/build/mcp-tools.js +64 -0
- package/build/server.js +4 -4
- package/build/server.log +34 -211
- package/build/services/clickup/bulk.js +132 -101
- package/build/services/clickup/task.js +40 -239
- package/build/tools/bulk-tasks.js +36 -0
- package/build/tools/task.js +616 -539
- package/build/tools/utils.js +8 -147
- package/build/utils/concurrency-utils.js +245 -0
- package/build/utils/date-utils.js +152 -0
- package/build/utils/params-utils.js +39 -0
- package/build/utils/resolver-utils.js +66 -0
- package/build/utils/sponsor-utils.js +49 -0
- package/package.json +1 -1
|
@@ -1,116 +1,147 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
104
|
+
// Reuse the single-task move method
|
|
105
|
+
return this.taskService.moveTask(taskId, targetListId);
|
|
106
|
+
}, options);
|
|
86
107
|
}
|
|
87
108
|
catch (error) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
//
|
|
270
|
+
// Get the original task to duplicate
|
|
267
271
|
const originalTask = await this.getTask(taskId);
|
|
268
|
-
//
|
|
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} (
|
|
283
|
-
description: originalTask.description,
|
|
274
|
+
name: `${originalTask.name} (copy)`,
|
|
275
|
+
description: originalTask.description || '',
|
|
284
276
|
status: originalTask.status?.status,
|
|
285
|
-
priority:
|
|
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
|
-
*
|
|
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
|
|
376
|
-
* @param
|
|
377
|
-
* @param
|
|
378
|
-
* @returns
|
|
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
|
|
381
|
-
this.logOperation('
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
|
|
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
|
-
|
|
419
|
-
const
|
|
420
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
317
|
+
* Validate that a list exists
|
|
487
318
|
*
|
|
488
|
-
* @param
|
|
489
|
-
* @
|
|
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
|
|
493
|
-
this.logOperation('
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
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
|
|
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
|
+
}
|