@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.
Files changed (36) hide show
  1. package/README.md +29 -38
  2. package/build/config.js +33 -2
  3. package/build/index.js +16 -29
  4. package/build/logger.js +8 -11
  5. package/build/server.js +33 -8
  6. package/build/services/clickup/base.js +3 -0
  7. package/build/services/clickup/bulk.js +3 -0
  8. package/build/services/clickup/folder.js +3 -0
  9. package/build/services/clickup/index.js +9 -1
  10. package/build/services/clickup/list.js +3 -0
  11. package/build/services/clickup/tag.js +190 -0
  12. package/build/services/clickup/task.js +165 -2
  13. package/build/services/clickup/types.js +3 -0
  14. package/build/services/clickup/workspace.js +3 -0
  15. package/build/services/shared.js +3 -0
  16. package/build/tools/folder.js +3 -0
  17. package/build/tools/index.js +4 -0
  18. package/build/tools/list.js +3 -0
  19. package/build/tools/tag.js +824 -0
  20. package/build/tools/task/attachments.js +3 -0
  21. package/build/tools/task/bulk-operations.js +10 -0
  22. package/build/tools/task/handlers.js +76 -10
  23. package/build/tools/task/index.js +8 -1
  24. package/build/tools/task/main.js +18 -2
  25. package/build/tools/task/single-operations.js +64 -46
  26. package/build/tools/task/utilities.js +40 -3
  27. package/build/tools/task/workspace-operations.js +222 -0
  28. package/build/tools/utils.js +3 -0
  29. package/build/tools/workspace.js +9 -46
  30. package/build/utils/color-processor.js +183 -0
  31. package/build/utils/concurrency-utils.js +3 -0
  32. package/build/utils/date-utils.js +3 -0
  33. package/build/utils/resolver-utils.js +3 -0
  34. package/build/utils/sponsor-service.js +5 -2
  35. package/build/utils/token-utils.js +49 -0
  36. 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({ custom_task_ids: 'true' });
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
  }
@@ -1,4 +1,7 @@
1
1
  /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
2
5
  * Common type definitions for ClickUp API entities
3
6
  */
4
7
  // Helper function to validate and convert priority values
@@ -1,4 +1,7 @@
1
1
  /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
2
5
  * ClickUp Workspace Service Module
3
6
  *
4
7
  * Handles workspace hierarchy and space-related operations
@@ -1,4 +1,7 @@
1
1
  /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
2
5
  * Shared Services Module
3
6
  *
4
7
  * This module maintains singleton instances of services that should be shared
@@ -1,4 +1,7 @@
1
1
  /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
2
5
  * ClickUp MCP Folder Tools
3
6
  *
4
7
  * This module defines folder-related tools for creating, retrieving,
@@ -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';
@@ -1,4 +1,7 @@
1
1
  /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
2
5
  * ClickUp MCP List Tools
3
6
  *
4
7
  * This module defines list-related tools including creating, updating,