@taazkareem/clickup-mcp-server 0.8.4 → 0.9.0
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/LICENSE +9 -17
- package/README.md +33 -38
- package/build/enhanced_server.js +262 -0
- package/build/index.js +9 -3
- package/build/license.js +172 -0
- package/build/middleware/auth.js +211 -0
- package/build/routes/auth.js +306 -0
- package/build/schemas/member.js +13 -0
- package/build/server.js +15 -1
- package/build/server.log +15 -0
- package/build/services/auth/oauth2.js +236 -0
- package/build/services/auth/session.js +337 -0
- package/build/services/clickup/adapter.js +281 -0
- package/build/services/clickup/factory.js +339 -0
- package/build/services/clickup/task/task-attachments.js +20 -12
- package/build/services/clickup/task/task-comments.js +19 -9
- package/build/services/clickup/task/task-core.js +68 -4
- package/build/services/clickup/task/task-custom-fields.js +23 -13
- package/build/services/clickup/task/task-search.js +79 -71
- package/build/services/clickup/task/task-service.js +88 -9
- package/build/services/clickup/task/task-tags.js +25 -13
- package/build/sse_server.js +4 -4
- package/build/tools/documents.js +11 -4
- package/build/tools/health.js +23 -0
- package/build/tools/member.js +2 -4
- package/build/tools/task/bulk-operations.js +5 -5
- package/build/tools/task/handlers.js +62 -12
- package/build/tools/task/single-operations.js +9 -9
- package/build/tools/task/time-tracking.js +61 -170
- package/build/tools/task/utilities.js +56 -22
- package/build/utils/date-utils.js +341 -141
- package/build/utils/schema-compatibility.js +222 -0
- package/build/utils/universal-schema-compatibility.js +171 -0
- package/build/virtual-sdk/generator.js +53 -0
- package/build/virtual-sdk/registry.js +45 -0
- package/package.json +2 -2
|
@@ -7,12 +7,20 @@
|
|
|
7
7
|
* Handles comment operations for ClickUp tasks, including:
|
|
8
8
|
* - Retrieving comments for a task
|
|
9
9
|
* - Creating comments on a task
|
|
10
|
+
*
|
|
11
|
+
* REFACTORED: Now uses composition instead of inheritance.
|
|
12
|
+
* Only depends on TaskServiceCore for base functionality.
|
|
10
13
|
*/
|
|
11
|
-
import { TaskServiceAttachments } from './task-attachments.js';
|
|
12
14
|
/**
|
|
13
15
|
* Comments functionality for the TaskService
|
|
16
|
+
*
|
|
17
|
+
* This service handles all comment-related operations for ClickUp tasks.
|
|
18
|
+
* It uses composition to access core functionality instead of inheritance.
|
|
14
19
|
*/
|
|
15
|
-
export class TaskServiceComments
|
|
20
|
+
export class TaskServiceComments {
|
|
21
|
+
constructor(core) {
|
|
22
|
+
this.core = core;
|
|
23
|
+
}
|
|
16
24
|
/**
|
|
17
25
|
* Get all comments for a task
|
|
18
26
|
*
|
|
@@ -22,7 +30,7 @@ export class TaskServiceComments extends TaskServiceAttachments {
|
|
|
22
30
|
* @returns Array of task comments
|
|
23
31
|
*/
|
|
24
32
|
async getTaskComments(taskId, start, startId) {
|
|
25
|
-
this.logOperation('getTaskComments', { taskId, start, startId });
|
|
33
|
+
this.core.logOperation('getTaskComments', { taskId, start, startId });
|
|
26
34
|
try {
|
|
27
35
|
// Build query parameters for pagination
|
|
28
36
|
const queryParams = new URLSearchParams();
|
|
@@ -33,11 +41,13 @@ export class TaskServiceComments extends TaskServiceAttachments {
|
|
|
33
41
|
queryParams.append('start_id', startId);
|
|
34
42
|
}
|
|
35
43
|
const queryString = queryParams.toString() ? `?${queryParams.toString()}` : '';
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
return await this.core.makeRequest(async () => {
|
|
45
|
+
const response = await this.core.client.get(`/task/${taskId}/comment${queryString}`);
|
|
46
|
+
return response.data.comments || [];
|
|
47
|
+
});
|
|
38
48
|
}
|
|
39
49
|
catch (error) {
|
|
40
|
-
throw this.handleError(error, 'Failed to get task comments');
|
|
50
|
+
throw this.core.handleError(error, 'Failed to get task comments');
|
|
41
51
|
}
|
|
42
52
|
}
|
|
43
53
|
/**
|
|
@@ -50,7 +60,7 @@ export class TaskServiceComments extends TaskServiceAttachments {
|
|
|
50
60
|
* @returns The created comment
|
|
51
61
|
*/
|
|
52
62
|
async createTaskComment(taskId, commentText, notifyAll = false, assignee) {
|
|
53
|
-
this.logOperation('createTaskComment', { taskId, commentText, notifyAll, assignee });
|
|
63
|
+
this.core.logOperation('createTaskComment', { taskId, commentText, notifyAll, assignee });
|
|
54
64
|
try {
|
|
55
65
|
const payload = {
|
|
56
66
|
comment_text: commentText,
|
|
@@ -60,7 +70,7 @@ export class TaskServiceComments extends TaskServiceAttachments {
|
|
|
60
70
|
payload.assignee = assignee;
|
|
61
71
|
}
|
|
62
72
|
// Make the request directly without using makeRequest for better error handling
|
|
63
|
-
const response = await this.client.post(`/task/${taskId}/comment`, payload);
|
|
73
|
+
const response = await this.core.client.post(`/task/${taskId}/comment`, payload);
|
|
64
74
|
// Handle different response formats from ClickUp API
|
|
65
75
|
if (response.data) {
|
|
66
76
|
if (response.data.comment) {
|
|
@@ -98,7 +108,7 @@ export class TaskServiceComments extends TaskServiceAttachments {
|
|
|
98
108
|
resolved: false
|
|
99
109
|
};
|
|
100
110
|
}
|
|
101
|
-
throw this.handleError(error, 'Failed to create task comment');
|
|
111
|
+
throw this.core.handleError(error, 'Failed to create task comment');
|
|
102
112
|
}
|
|
103
113
|
}
|
|
104
114
|
}
|
|
@@ -179,11 +179,29 @@ export class TaskServiceCore extends BaseClickUpService {
|
|
|
179
179
|
}
|
|
180
180
|
/**
|
|
181
181
|
* Get a task by its ID
|
|
182
|
-
*
|
|
182
|
+
* Automatically detects custom task IDs and routes them appropriately
|
|
183
|
+
* @param taskId The ID of the task to retrieve (regular or custom)
|
|
183
184
|
* @returns The task
|
|
184
185
|
*/
|
|
185
186
|
async getTask(taskId) {
|
|
186
187
|
this.logOperation('getTask', { taskId });
|
|
188
|
+
// Import the detection function here to avoid circular dependencies
|
|
189
|
+
const { isCustomTaskId } = await import('../../../tools/task/utilities.js');
|
|
190
|
+
// Test the detection function
|
|
191
|
+
const isCustom = isCustomTaskId(taskId);
|
|
192
|
+
this.logger.debug('Custom task ID detection result', {
|
|
193
|
+
taskId,
|
|
194
|
+
isCustom,
|
|
195
|
+
taskIdLength: taskId.length,
|
|
196
|
+
containsHyphen: taskId.includes('-'),
|
|
197
|
+
containsUnderscore: taskId.includes('_')
|
|
198
|
+
});
|
|
199
|
+
// Automatically detect custom task IDs and route to appropriate method
|
|
200
|
+
if (isCustom) {
|
|
201
|
+
this.logger.debug('Detected custom task ID, routing to getTaskByCustomId', { taskId });
|
|
202
|
+
return this.getTaskByCustomId(taskId);
|
|
203
|
+
}
|
|
204
|
+
this.logger.debug('Detected regular task ID, using standard getTask flow', { taskId });
|
|
187
205
|
try {
|
|
188
206
|
return await this.makeRequest(async () => {
|
|
189
207
|
const response = await this.client.get(`/task/${taskId}`);
|
|
@@ -196,6 +214,14 @@ export class TaskServiceCore extends BaseClickUpService {
|
|
|
196
214
|
});
|
|
197
215
|
}
|
|
198
216
|
catch (error) {
|
|
217
|
+
// If this was detected as a regular task ID but failed, provide helpful error message
|
|
218
|
+
// suggesting it might be a custom ID that wasn't properly detected
|
|
219
|
+
if (error instanceof ClickUpServiceError && error.code === ErrorCode.NOT_FOUND) {
|
|
220
|
+
const { isCustomTaskId } = await import('../../../tools/task/utilities.js');
|
|
221
|
+
if (!isCustomTaskId(taskId) && (taskId.includes('-') || taskId.includes('_'))) {
|
|
222
|
+
throw new ClickUpServiceError(`Task ${taskId} not found. If this is a custom task ID, ensure your workspace has custom task IDs enabled and you have access to the task.`, ErrorCode.NOT_FOUND, error.data);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
199
225
|
throw this.handleError(error, `Failed to get task ${taskId}`);
|
|
200
226
|
}
|
|
201
227
|
}
|
|
@@ -259,6 +285,14 @@ export class TaskServiceCore extends BaseClickUpService {
|
|
|
259
285
|
custom_task_ids: 'true',
|
|
260
286
|
team_id: this.teamId // team_id is required when custom_task_ids is true
|
|
261
287
|
});
|
|
288
|
+
// Debug logging for troubleshooting
|
|
289
|
+
this.logger.debug('Making custom task ID API request', {
|
|
290
|
+
customTaskId,
|
|
291
|
+
url,
|
|
292
|
+
teamId: this.teamId,
|
|
293
|
+
params: params.toString(),
|
|
294
|
+
fullUrl: `${url}?${params.toString()}`
|
|
295
|
+
});
|
|
262
296
|
// Note: The ClickUp API documentation for GET /task/{task_id} doesn't explicitly mention
|
|
263
297
|
// filtering by list_id when custom_task_ids=true. This parameter might be ignored.
|
|
264
298
|
if (listId) {
|
|
@@ -276,6 +310,13 @@ export class TaskServiceCore extends BaseClickUpService {
|
|
|
276
310
|
});
|
|
277
311
|
}
|
|
278
312
|
catch (error) {
|
|
313
|
+
// Enhanced error logging for debugging
|
|
314
|
+
this.logger.error('Custom task ID request failed', {
|
|
315
|
+
customTaskId,
|
|
316
|
+
teamId: this.teamId,
|
|
317
|
+
error: error instanceof Error ? error.message : String(error),
|
|
318
|
+
errorDetails: error
|
|
319
|
+
});
|
|
279
320
|
// Provide more specific error context if possible
|
|
280
321
|
if (error instanceof ClickUpServiceError && error.code === ErrorCode.NOT_FOUND) {
|
|
281
322
|
throw new ClickUpServiceError(`Task with custom ID ${customTaskId} not found or not accessible for team ${this.teamId}.`, ErrorCode.NOT_FOUND, error.data);
|
|
@@ -292,11 +333,34 @@ export class TaskServiceCore extends BaseClickUpService {
|
|
|
292
333
|
async updateTask(taskId, updateData) {
|
|
293
334
|
this.logOperation('updateTask', { taskId, ...updateData });
|
|
294
335
|
try {
|
|
295
|
-
// Extract custom fields from updateData
|
|
296
|
-
const { custom_fields, ...standardFields } = updateData;
|
|
336
|
+
// Extract custom fields and assignees from updateData
|
|
337
|
+
const { custom_fields, assignees, ...standardFields } = updateData;
|
|
338
|
+
// Prepare the fields to send to API
|
|
339
|
+
let fieldsToSend = { ...standardFields };
|
|
340
|
+
// Handle assignees separately if provided
|
|
341
|
+
if (assignees !== undefined) {
|
|
342
|
+
// Get current task to compare assignees
|
|
343
|
+
const currentTask = await this.getTask(taskId);
|
|
344
|
+
const currentAssigneeIds = currentTask.assignees.map(a => a.id);
|
|
345
|
+
let assigneesToProcess;
|
|
346
|
+
if (Array.isArray(assignees)) {
|
|
347
|
+
// If assignees is an array, calculate add/rem based on current vs new
|
|
348
|
+
const newAssigneeIds = assignees;
|
|
349
|
+
assigneesToProcess = {
|
|
350
|
+
add: newAssigneeIds.filter(id => !currentAssigneeIds.includes(id)),
|
|
351
|
+
rem: currentAssigneeIds.filter(id => !newAssigneeIds.includes(id))
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
// If assignees is already in add/rem format, use it directly
|
|
356
|
+
assigneesToProcess = assignees;
|
|
357
|
+
}
|
|
358
|
+
// Add assignees to the fields in the correct format
|
|
359
|
+
fieldsToSend.assignees = assigneesToProcess;
|
|
360
|
+
}
|
|
297
361
|
// First update the standard fields
|
|
298
362
|
const updatedTask = await this.makeRequest(async () => {
|
|
299
|
-
const response = await this.client.put(`/task/${taskId}`,
|
|
363
|
+
const response = await this.client.put(`/task/${taskId}`, fieldsToSend);
|
|
300
364
|
// Handle both JSON and text responses
|
|
301
365
|
const data = response.data;
|
|
302
366
|
if (typeof data === 'string') {
|
|
@@ -7,12 +7,20 @@
|
|
|
7
7
|
* Handles custom fields operations for ClickUp tasks, including:
|
|
8
8
|
* - Setting custom field values
|
|
9
9
|
* - Retrieving custom field values
|
|
10
|
+
*
|
|
11
|
+
* REFACTORED: Now uses composition instead of inheritance.
|
|
12
|
+
* Only depends on TaskServiceCore for getTask() and base functionality.
|
|
10
13
|
*/
|
|
11
|
-
import { TaskServiceTags } from './task-tags.js';
|
|
12
14
|
/**
|
|
13
15
|
* Custom fields functionality for the TaskService
|
|
16
|
+
*
|
|
17
|
+
* This service handles all custom field operations for ClickUp tasks.
|
|
18
|
+
* It uses composition to access core functionality instead of inheritance.
|
|
14
19
|
*/
|
|
15
|
-
export class TaskServiceCustomFields
|
|
20
|
+
export class TaskServiceCustomFields {
|
|
21
|
+
constructor(core) {
|
|
22
|
+
this.core = core;
|
|
23
|
+
}
|
|
16
24
|
/**
|
|
17
25
|
* Set a single custom field value on a task
|
|
18
26
|
*
|
|
@@ -22,16 +30,18 @@ export class TaskServiceCustomFields extends TaskServiceTags {
|
|
|
22
30
|
* @returns Success response
|
|
23
31
|
*/
|
|
24
32
|
async setCustomFieldValue(taskId, fieldId, value) {
|
|
25
|
-
this.logOperation('setCustomFieldValue', { taskId, fieldId, value });
|
|
33
|
+
this.core.logOperation('setCustomFieldValue', { taskId, fieldId, value });
|
|
26
34
|
try {
|
|
27
35
|
const payload = {
|
|
28
36
|
value
|
|
29
37
|
};
|
|
30
|
-
await this.
|
|
38
|
+
await this.core.makeRequest(async () => {
|
|
39
|
+
return await this.core.client.post(`/task/${taskId}/field/${fieldId}`, payload);
|
|
40
|
+
});
|
|
31
41
|
return true;
|
|
32
42
|
}
|
|
33
43
|
catch (error) {
|
|
34
|
-
throw this.handleError(error, `Failed to set custom field "${fieldId}" value`);
|
|
44
|
+
throw this.core.handleError(error, `Failed to set custom field "${fieldId}" value`);
|
|
35
45
|
}
|
|
36
46
|
}
|
|
37
47
|
/**
|
|
@@ -42,7 +52,7 @@ export class TaskServiceCustomFields extends TaskServiceTags {
|
|
|
42
52
|
* @returns Success response
|
|
43
53
|
*/
|
|
44
54
|
async setCustomFieldValues(taskId, customFields) {
|
|
45
|
-
this.logOperation('setCustomFieldValues', { taskId, customFields });
|
|
55
|
+
this.core.logOperation('setCustomFieldValues', { taskId, customFields });
|
|
46
56
|
try {
|
|
47
57
|
// Execute each update sequentially
|
|
48
58
|
for (const field of customFields) {
|
|
@@ -51,7 +61,7 @@ export class TaskServiceCustomFields extends TaskServiceTags {
|
|
|
51
61
|
return true;
|
|
52
62
|
}
|
|
53
63
|
catch (error) {
|
|
54
|
-
throw this.handleError(error, 'Failed to set custom field values');
|
|
64
|
+
throw this.core.handleError(error, 'Failed to set custom field values');
|
|
55
65
|
}
|
|
56
66
|
}
|
|
57
67
|
/**
|
|
@@ -61,14 +71,14 @@ export class TaskServiceCustomFields extends TaskServiceTags {
|
|
|
61
71
|
* @returns Record mapping field IDs to their values
|
|
62
72
|
*/
|
|
63
73
|
async getCustomFieldValues(taskId) {
|
|
64
|
-
this.logOperation('getCustomFieldValues', { taskId });
|
|
74
|
+
this.core.logOperation('getCustomFieldValues', { taskId });
|
|
65
75
|
try {
|
|
66
76
|
// We need to fetch the full task to get its custom fields
|
|
67
|
-
const task = await this.getTask(taskId);
|
|
77
|
+
const task = await this.core.getTask(taskId);
|
|
68
78
|
return task.custom_fields || {};
|
|
69
79
|
}
|
|
70
80
|
catch (error) {
|
|
71
|
-
throw this.handleError(error, 'Failed to get custom field values');
|
|
81
|
+
throw this.core.handleError(error, 'Failed to get custom field values');
|
|
72
82
|
}
|
|
73
83
|
}
|
|
74
84
|
/**
|
|
@@ -80,18 +90,18 @@ export class TaskServiceCustomFields extends TaskServiceTags {
|
|
|
80
90
|
* @throws ClickUpServiceError if the field doesn't exist
|
|
81
91
|
*/
|
|
82
92
|
async getCustomFieldValue(taskId, fieldId) {
|
|
83
|
-
this.logOperation('getCustomFieldValue', { taskId, fieldId });
|
|
93
|
+
this.core.logOperation('getCustomFieldValue', { taskId, fieldId });
|
|
84
94
|
try {
|
|
85
95
|
const customFields = await this.getCustomFieldValues(taskId);
|
|
86
96
|
if (fieldId in customFields) {
|
|
87
97
|
return customFields[fieldId];
|
|
88
98
|
}
|
|
89
99
|
else {
|
|
90
|
-
throw this.handleError(new Error(`Custom field "${fieldId}" not found on task`), `Custom field "${fieldId}" not found on task`);
|
|
100
|
+
throw this.core.handleError(new Error(`Custom field "${fieldId}" not found on task`), `Custom field "${fieldId}" not found on task`);
|
|
91
101
|
}
|
|
92
102
|
}
|
|
93
103
|
catch (error) {
|
|
94
|
-
throw this.handleError(error, `Failed to get custom field "${fieldId}" value`);
|
|
104
|
+
throw this.core.handleError(error, `Failed to get custom field "${fieldId}" value`);
|
|
95
105
|
}
|
|
96
106
|
}
|
|
97
107
|
}
|