@taazkareem/clickup-mcp-server 0.6.3 → 0.6.4
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/build/server.js +1 -1
- package/build/services/clickup/bulk.js +92 -81
- package/build/services/clickup/task/task-core.js +18 -56
- package/build/tools/task/attachments.js +31 -15
- package/build/tools/task/bulk-operations.js +57 -69
- package/build/tools/task/handlers.js +51 -33
- package/build/tools/task/main.js +125 -31
- package/build/tools/task/single-operations.js +33 -15
- package/build/tools/task/utilities.js +23 -4
- package/package.json +1 -1
package/build/server.js
CHANGED
|
@@ -7,15 +7,21 @@
|
|
|
7
7
|
* Enhanced implementation for bulk operations that leverages the existing single-operation methods.
|
|
8
8
|
* This approach reduces code duplication while offering powerful concurrency management.
|
|
9
9
|
*/
|
|
10
|
-
import { ClickUpServiceError, ErrorCode } from './base.js';
|
|
11
|
-
import { processBatch } from '../../utils/concurrency-utils.js';
|
|
12
10
|
import { Logger } from '../../logger.js';
|
|
11
|
+
import { processBatch } from '../../utils/concurrency-utils.js';
|
|
12
|
+
import { ClickUpServiceError, ErrorCode } from './base.js';
|
|
13
|
+
import { clickUpServices } from '../shared.js';
|
|
14
|
+
import { findListIDByName } from '../../tools/list.js';
|
|
13
15
|
// Create logger instance
|
|
14
16
|
const logger = new Logger('BulkService');
|
|
15
17
|
/**
|
|
16
|
-
* Service for
|
|
18
|
+
* Service for performing bulk operations in ClickUp
|
|
17
19
|
*/
|
|
18
20
|
export class BulkService {
|
|
21
|
+
/**
|
|
22
|
+
* Create a new bulk service
|
|
23
|
+
* @param taskService ClickUp Task Service instance
|
|
24
|
+
*/
|
|
19
25
|
constructor(taskService) {
|
|
20
26
|
this.taskService = taskService;
|
|
21
27
|
logger.info('BulkService initialized');
|
|
@@ -54,6 +60,61 @@ export class BulkService {
|
|
|
54
60
|
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 });
|
|
55
61
|
}
|
|
56
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Find task by name within a specific list
|
|
65
|
+
*/
|
|
66
|
+
async findTaskInList(taskName, listName) {
|
|
67
|
+
try {
|
|
68
|
+
// First get the list ID using the global lookup utility
|
|
69
|
+
const listInfo = await findListIDByName(clickUpServices.workspace, listName);
|
|
70
|
+
if (!listInfo) {
|
|
71
|
+
throw new ClickUpServiceError(`List "${listName}" not found`, ErrorCode.NOT_FOUND);
|
|
72
|
+
}
|
|
73
|
+
logger.info(`List "${listName}" resolved to ID: ${listInfo.id}`);
|
|
74
|
+
// Get tasks from the list using the resolved ID
|
|
75
|
+
const taskList = await this.taskService.getTasks(listInfo.id);
|
|
76
|
+
// Find the task by name - first try exact match
|
|
77
|
+
let matchingTask = taskList.find(t => t.name === taskName);
|
|
78
|
+
// If no exact match, try case-insensitive match
|
|
79
|
+
if (!matchingTask) {
|
|
80
|
+
matchingTask = taskList.find(t => t.name.toLowerCase() === taskName.toLowerCase());
|
|
81
|
+
// If still no match, try substring match as a fallback
|
|
82
|
+
if (!matchingTask) {
|
|
83
|
+
matchingTask = taskList.find(t => t.name.toLowerCase().includes(taskName.toLowerCase()) ||
|
|
84
|
+
taskName.toLowerCase().includes(t.name.toLowerCase()));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (!matchingTask) {
|
|
88
|
+
throw new ClickUpServiceError(`Task "${taskName}" not found in list "${listName}"`, ErrorCode.NOT_FOUND);
|
|
89
|
+
}
|
|
90
|
+
logger.info(`Task "${taskName}" found with ID: ${matchingTask.id}`);
|
|
91
|
+
return matchingTask.id;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
// Enhance the error message
|
|
95
|
+
if (error instanceof ClickUpServiceError) {
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
throw new ClickUpServiceError(`Error finding task "${taskName}" in list "${listName}": ${error instanceof Error ? error.message : String(error)}`, ErrorCode.UNKNOWN);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Resolve task ID using provided identifiers
|
|
103
|
+
*/
|
|
104
|
+
async resolveTaskId(task) {
|
|
105
|
+
const { taskId, taskName, listName, customTaskId } = task;
|
|
106
|
+
if (taskId) {
|
|
107
|
+
return taskId;
|
|
108
|
+
}
|
|
109
|
+
if (customTaskId) {
|
|
110
|
+
const resolvedTask = await this.taskService.getTaskByCustomId(customTaskId);
|
|
111
|
+
return resolvedTask.id;
|
|
112
|
+
}
|
|
113
|
+
if (taskName && listName) {
|
|
114
|
+
return await this.findTaskInList(taskName, listName);
|
|
115
|
+
}
|
|
116
|
+
throw new ClickUpServiceError('Invalid task identification. Provide either taskId, customTaskId, or both taskName and listName', ErrorCode.INVALID_PARAMETER);
|
|
117
|
+
}
|
|
57
118
|
/**
|
|
58
119
|
* Update multiple tasks
|
|
59
120
|
* @param tasks Array of tasks to update with their new data
|
|
@@ -63,36 +124,10 @@ export class BulkService {
|
|
|
63
124
|
async updateTasks(tasks, options) {
|
|
64
125
|
logger.info('Starting bulk update operation', { taskCount: tasks.length });
|
|
65
126
|
try {
|
|
66
|
-
// Extract all task IDs that need validation
|
|
67
|
-
const taskIds = tasks
|
|
68
|
-
.map(task => task.taskId)
|
|
69
|
-
.filter((id) => !!id);
|
|
70
|
-
// Validate all tasks exist in parallel
|
|
71
|
-
if (taskIds.length > 0) {
|
|
72
|
-
await this.taskService.validateTasksExist(taskIds);
|
|
73
|
-
}
|
|
74
|
-
// Process updates in batches
|
|
75
127
|
return await processBatch(tasks, async (task) => {
|
|
76
128
|
const { taskId, taskName, listName, customTaskId, ...updateData } = task;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
else if (customTaskId) {
|
|
81
|
-
const resolvedTask = await this.taskService.getTaskByCustomId(customTaskId);
|
|
82
|
-
return await this.taskService.updateTask(resolvedTask.id, updateData);
|
|
83
|
-
}
|
|
84
|
-
else if (taskName && listName) {
|
|
85
|
-
// For tasks identified by name, we need to resolve the ID first
|
|
86
|
-
const taskList = await this.taskService.getTasks(listName);
|
|
87
|
-
const matchingTask = taskList.find(t => t.name === taskName);
|
|
88
|
-
if (!matchingTask) {
|
|
89
|
-
throw new ClickUpServiceError(`Task "${taskName}" not found in list "${listName}"`, ErrorCode.NOT_FOUND);
|
|
90
|
-
}
|
|
91
|
-
return await this.taskService.updateTask(matchingTask.id, updateData);
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
throw new ClickUpServiceError('Invalid task identification. Provide either taskId, customTaskId, or both taskName and listName', ErrorCode.INVALID_PARAMETER);
|
|
95
|
-
}
|
|
129
|
+
const resolvedTaskId = await this.resolveTaskId({ taskId, taskName, listName, customTaskId });
|
|
130
|
+
return await this.taskService.updateTask(resolvedTaskId, updateData);
|
|
96
131
|
}, options);
|
|
97
132
|
}
|
|
98
133
|
catch (error) {
|
|
@@ -103,44 +138,31 @@ export class BulkService {
|
|
|
103
138
|
/**
|
|
104
139
|
* Move multiple tasks to a different list
|
|
105
140
|
* @param tasks Array of tasks to move (each with taskId or taskName + listName)
|
|
106
|
-
* @param targetListId ID of the destination list
|
|
141
|
+
* @param targetListId ID of the destination list or list name
|
|
107
142
|
* @param options Optional batch processing settings
|
|
108
143
|
* @returns Array of moved tasks
|
|
109
144
|
*/
|
|
110
145
|
async moveTasks(tasks, targetListId, options) {
|
|
111
146
|
logger.info('Starting bulk move operation', { taskCount: tasks.length, targetListId });
|
|
112
147
|
try {
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
148
|
+
// Determine if targetListId is actually an ID or a name
|
|
149
|
+
let resolvedTargetListId = targetListId;
|
|
150
|
+
// If the targetListId doesn't match the pattern of a list ID (usually just numbers),
|
|
151
|
+
// assume it's a list name and try to resolve it
|
|
152
|
+
if (!/^\d+$/.test(targetListId)) {
|
|
153
|
+
logger.info(`Target list appears to be a name: "${targetListId}", attempting to resolve`);
|
|
154
|
+
const listInfo = await findListIDByName(clickUpServices.workspace, targetListId);
|
|
155
|
+
if (!listInfo) {
|
|
156
|
+
throw new ClickUpServiceError(`Target list "${targetListId}" not found`, ErrorCode.NOT_FOUND);
|
|
157
|
+
}
|
|
158
|
+
resolvedTargetListId = listInfo.id;
|
|
159
|
+
logger.info(`Resolved target list to ID: ${resolvedTargetListId}`);
|
|
122
160
|
}
|
|
123
|
-
//
|
|
161
|
+
// Validate the destination list exists
|
|
162
|
+
await this.taskService.validateListExists(resolvedTargetListId);
|
|
124
163
|
return await processBatch(tasks, async (task) => {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
else if (task.customTaskId) {
|
|
129
|
-
const resolvedTask = await this.taskService.getTaskByCustomId(task.customTaskId);
|
|
130
|
-
return await this.taskService.moveTask(resolvedTask.id, targetListId);
|
|
131
|
-
}
|
|
132
|
-
else if (task.taskName && task.listName) {
|
|
133
|
-
// For tasks identified by name, we need to resolve the ID first
|
|
134
|
-
const taskList = await this.taskService.getTasks(task.listName);
|
|
135
|
-
const matchingTask = taskList.find(t => t.name === task.taskName);
|
|
136
|
-
if (!matchingTask) {
|
|
137
|
-
throw new ClickUpServiceError(`Task "${task.taskName}" not found in list "${task.listName}"`, ErrorCode.NOT_FOUND);
|
|
138
|
-
}
|
|
139
|
-
return await this.taskService.moveTask(matchingTask.id, targetListId);
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
throw new ClickUpServiceError('Invalid task identification. Provide either taskId, customTaskId, or both taskName and listName', ErrorCode.INVALID_PARAMETER);
|
|
143
|
-
}
|
|
164
|
+
const resolvedTaskId = await this.resolveTaskId(task);
|
|
165
|
+
return await this.taskService.moveTask(resolvedTaskId, resolvedTargetListId);
|
|
144
166
|
}, options);
|
|
145
167
|
}
|
|
146
168
|
catch (error) {
|
|
@@ -149,33 +171,22 @@ export class BulkService {
|
|
|
149
171
|
}
|
|
150
172
|
}
|
|
151
173
|
/**
|
|
152
|
-
* Delete multiple tasks
|
|
153
|
-
*
|
|
154
|
-
* @param taskIds Array of task IDs to delete
|
|
174
|
+
* Delete multiple tasks
|
|
175
|
+
* @param tasks Array of tasks to delete (each with taskId or taskName + listName)
|
|
155
176
|
* @param options Batch processing options
|
|
156
177
|
* @returns Results containing successful and failed deletions
|
|
157
178
|
*/
|
|
158
|
-
async deleteTasks(
|
|
159
|
-
logger.info(
|
|
160
|
-
batchSize: options?.batchSize,
|
|
161
|
-
concurrency: options?.concurrency
|
|
162
|
-
});
|
|
179
|
+
async deleteTasks(tasks, options) {
|
|
180
|
+
logger.info('Starting bulk delete operation', { taskCount: tasks.length });
|
|
163
181
|
try {
|
|
164
|
-
return await processBatch(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
});
|
|
168
|
-
// Reuse the single-task delete method
|
|
169
|
-
await this.taskService.deleteTask(taskId);
|
|
170
|
-
return taskId; // Return the ID for successful deletions
|
|
182
|
+
return await processBatch(tasks, async (task) => {
|
|
183
|
+
const resolvedTaskId = await this.resolveTaskId(task);
|
|
184
|
+
await this.taskService.deleteTask(resolvedTaskId);
|
|
171
185
|
}, options);
|
|
172
186
|
}
|
|
173
187
|
catch (error) {
|
|
174
|
-
logger.error(
|
|
175
|
-
|
|
176
|
-
error: error instanceof Error ? error.message : String(error)
|
|
177
|
-
});
|
|
178
|
-
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 });
|
|
188
|
+
logger.error('Bulk delete operation failed', error);
|
|
189
|
+
throw error;
|
|
179
190
|
}
|
|
180
191
|
}
|
|
181
192
|
}
|
|
@@ -276,7 +276,7 @@ export class TaskServiceCore extends BaseClickUpService {
|
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
/**
|
|
279
|
-
* Move a task to
|
|
279
|
+
* Move a task to another list
|
|
280
280
|
* @param taskId The ID of the task to move
|
|
281
281
|
* @param destinationListId The ID of the list to move the task to
|
|
282
282
|
* @returns The updated task
|
|
@@ -285,70 +285,32 @@ export class TaskServiceCore extends BaseClickUpService {
|
|
|
285
285
|
const startTime = Date.now();
|
|
286
286
|
this.logOperation('moveTask', { taskId, destinationListId, operation: 'start' });
|
|
287
287
|
try {
|
|
288
|
-
// First, get
|
|
289
|
-
const [
|
|
288
|
+
// First, get task and validate destination list
|
|
289
|
+
const [sourceTask, _] = await Promise.all([
|
|
290
290
|
this.validateTaskExists(taskId),
|
|
291
|
-
this.validateListExists(destinationListId)
|
|
291
|
+
this.validateListExists(destinationListId)
|
|
292
292
|
]);
|
|
293
|
-
|
|
294
|
-
const
|
|
295
|
-
//
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
});
|
|
303
|
-
const currentStatus = originalTask.status?.status;
|
|
304
|
-
const availableStatuses = destinationList.statuses?.map(s => s.status) || [];
|
|
305
|
-
// Enhanced status mapping logic
|
|
306
|
-
let newStatus = currentStatus;
|
|
307
|
-
if (currentStatus && availableStatuses.length > 0) {
|
|
308
|
-
// Only map status if current status isn't available in destination list
|
|
309
|
-
if (!availableStatuses.includes(currentStatus)) {
|
|
310
|
-
// Try to find a similar status
|
|
311
|
-
const similarStatus = availableStatuses.find(s => s.toLowerCase().includes(currentStatus.toLowerCase()) ||
|
|
312
|
-
currentStatus.toLowerCase().includes(s.toLowerCase()));
|
|
313
|
-
// If no similar status found, use the first available status
|
|
314
|
-
newStatus = similarStatus || availableStatuses[0];
|
|
315
|
-
this.logger.debug('Status mapping', {
|
|
316
|
-
original: currentStatus,
|
|
317
|
-
mapped: newStatus,
|
|
318
|
-
available: availableStatuses
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
// Make the move request
|
|
323
|
-
const movedTask = await this.makeRequest(async () => {
|
|
324
|
-
const response = await this.client.post(`/task/${taskId}`, {
|
|
325
|
-
list: destinationListId,
|
|
326
|
-
status: newStatus
|
|
327
|
-
});
|
|
328
|
-
return response.data;
|
|
329
|
-
});
|
|
330
|
-
// Cache the moved task
|
|
331
|
-
this.validationCache.tasks.set(taskId, {
|
|
293
|
+
// Extract task data for creating the new task
|
|
294
|
+
const taskData = this.extractTaskData(sourceTask);
|
|
295
|
+
// Create the task in the new list
|
|
296
|
+
const newTask = await this.createTask(destinationListId, taskData);
|
|
297
|
+
// Delete the original task
|
|
298
|
+
await this.deleteTask(taskId);
|
|
299
|
+
// Update the cache
|
|
300
|
+
this.validationCache.tasks.delete(taskId);
|
|
301
|
+
this.validationCache.tasks.set(newTask.id, {
|
|
332
302
|
validatedAt: Date.now(),
|
|
333
|
-
task:
|
|
303
|
+
task: newTask
|
|
334
304
|
});
|
|
335
305
|
const totalTime = Date.now() - startTime;
|
|
336
306
|
this.logOperation('moveTask', {
|
|
337
307
|
taskId,
|
|
338
308
|
destinationListId,
|
|
339
309
|
operation: 'complete',
|
|
340
|
-
timing: {
|
|
341
|
-
|
|
342
|
-
parallelRequestTime,
|
|
343
|
-
moveOperationTime: totalTime - parallelRequestTime
|
|
344
|
-
},
|
|
345
|
-
statusMapping: {
|
|
346
|
-
original: currentStatus,
|
|
347
|
-
new: newStatus,
|
|
348
|
-
wasMapped: currentStatus !== newStatus
|
|
349
|
-
}
|
|
310
|
+
timing: { totalTime },
|
|
311
|
+
newTaskId: newTask.id
|
|
350
312
|
});
|
|
351
|
-
return
|
|
313
|
+
return newTask;
|
|
352
314
|
}
|
|
353
315
|
catch (error) {
|
|
354
316
|
// Log failure
|
|
@@ -356,7 +318,7 @@ export class TaskServiceCore extends BaseClickUpService {
|
|
|
356
318
|
taskId,
|
|
357
319
|
destinationListId,
|
|
358
320
|
operation: 'failed',
|
|
359
|
-
error: error.message,
|
|
321
|
+
error: error instanceof Error ? error.message : String(error),
|
|
360
322
|
timing: { totalTime: Date.now() - startTime }
|
|
361
323
|
});
|
|
362
324
|
throw this.handleError(error, 'Failed to move task');
|
|
@@ -33,22 +33,39 @@ export const attachTaskFileTool = {
|
|
|
33
33
|
description: `Purpose: Attaches a file to a ClickUp task.
|
|
34
34
|
|
|
35
35
|
Valid Usage:
|
|
36
|
+
1. Use taskId alone (preferred) - works with both regular and custom IDs
|
|
37
|
+
2. Use taskName alone (will search across all lists)
|
|
38
|
+
3. Use taskName + listName (for faster, targeted search)
|
|
39
|
+
|
|
40
|
+
File Source Options:
|
|
36
41
|
1. Upload from base64: Provide file_data + file_name
|
|
37
|
-
2. Upload from URL
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
3. For large files, advanced options are available via chunk_* parameters
|
|
42
|
+
2. Upload from URL: Provide file_url starting with http:// or https://
|
|
43
|
+
3. Upload from local file: Provide file_url as absolute path (starting with / or drive letter)
|
|
44
|
+
4. For large files: Use chunk_* parameters for advanced chunked uploading
|
|
41
45
|
|
|
42
46
|
Requirements:
|
|
43
|
-
- EITHER taskId OR
|
|
44
|
-
-
|
|
47
|
+
- EITHER taskId OR taskName: REQUIRED
|
|
48
|
+
- listName: Optional, but recommended when using taskName
|
|
49
|
+
- File Source: ONE of the following is REQUIRED:
|
|
50
|
+
- file_data + file_name
|
|
51
|
+
- file_url (web URL or local path)
|
|
52
|
+
- chunk_session (for continuing chunked upload)
|
|
45
53
|
|
|
46
54
|
Notes:
|
|
47
|
-
- The
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
55
|
+
- The tool automatically searches for tasks using smart name matching
|
|
56
|
+
- When only taskName is provided, it searches across all lists
|
|
57
|
+
- Adding listName narrows the search to a specific list for better performance
|
|
58
|
+
- The system automatically selects the best upload method based on file size and source:
|
|
59
|
+
- Base64 method: Limited to 10MB due to encoding overhead
|
|
60
|
+
- URL method: Works for files hosted online
|
|
61
|
+
- Local file method: Works with absolute paths only
|
|
62
|
+
- Large files: Automatically uses chunked uploading
|
|
63
|
+
|
|
64
|
+
Warning:
|
|
65
|
+
- Using taskName without listName may match multiple tasks
|
|
66
|
+
- If multiple matches are found, the operation will fail with a disambiguation error
|
|
67
|
+
- For local files, relative paths are not supported
|
|
68
|
+
- Base64 uploads over 10MB will automatically switch to chunked upload mode`,
|
|
52
69
|
inputSchema: {
|
|
53
70
|
type: "object",
|
|
54
71
|
properties: {
|
|
@@ -58,11 +75,11 @@ Notes:
|
|
|
58
75
|
},
|
|
59
76
|
taskName: {
|
|
60
77
|
type: "string",
|
|
61
|
-
description: "Name of the task to attach the file to.
|
|
78
|
+
description: "Name of the task to attach the file to. The tool will search for tasks with this name across all lists unless listName is specified."
|
|
62
79
|
},
|
|
63
80
|
listName: {
|
|
64
81
|
type: "string",
|
|
65
|
-
description: "Name of
|
|
82
|
+
description: "Optional: Name of list containing the task. Providing this narrows the search to a specific list, improving performance and reducing ambiguity."
|
|
66
83
|
},
|
|
67
84
|
file_name: {
|
|
68
85
|
type: "string",
|
|
@@ -97,8 +114,7 @@ Notes:
|
|
|
97
114
|
type: "boolean",
|
|
98
115
|
description: "Optional: For advanced usage with large file chunking. Whether this is the final chunk."
|
|
99
116
|
}
|
|
100
|
-
}
|
|
101
|
-
required: [] // Will validate based on context in the handler
|
|
117
|
+
}
|
|
102
118
|
}
|
|
103
119
|
};
|
|
104
120
|
/**
|
|
@@ -76,17 +76,17 @@ export const createBulkTasksTool = {
|
|
|
76
76
|
description: `Purpose: Create multiple tasks in a list efficiently.
|
|
77
77
|
|
|
78
78
|
Valid Usage:
|
|
79
|
-
1.
|
|
80
|
-
2.
|
|
79
|
+
1. Provide listId + array of tasks (preferred)
|
|
80
|
+
2. Provide listName + array of tasks
|
|
81
81
|
|
|
82
82
|
Requirements:
|
|
83
83
|
- tasks: REQUIRED (array of tasks, each with at least a name)
|
|
84
84
|
- EITHER listId OR listName: REQUIRED
|
|
85
|
+
- All tasks will be created in the specified list
|
|
85
86
|
|
|
86
87
|
Notes:
|
|
87
88
|
- Configure batch size and concurrency via options for performance
|
|
88
89
|
- Each task should have a name with emoji prefix
|
|
89
|
-
- All tasks will be created in the same list
|
|
90
90
|
- Custom fields can be set for each task using the custom_fields property (array of {id, value} objects)`,
|
|
91
91
|
inputSchema: {
|
|
92
92
|
type: "object",
|
|
@@ -157,37 +157,7 @@ Notes:
|
|
|
157
157
|
type: "string",
|
|
158
158
|
description: "Name of list for new tasks. Only use if you don't have listId."
|
|
159
159
|
},
|
|
160
|
-
options:
|
|
161
|
-
description: "Processing options (or JSON string representing options)",
|
|
162
|
-
oneOf: [
|
|
163
|
-
{
|
|
164
|
-
type: "object",
|
|
165
|
-
description: "Optional processing settings",
|
|
166
|
-
properties: {
|
|
167
|
-
batchSize: {
|
|
168
|
-
type: "number",
|
|
169
|
-
description: "Tasks per batch (default: 10)"
|
|
170
|
-
},
|
|
171
|
-
concurrency: {
|
|
172
|
-
type: "number",
|
|
173
|
-
description: "Parallel operations (default: 3)"
|
|
174
|
-
},
|
|
175
|
-
continueOnError: {
|
|
176
|
-
type: "boolean",
|
|
177
|
-
description: "Continue if some tasks fail"
|
|
178
|
-
},
|
|
179
|
-
retryCount: {
|
|
180
|
-
type: "number",
|
|
181
|
-
description: "Retry attempts for failures"
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
type: "string",
|
|
187
|
-
description: "JSON string representing options. Will be parsed automatically."
|
|
188
|
-
}
|
|
189
|
-
]
|
|
190
|
-
}
|
|
160
|
+
options: bulkOptionsSchema
|
|
191
161
|
},
|
|
192
162
|
required: ["tasks"]
|
|
193
163
|
}
|
|
@@ -212,7 +182,10 @@ Notes:
|
|
|
212
182
|
- Only specified fields will be updated for each task
|
|
213
183
|
- Configure batch size and concurrency via options for performance
|
|
214
184
|
- Each task can have different fields to update
|
|
215
|
-
- Custom fields can be updated using the custom_fields property (array of {id, value} objects)
|
|
185
|
+
- Custom fields can be updated using the custom_fields property (array of {id, value} objects)
|
|
186
|
+
|
|
187
|
+
Warning:
|
|
188
|
+
- Using taskName without listName will fail as tasks may have identical names across lists`,
|
|
216
189
|
inputSchema: {
|
|
217
190
|
type: "object",
|
|
218
191
|
properties: {
|
|
@@ -222,7 +195,22 @@ Notes:
|
|
|
222
195
|
items: {
|
|
223
196
|
type: "object",
|
|
224
197
|
properties: {
|
|
225
|
-
|
|
198
|
+
taskId: {
|
|
199
|
+
type: "string",
|
|
200
|
+
description: "Task ID (preferred). Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234')."
|
|
201
|
+
},
|
|
202
|
+
taskName: {
|
|
203
|
+
type: "string",
|
|
204
|
+
description: "Task name. Requires listName when used."
|
|
205
|
+
},
|
|
206
|
+
listName: {
|
|
207
|
+
type: "string",
|
|
208
|
+
description: "REQUIRED with taskName: List containing the task."
|
|
209
|
+
},
|
|
210
|
+
customTaskId: {
|
|
211
|
+
type: "string",
|
|
212
|
+
description: "Custom task ID (e.g., 'DEV-1234'). Only use if you want to explicitly force custom ID lookup. In most cases, use taskId which auto-detects ID format."
|
|
213
|
+
},
|
|
226
214
|
name: {
|
|
227
215
|
type: "string",
|
|
228
216
|
description: "New name with emoji prefix"
|
|
@@ -268,37 +256,7 @@ Notes:
|
|
|
268
256
|
}
|
|
269
257
|
}
|
|
270
258
|
},
|
|
271
|
-
options:
|
|
272
|
-
description: "Processing options (or JSON string representing options)",
|
|
273
|
-
oneOf: [
|
|
274
|
-
{
|
|
275
|
-
type: "object",
|
|
276
|
-
description: "Optional processing settings",
|
|
277
|
-
properties: {
|
|
278
|
-
batchSize: {
|
|
279
|
-
type: "number",
|
|
280
|
-
description: "Tasks per batch (default: 10)"
|
|
281
|
-
},
|
|
282
|
-
concurrency: {
|
|
283
|
-
type: "number",
|
|
284
|
-
description: "Parallel operations (default: 3)"
|
|
285
|
-
},
|
|
286
|
-
continueOnError: {
|
|
287
|
-
type: "boolean",
|
|
288
|
-
description: "Continue if some tasks fail"
|
|
289
|
-
},
|
|
290
|
-
retryCount: {
|
|
291
|
-
type: "number",
|
|
292
|
-
description: "Retry attempts for failures"
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
},
|
|
296
|
-
{
|
|
297
|
-
type: "string",
|
|
298
|
-
description: "JSON string representing options. Will be parsed automatically."
|
|
299
|
-
}
|
|
300
|
-
]
|
|
301
|
-
}
|
|
259
|
+
options: bulkOptionsSchema
|
|
302
260
|
},
|
|
303
261
|
required: ["tasks"]
|
|
304
262
|
}
|
|
@@ -335,7 +293,22 @@ Warning:
|
|
|
335
293
|
items: {
|
|
336
294
|
type: "object",
|
|
337
295
|
properties: {
|
|
338
|
-
|
|
296
|
+
taskId: {
|
|
297
|
+
type: "string",
|
|
298
|
+
description: "Task ID (preferred). Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234')."
|
|
299
|
+
},
|
|
300
|
+
taskName: {
|
|
301
|
+
type: "string",
|
|
302
|
+
description: "Task name. Requires listName when used."
|
|
303
|
+
},
|
|
304
|
+
listName: {
|
|
305
|
+
type: "string",
|
|
306
|
+
description: "REQUIRED with taskName: List containing the task."
|
|
307
|
+
},
|
|
308
|
+
customTaskId: {
|
|
309
|
+
type: "string",
|
|
310
|
+
description: "Custom task ID (e.g., 'DEV-1234'). Only use if you want to explicitly force custom ID lookup. In most cases, use taskId which auto-detects ID format."
|
|
311
|
+
}
|
|
339
312
|
}
|
|
340
313
|
}
|
|
341
314
|
},
|
|
@@ -383,7 +356,22 @@ Warning:
|
|
|
383
356
|
items: {
|
|
384
357
|
type: "object",
|
|
385
358
|
properties: {
|
|
386
|
-
|
|
359
|
+
taskId: {
|
|
360
|
+
type: "string",
|
|
361
|
+
description: "Task ID (preferred). Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234')."
|
|
362
|
+
},
|
|
363
|
+
taskName: {
|
|
364
|
+
type: "string",
|
|
365
|
+
description: "Task name. Requires listName when used."
|
|
366
|
+
},
|
|
367
|
+
listName: {
|
|
368
|
+
type: "string",
|
|
369
|
+
description: "REQUIRED with taskName: List containing the task."
|
|
370
|
+
},
|
|
371
|
+
customTaskId: {
|
|
372
|
+
type: "string",
|
|
373
|
+
description: "Custom task ID (e.g., 'DEV-1234'). Only use if you want to explicitly force custom ID lookup. In most cases, use taskId which auto-detects ID format."
|
|
374
|
+
}
|
|
387
375
|
}
|
|
388
376
|
}
|
|
389
377
|
},
|
|
@@ -392,58 +392,76 @@ export async function getWorkspaceTasksHandler(taskService, params) {
|
|
|
392
392
|
* Handler for creating multiple tasks
|
|
393
393
|
*/
|
|
394
394
|
export async function createBulkTasksHandler(params) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
395
|
+
const { tasks, listId, listName, options } = params;
|
|
396
|
+
// Validate tasks array
|
|
397
|
+
validateBulkTasks(tasks, 'create');
|
|
398
|
+
// Validate and resolve list ID
|
|
399
|
+
const targetListId = await resolveListIdWithValidation(listId, listName);
|
|
400
|
+
// Format tasks for creation
|
|
401
|
+
const formattedTasks = tasks.map(task => {
|
|
402
|
+
const taskData = {
|
|
403
|
+
name: task.name,
|
|
404
|
+
description: task.description,
|
|
405
|
+
markdown_description: task.markdown_description,
|
|
406
|
+
status: task.status,
|
|
407
|
+
priority: toTaskPriority(task.priority),
|
|
408
|
+
tags: task.tags,
|
|
409
|
+
custom_fields: task.custom_fields
|
|
410
|
+
};
|
|
411
|
+
// Add due date if specified
|
|
400
412
|
if (task.dueDate) {
|
|
401
|
-
|
|
402
|
-
|
|
413
|
+
taskData.due_date = parseDueDate(task.dueDate);
|
|
414
|
+
taskData.due_date_time = true;
|
|
403
415
|
}
|
|
404
|
-
//
|
|
405
|
-
if (task.
|
|
406
|
-
|
|
416
|
+
// Add start date if specified
|
|
417
|
+
if (task.startDate) {
|
|
418
|
+
taskData.start_date = parseDueDate(task.startDate);
|
|
419
|
+
taskData.start_date_time = true;
|
|
407
420
|
}
|
|
408
|
-
return
|
|
421
|
+
return taskData;
|
|
409
422
|
});
|
|
410
|
-
|
|
411
|
-
|
|
423
|
+
// Parse bulk options
|
|
424
|
+
const bulkOptions = parseBulkOptions(options);
|
|
425
|
+
// Create tasks - pass arguments in correct order: listId, tasks, options
|
|
426
|
+
return await bulkService.createTasks(targetListId, formattedTasks, bulkOptions);
|
|
412
427
|
}
|
|
413
428
|
/**
|
|
414
429
|
* Handler for updating multiple tasks
|
|
415
430
|
*/
|
|
416
431
|
export async function updateBulkTasksHandler(params) {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
return result.successful;
|
|
432
|
+
const { tasks, options } = params;
|
|
433
|
+
// Validate tasks array
|
|
434
|
+
validateBulkTasks(tasks, 'update');
|
|
435
|
+
// Parse bulk options
|
|
436
|
+
const bulkOptions = parseBulkOptions(options);
|
|
437
|
+
// Update tasks
|
|
438
|
+
return await bulkService.updateTasks(tasks, bulkOptions);
|
|
425
439
|
}
|
|
426
440
|
/**
|
|
427
441
|
* Handler for moving multiple tasks
|
|
428
442
|
*/
|
|
429
443
|
export async function moveBulkTasksHandler(params) {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
const
|
|
437
|
-
|
|
444
|
+
const { tasks, targetListId, targetListName, options } = params;
|
|
445
|
+
// Validate tasks array
|
|
446
|
+
validateBulkTasks(tasks, 'move');
|
|
447
|
+
// Validate and resolve target list ID
|
|
448
|
+
const resolvedTargetListId = await resolveListIdWithValidation(targetListId, targetListName);
|
|
449
|
+
// Parse bulk options
|
|
450
|
+
const bulkOptions = parseBulkOptions(options);
|
|
451
|
+
// Move tasks
|
|
452
|
+
return await bulkService.moveTasks(tasks, resolvedTargetListId, bulkOptions);
|
|
438
453
|
}
|
|
439
454
|
/**
|
|
440
455
|
* Handler for deleting multiple tasks
|
|
441
456
|
*/
|
|
442
457
|
export async function deleteBulkTasksHandler(params) {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
458
|
+
const { tasks, options } = params;
|
|
459
|
+
// Validate tasks array
|
|
460
|
+
validateBulkTasks(tasks, 'delete');
|
|
461
|
+
// Parse bulk options
|
|
462
|
+
const bulkOptions = parseBulkOptions(options);
|
|
463
|
+
// Delete tasks
|
|
464
|
+
return await bulkService.deleteTasks(tasks, bulkOptions);
|
|
447
465
|
}
|
|
448
466
|
/**
|
|
449
467
|
* Handler for deleting a task
|
package/build/tools/task/main.js
CHANGED
|
@@ -100,22 +100,37 @@ export const handleCreateTaskComment = createHandlerWrapper(createTaskCommentHan
|
|
|
100
100
|
//=============================================================================
|
|
101
101
|
// BULK TASK OPERATIONS - HANDLER IMPLEMENTATIONS
|
|
102
102
|
//=============================================================================
|
|
103
|
-
export const handleCreateBulkTasks = createHandlerWrapper(createBulkTasksHandler, (
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
export const handleCreateBulkTasks = createHandlerWrapper(createBulkTasksHandler, (result) => ({
|
|
104
|
+
successful: result.successful,
|
|
105
|
+
failed: result.failed,
|
|
106
|
+
count: result.totals.total,
|
|
107
|
+
success_count: result.totals.success,
|
|
108
|
+
failure_count: result.totals.failure,
|
|
109
|
+
errors: result.failed.map(f => f.error)
|
|
106
110
|
}));
|
|
107
|
-
export const handleUpdateBulkTasks = createHandlerWrapper(updateBulkTasksHandler, (
|
|
108
|
-
|
|
109
|
-
|
|
111
|
+
export const handleUpdateBulkTasks = createHandlerWrapper(updateBulkTasksHandler, (result) => ({
|
|
112
|
+
successful: result.successful,
|
|
113
|
+
failed: result.failed,
|
|
114
|
+
count: result.totals.total,
|
|
115
|
+
success_count: result.totals.success,
|
|
116
|
+
failure_count: result.totals.failure,
|
|
117
|
+
errors: result.failed.map(f => f.error)
|
|
110
118
|
}));
|
|
111
|
-
export const handleMoveBulkTasks = createHandlerWrapper(moveBulkTasksHandler, (
|
|
112
|
-
|
|
113
|
-
|
|
119
|
+
export const handleMoveBulkTasks = createHandlerWrapper(moveBulkTasksHandler, (result) => ({
|
|
120
|
+
successful: result.successful,
|
|
121
|
+
failed: result.failed,
|
|
122
|
+
count: result.totals.total,
|
|
123
|
+
success_count: result.totals.success,
|
|
124
|
+
failure_count: result.totals.failure,
|
|
125
|
+
errors: result.failed.map(f => f.error)
|
|
114
126
|
}));
|
|
115
|
-
export const handleDeleteBulkTasks = createHandlerWrapper(deleteBulkTasksHandler, (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
127
|
+
export const handleDeleteBulkTasks = createHandlerWrapper(deleteBulkTasksHandler, (result) => ({
|
|
128
|
+
successful: result.successful,
|
|
129
|
+
failed: result.failed,
|
|
130
|
+
count: result.totals.total,
|
|
131
|
+
success_count: result.totals.success,
|
|
132
|
+
failure_count: result.totals.failure,
|
|
133
|
+
errors: result.failed.map(f => f.error)
|
|
119
134
|
}));
|
|
120
135
|
//=============================================================================
|
|
121
136
|
// WORKSPACE TASK OPERATIONS - HANDLER IMPLEMENTATIONS
|
|
@@ -128,22 +143,101 @@ export const handleGetWorkspaceTasks = createHandlerWrapper(
|
|
|
128
143
|
// TOOL DEFINITIONS AND HANDLERS EXPORT
|
|
129
144
|
//=============================================================================
|
|
130
145
|
// Tool definitions with their handler mappings
|
|
131
|
-
export const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
{
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
{
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
{
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
{
|
|
146
|
+
export const tools = [
|
|
147
|
+
{
|
|
148
|
+
definition: createTaskTool,
|
|
149
|
+
handler: createTaskHandler
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
definition: updateTaskTool,
|
|
153
|
+
handler: updateTaskHandler
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
definition: moveTaskTool,
|
|
157
|
+
handler: moveTaskHandler
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
definition: duplicateTaskTool,
|
|
161
|
+
handler: duplicateTaskHandler
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
definition: getTaskTool,
|
|
165
|
+
handler: getTaskHandler
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
definition: getTasksTool,
|
|
169
|
+
handler: getTasksHandler
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
definition: getTaskCommentsTool,
|
|
173
|
+
handler: getTaskCommentsHandler
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
definition: createTaskCommentTool,
|
|
177
|
+
handler: createTaskCommentHandler
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
definition: deleteTaskTool,
|
|
181
|
+
handler: deleteTaskHandler
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
definition: getWorkspaceTasksTool,
|
|
185
|
+
handler: getWorkspaceTasksHandler
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
definition: createBulkTasksTool,
|
|
189
|
+
handler: async (params) => {
|
|
190
|
+
const result = await createBulkTasksHandler(params);
|
|
191
|
+
return {
|
|
192
|
+
successful: result.successful,
|
|
193
|
+
failed: result.failed,
|
|
194
|
+
count: result.totals.total,
|
|
195
|
+
success_count: result.totals.success,
|
|
196
|
+
failure_count: result.totals.failure,
|
|
197
|
+
errors: result.failed.map(f => f.error)
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
definition: updateBulkTasksTool,
|
|
203
|
+
handler: async (params) => {
|
|
204
|
+
const result = await updateBulkTasksHandler(params);
|
|
205
|
+
return {
|
|
206
|
+
successful: result.successful,
|
|
207
|
+
failed: result.failed,
|
|
208
|
+
count: result.totals.total,
|
|
209
|
+
success_count: result.totals.success,
|
|
210
|
+
failure_count: result.totals.failure,
|
|
211
|
+
errors: result.failed.map(f => f.error)
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
definition: moveBulkTasksTool,
|
|
217
|
+
handler: async (params) => {
|
|
218
|
+
const result = await moveBulkTasksHandler(params);
|
|
219
|
+
return {
|
|
220
|
+
successful: result.successful,
|
|
221
|
+
failed: result.failed,
|
|
222
|
+
count: result.totals.total,
|
|
223
|
+
success_count: result.totals.success,
|
|
224
|
+
failure_count: result.totals.failure,
|
|
225
|
+
errors: result.failed.map(f => f.error)
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
definition: deleteBulkTasksTool,
|
|
231
|
+
handler: async (params) => {
|
|
232
|
+
const result = await deleteBulkTasksHandler(params);
|
|
233
|
+
return {
|
|
234
|
+
successful: result.successful,
|
|
235
|
+
failed: result.failed,
|
|
236
|
+
count: result.totals.total,
|
|
237
|
+
success_count: result.totals.success,
|
|
238
|
+
failure_count: result.totals.failure,
|
|
239
|
+
errors: result.failed.map(f => f.error)
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
149
243
|
];
|
|
@@ -145,31 +145,39 @@ export const updateTaskTool = {
|
|
|
145
145
|
description: `Purpose: Modify properties of an existing task.
|
|
146
146
|
|
|
147
147
|
Valid Usage:
|
|
148
|
-
1. Use taskId alone (preferred
|
|
149
|
-
2. Use taskName
|
|
148
|
+
1. Use taskId alone (preferred) - works with both regular and custom IDs
|
|
149
|
+
2. Use taskName alone (will search across all lists)
|
|
150
|
+
3. Use taskName + listName (for faster, targeted search)
|
|
150
151
|
|
|
151
152
|
Requirements:
|
|
152
153
|
- At least one update field (name, description, status, priority, dueDate) must be provided
|
|
153
|
-
-
|
|
154
|
+
- EITHER taskId OR taskName: REQUIRED
|
|
155
|
+
- listName: Optional, but recommended when using taskName
|
|
154
156
|
|
|
155
157
|
Notes:
|
|
158
|
+
- The tool automatically searches for tasks using smart name matching
|
|
159
|
+
- When only taskName is provided, it searches across all lists
|
|
160
|
+
- Adding listName narrows the search to a specific list for better performance
|
|
156
161
|
- Only specified fields will be updated
|
|
157
|
-
-
|
|
158
|
-
|
|
162
|
+
- Custom fields can be set using the custom_fields parameter (array of {id, value} objects)
|
|
163
|
+
|
|
164
|
+
Warning:
|
|
165
|
+
- Using taskName without listName may match multiple tasks
|
|
166
|
+
- If multiple matches are found, the operation will fail with a disambiguation error`,
|
|
159
167
|
inputSchema: {
|
|
160
168
|
type: "object",
|
|
161
169
|
properties: {
|
|
162
170
|
taskId: {
|
|
163
171
|
type: "string",
|
|
164
|
-
description: "ID of
|
|
172
|
+
description: "ID of task to update (preferred). Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234')."
|
|
165
173
|
},
|
|
166
174
|
taskName: {
|
|
167
175
|
type: "string",
|
|
168
|
-
description: "Name of
|
|
176
|
+
description: "Name of task to update. The tool will search for tasks with this name across all lists unless listName is specified."
|
|
169
177
|
},
|
|
170
178
|
listName: {
|
|
171
179
|
type: "string",
|
|
172
|
-
description: "Name of
|
|
180
|
+
description: "Optional: Name of list containing the task. Providing this narrows the search to a specific list, improving performance and reducing ambiguity."
|
|
173
181
|
},
|
|
174
182
|
name: {
|
|
175
183
|
type: "string",
|
|
@@ -356,8 +364,7 @@ Note:
|
|
|
356
364
|
type: "boolean",
|
|
357
365
|
description: "Whether to include subtasks in the response. Set to true to retrieve full details of all subtasks."
|
|
358
366
|
}
|
|
359
|
-
}
|
|
360
|
-
required: []
|
|
367
|
+
}
|
|
361
368
|
}
|
|
362
369
|
};
|
|
363
370
|
/**
|
|
@@ -518,12 +525,23 @@ export const deleteTaskTool = {
|
|
|
518
525
|
|
|
519
526
|
Valid Usage:
|
|
520
527
|
1. Use taskId alone (preferred and safest)
|
|
521
|
-
2. Use taskName
|
|
528
|
+
2. Use taskName alone (will search across all lists)
|
|
529
|
+
3. Use taskName + listName (for faster, targeted search)
|
|
530
|
+
|
|
531
|
+
Requirements:
|
|
532
|
+
- EITHER taskId OR taskName: REQUIRED
|
|
533
|
+
- listName: Optional, but recommended when using taskName
|
|
534
|
+
|
|
535
|
+
Notes:
|
|
536
|
+
- The tool automatically searches for tasks using smart name matching
|
|
537
|
+
- When only taskName is provided, it searches across all lists
|
|
538
|
+
- Adding listName narrows the search to a specific list for better performance
|
|
539
|
+
- Supports both regular task IDs and custom IDs (like 'DEV-1234')
|
|
522
540
|
|
|
523
541
|
Warning:
|
|
524
542
|
- This action CANNOT be undone
|
|
525
|
-
- Using taskName
|
|
526
|
-
-
|
|
543
|
+
- Using taskName without listName may match multiple tasks
|
|
544
|
+
- If multiple matches are found, the operation will fail with a disambiguation error`,
|
|
527
545
|
inputSchema: {
|
|
528
546
|
type: "object",
|
|
529
547
|
properties: {
|
|
@@ -533,11 +551,11 @@ Warning:
|
|
|
533
551
|
},
|
|
534
552
|
taskName: {
|
|
535
553
|
type: "string",
|
|
536
|
-
description: "Name of task to delete.
|
|
554
|
+
description: "Name of task to delete. The tool will search for tasks with this name across all lists unless listName is specified."
|
|
537
555
|
},
|
|
538
556
|
listName: {
|
|
539
557
|
type: "string",
|
|
540
|
-
description: "Name of list containing the task.
|
|
558
|
+
description: "Optional: Name of list containing the task. Providing this narrows the search to a specific list, improving performance and reducing ambiguity."
|
|
541
559
|
}
|
|
542
560
|
}
|
|
543
561
|
}
|
|
@@ -117,12 +117,31 @@ export function validateTaskUpdateData(updateData) {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
/**
|
|
120
|
-
* Validate bulk
|
|
120
|
+
* Validate bulk task array and task identification
|
|
121
|
+
* @param tasks Array of tasks to validate
|
|
122
|
+
* @param operation The bulk operation type ('create', 'update', 'move', 'delete')
|
|
121
123
|
*/
|
|
122
|
-
export function validateBulkTasks(tasks) {
|
|
123
|
-
if (!
|
|
124
|
-
throw new Error(
|
|
124
|
+
export function validateBulkTasks(tasks, operation = 'update') {
|
|
125
|
+
if (!Array.isArray(tasks) || tasks.length === 0) {
|
|
126
|
+
throw new Error("tasks must be a non-empty array");
|
|
125
127
|
}
|
|
128
|
+
tasks.forEach((task, index) => {
|
|
129
|
+
if (!task || typeof task !== 'object') {
|
|
130
|
+
throw new Error(`Task at index ${index} must be an object`);
|
|
131
|
+
}
|
|
132
|
+
// Skip task identification validation for create operations
|
|
133
|
+
if (operation === 'create') {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// For bulk operations, require listName when using taskName
|
|
137
|
+
if (task.taskName && !task.listName) {
|
|
138
|
+
throw new Error(`Task at index ${index} using taskName must also provide listName`);
|
|
139
|
+
}
|
|
140
|
+
// At least one identifier is required for non-create operations
|
|
141
|
+
if (!task.taskId && !task.taskName && !task.customTaskId) {
|
|
142
|
+
throw new Error(`Task at index ${index} must provide either taskId, taskName + listName, or customTaskId`);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
126
145
|
}
|
|
127
146
|
/**
|
|
128
147
|
* Parse options for bulk operations
|
package/package.json
CHANGED