@taazkareem/clickup-mcp-server 0.5.1 → 0.6.1
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 +29 -38
- package/build/config.js +33 -2
- package/build/index.js +16 -29
- package/build/logger.js +8 -11
- package/build/server.js +33 -8
- package/build/services/clickup/base.js +3 -0
- package/build/services/clickup/bulk.js +3 -0
- package/build/services/clickup/folder.js +3 -0
- package/build/services/clickup/index.js +9 -1
- package/build/services/clickup/list.js +3 -0
- package/build/services/clickup/tag.js +190 -0
- package/build/services/clickup/task.js +165 -2
- package/build/services/clickup/types.js +3 -0
- package/build/services/clickup/workspace.js +3 -0
- package/build/services/shared.js +3 -0
- package/build/tools/folder.js +3 -0
- package/build/tools/index.js +4 -0
- package/build/tools/list.js +3 -0
- package/build/tools/tag.js +824 -0
- package/build/tools/task/attachments.js +3 -0
- package/build/tools/task/bulk-operations.js +10 -0
- package/build/tools/task/handlers.js +76 -10
- package/build/tools/task/index.js +8 -1
- package/build/tools/task/main.js +18 -2
- package/build/tools/task/single-operations.js +64 -46
- package/build/tools/task/utilities.js +40 -3
- package/build/tools/task/workspace-operations.js +222 -0
- package/build/tools/utils.js +3 -0
- package/build/tools/workspace.js +9 -46
- package/build/utils/color-processor.js +183 -0
- package/build/utils/concurrency-utils.js +3 -0
- package/build/utils/date-utils.js +3 -0
- package/build/utils/resolver-utils.js +3 -0
- package/build/utils/sponsor-service.js +5 -2
- package/build/utils/token-utils.js +49 -0
- package/package.json +1 -1
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
2
5
|
* ClickUp Task Service
|
|
3
6
|
*
|
|
4
7
|
* Handles all operations related to tasks in ClickUp, including:
|
|
@@ -10,6 +13,7 @@
|
|
|
10
13
|
*/
|
|
11
14
|
import { BaseClickUpService, ErrorCode, ClickUpServiceError } from './base.js';
|
|
12
15
|
import { ListService } from './list.js';
|
|
16
|
+
import { estimateTokensFromObject, wouldExceedTokenLimit } from '../../utils/token-utils.js';
|
|
13
17
|
export class TaskService extends BaseClickUpService {
|
|
14
18
|
constructor(apiKey, teamId, baseUrl, workspaceService) {
|
|
15
19
|
super(apiKey, teamId, baseUrl);
|
|
@@ -59,6 +63,27 @@ export class TaskService extends BaseClickUpService {
|
|
|
59
63
|
if (filters.assignees && filters.assignees.length > 0) {
|
|
60
64
|
filters.assignees.forEach(assignee => params.append('assignees[]', assignee));
|
|
61
65
|
}
|
|
66
|
+
// Team tasks endpoint specific parameters
|
|
67
|
+
if (filters.tags && filters.tags.length > 0) {
|
|
68
|
+
filters.tags.forEach(tag => params.append('tags[]', tag));
|
|
69
|
+
}
|
|
70
|
+
if (filters.list_ids && filters.list_ids.length > 0) {
|
|
71
|
+
filters.list_ids.forEach(id => params.append('list_ids[]', id));
|
|
72
|
+
}
|
|
73
|
+
if (filters.folder_ids && filters.folder_ids.length > 0) {
|
|
74
|
+
filters.folder_ids.forEach(id => params.append('folder_ids[]', id));
|
|
75
|
+
}
|
|
76
|
+
if (filters.space_ids && filters.space_ids.length > 0) {
|
|
77
|
+
filters.space_ids.forEach(id => params.append('space_ids[]', id));
|
|
78
|
+
}
|
|
79
|
+
if (filters.archived !== undefined)
|
|
80
|
+
params.append('archived', String(filters.archived));
|
|
81
|
+
if (filters.include_closed_lists !== undefined)
|
|
82
|
+
params.append('include_closed_lists', String(filters.include_closed_lists));
|
|
83
|
+
if (filters.include_archived_lists !== undefined)
|
|
84
|
+
params.append('include_archived_lists', String(filters.include_archived_lists));
|
|
85
|
+
if (filters.include_compact_time_entries !== undefined)
|
|
86
|
+
params.append('include_compact_time_entries', String(filters.include_compact_time_entries));
|
|
62
87
|
// Date filters
|
|
63
88
|
if (filters.due_date_gt)
|
|
64
89
|
params.append('due_date_gt', String(filters.due_date_gt));
|
|
@@ -204,6 +229,28 @@ export class TaskService extends BaseClickUpService {
|
|
|
204
229
|
throw this.handleError(error, `Failed to get task ${taskId}`);
|
|
205
230
|
}
|
|
206
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Get subtasks of a specific task
|
|
234
|
+
* @param taskId The ID of the parent task
|
|
235
|
+
* @returns Array of subtask details
|
|
236
|
+
*/
|
|
237
|
+
async getSubtasks(taskId) {
|
|
238
|
+
this.logOperation('getSubtasks', { taskId });
|
|
239
|
+
try {
|
|
240
|
+
return await this.makeRequest(async () => {
|
|
241
|
+
// First, get the task to get its list ID
|
|
242
|
+
const task = await this.getTask(taskId);
|
|
243
|
+
const listId = task.list.id;
|
|
244
|
+
// Then get all tasks from the list
|
|
245
|
+
const allTasks = await this.getTasks(listId, { subtasks: true });
|
|
246
|
+
// Filter tasks that have the specified task as parent
|
|
247
|
+
return allTasks.filter(t => t.parent === taskId || t.top_level_parent === taskId);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
throw this.handleError(error, `Failed to get subtasks of task ${taskId}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
207
254
|
/**
|
|
208
255
|
* Get a specific task by its custom ID
|
|
209
256
|
* @param customTaskId The custom ID of the task (e.g., "DEV-1234")
|
|
@@ -214,8 +261,11 @@ export class TaskService extends BaseClickUpService {
|
|
|
214
261
|
this.logOperation('getTaskByCustomId', { customTaskId, listId });
|
|
215
262
|
try {
|
|
216
263
|
return await this.makeRequest(async () => {
|
|
217
|
-
// Build query with custom_task_ids=true
|
|
218
|
-
const params = new URLSearchParams({
|
|
264
|
+
// Build query with custom_task_ids=true and team_id
|
|
265
|
+
const params = new URLSearchParams({
|
|
266
|
+
custom_task_ids: 'true',
|
|
267
|
+
team_id: this.teamId
|
|
268
|
+
});
|
|
219
269
|
// Use the ClickUp API endpoint for retrieving tasks by ID
|
|
220
270
|
// With custom_task_ids=true parameter, it will treat the ID as a custom ID
|
|
221
271
|
const response = await this.client.get(`/task/${customTaskId}?${params.toString()}`);
|
|
@@ -535,4 +585,117 @@ export class TaskService extends BaseClickUpService {
|
|
|
535
585
|
throw this.handleError(error, `Failed to upload attachment from URL to task ${taskId}`);
|
|
536
586
|
}
|
|
537
587
|
}
|
|
588
|
+
/**
|
|
589
|
+
* Format task data for summary view
|
|
590
|
+
* @param task The task to format
|
|
591
|
+
* @returns TaskSummary object
|
|
592
|
+
*/
|
|
593
|
+
formatTaskSummary(task) {
|
|
594
|
+
return {
|
|
595
|
+
id: task.id,
|
|
596
|
+
name: task.name,
|
|
597
|
+
status: task.status.status,
|
|
598
|
+
list: {
|
|
599
|
+
id: task.list.id,
|
|
600
|
+
name: task.list.name
|
|
601
|
+
},
|
|
602
|
+
due_date: task.due_date,
|
|
603
|
+
url: task.url,
|
|
604
|
+
priority: this.extractPriorityValue(task),
|
|
605
|
+
tags: task.tags.map(tag => ({
|
|
606
|
+
name: tag.name,
|
|
607
|
+
tag_bg: tag.tag_bg,
|
|
608
|
+
tag_fg: tag.tag_fg
|
|
609
|
+
}))
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Estimates token count for a task in JSON format
|
|
614
|
+
* @param task ClickUp task
|
|
615
|
+
* @returns Estimated token count
|
|
616
|
+
*/
|
|
617
|
+
estimateTaskTokens(task) {
|
|
618
|
+
return estimateTokensFromObject(task);
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Get filtered tasks across the entire team/workspace using tags and other filters
|
|
622
|
+
* @param filters Task filters to apply including tags, list/folder/space filtering
|
|
623
|
+
* @returns Either a DetailedTaskResponse or WorkspaceTasksResponse depending on detail_level
|
|
624
|
+
*/
|
|
625
|
+
async getWorkspaceTasks(filters = {}) {
|
|
626
|
+
try {
|
|
627
|
+
this.logOperation('getWorkspaceTasks', { filters });
|
|
628
|
+
const params = this.buildTaskFilterParams(filters);
|
|
629
|
+
const response = await this.client.get(`/team/${this.teamId}/task`, {
|
|
630
|
+
params
|
|
631
|
+
});
|
|
632
|
+
const tasks = response.data.tasks;
|
|
633
|
+
const totalCount = tasks.length; // Note: This is just the current page count
|
|
634
|
+
const hasMore = totalCount === 100; // ClickUp returns max 100 tasks per page
|
|
635
|
+
const nextPage = (filters.page || 0) + 1;
|
|
636
|
+
// If the estimated token count exceeds 50,000 or detail_level is 'summary',
|
|
637
|
+
// return summary format for efficiency and to avoid hitting token limits
|
|
638
|
+
const TOKEN_LIMIT = 50000;
|
|
639
|
+
// Estimate tokens for the full response
|
|
640
|
+
let tokensExceedLimit = false;
|
|
641
|
+
if (filters.detail_level !== 'summary' && tasks.length > 0) {
|
|
642
|
+
// We only need to check token count if detailed was requested
|
|
643
|
+
// For summary requests, we always return summary format
|
|
644
|
+
// First check with a sample task - if one task exceeds the limit, we definitely need summary
|
|
645
|
+
const sampleTask = tasks[0];
|
|
646
|
+
// Check if all tasks would exceed the token limit
|
|
647
|
+
const estimatedTokensPerTask = this.estimateTaskTokens(sampleTask);
|
|
648
|
+
const estimatedTotalTokens = estimatedTokensPerTask * tasks.length;
|
|
649
|
+
// Add 10% overhead for the response wrapper
|
|
650
|
+
tokensExceedLimit = estimatedTotalTokens * 1.1 > TOKEN_LIMIT;
|
|
651
|
+
// Double-check with more precise estimation if we're close to the limit
|
|
652
|
+
if (!tokensExceedLimit && estimatedTotalTokens * 1.1 > TOKEN_LIMIT * 0.8) {
|
|
653
|
+
// More precise check - build a representative sample and extrapolate
|
|
654
|
+
tokensExceedLimit = wouldExceedTokenLimit({ tasks, total_count: totalCount, has_more: hasMore, next_page: nextPage }, TOKEN_LIMIT);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
// Determine if we should return summary or detailed based on request and token limit
|
|
658
|
+
const shouldUseSummary = filters.detail_level === 'summary' || tokensExceedLimit;
|
|
659
|
+
this.logOperation('getWorkspaceTasks', {
|
|
660
|
+
totalTasks: tasks.length,
|
|
661
|
+
estimatedTokens: tasks.reduce((count, task) => count + this.estimateTaskTokens(task), 0),
|
|
662
|
+
usingDetailedFormat: !shouldUseSummary,
|
|
663
|
+
requestedFormat: filters.detail_level || 'auto'
|
|
664
|
+
});
|
|
665
|
+
if (shouldUseSummary) {
|
|
666
|
+
return {
|
|
667
|
+
summaries: tasks.map(task => this.formatTaskSummary(task)),
|
|
668
|
+
total_count: totalCount,
|
|
669
|
+
has_more: hasMore,
|
|
670
|
+
next_page: nextPage
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
return {
|
|
674
|
+
tasks,
|
|
675
|
+
total_count: totalCount,
|
|
676
|
+
has_more: hasMore,
|
|
677
|
+
next_page: nextPage
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
catch (error) {
|
|
681
|
+
this.logOperation('getWorkspaceTasks', { error: error.message, status: error.response?.status });
|
|
682
|
+
throw this.handleError(error, 'Failed to get workspace tasks');
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Get task summaries for lightweight retrieval
|
|
687
|
+
* @param filters Task filters to apply
|
|
688
|
+
* @returns WorkspaceTasksResponse with task summaries
|
|
689
|
+
*/
|
|
690
|
+
async getTaskSummaries(filters = {}) {
|
|
691
|
+
return this.getWorkspaceTasks({ ...filters, detail_level: 'summary' });
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Get detailed task data
|
|
695
|
+
* @param filters Task filters to apply
|
|
696
|
+
* @returns DetailedTaskResponse with full task data
|
|
697
|
+
*/
|
|
698
|
+
async getTaskDetails(filters = {}) {
|
|
699
|
+
return this.getWorkspaceTasks({ ...filters, detail_level: 'detailed' });
|
|
700
|
+
}
|
|
538
701
|
}
|
package/build/services/shared.js
CHANGED
package/build/tools/folder.js
CHANGED
package/build/tools/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
2
5
|
* Tools Index
|
|
3
6
|
*
|
|
4
7
|
* This file exports all tool definitions and handlers for the ClickUp MCP server.
|
|
@@ -9,3 +12,4 @@ export * from './workspace.js';
|
|
|
9
12
|
export * from './task/index.js';
|
|
10
13
|
export * from './list.js';
|
|
11
14
|
export * from './folder.js';
|
|
15
|
+
export * from './tag.js';
|