@alanse/clickup-multi-mcp-server 1.0.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.
Files changed (56) hide show
  1. package/Dockerfile +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +470 -0
  4. package/build/config.js +237 -0
  5. package/build/index.js +87 -0
  6. package/build/logger.js +163 -0
  7. package/build/middleware/security.js +231 -0
  8. package/build/server.js +288 -0
  9. package/build/services/clickup/base.js +432 -0
  10. package/build/services/clickup/bulk.js +180 -0
  11. package/build/services/clickup/document.js +159 -0
  12. package/build/services/clickup/folder.js +136 -0
  13. package/build/services/clickup/index.js +76 -0
  14. package/build/services/clickup/list.js +191 -0
  15. package/build/services/clickup/tag.js +239 -0
  16. package/build/services/clickup/task/index.js +32 -0
  17. package/build/services/clickup/task/task-attachments.js +105 -0
  18. package/build/services/clickup/task/task-comments.js +114 -0
  19. package/build/services/clickup/task/task-core.js +604 -0
  20. package/build/services/clickup/task/task-custom-fields.js +107 -0
  21. package/build/services/clickup/task/task-search.js +986 -0
  22. package/build/services/clickup/task/task-service.js +104 -0
  23. package/build/services/clickup/task/task-tags.js +113 -0
  24. package/build/services/clickup/time.js +244 -0
  25. package/build/services/clickup/types.js +33 -0
  26. package/build/services/clickup/workspace.js +397 -0
  27. package/build/services/shared.js +61 -0
  28. package/build/sse_server.js +277 -0
  29. package/build/tools/documents.js +489 -0
  30. package/build/tools/folder.js +331 -0
  31. package/build/tools/index.js +16 -0
  32. package/build/tools/list.js +428 -0
  33. package/build/tools/member.js +106 -0
  34. package/build/tools/tag.js +833 -0
  35. package/build/tools/task/attachments.js +357 -0
  36. package/build/tools/task/attachments.types.js +9 -0
  37. package/build/tools/task/bulk-operations.js +338 -0
  38. package/build/tools/task/handlers.js +919 -0
  39. package/build/tools/task/index.js +30 -0
  40. package/build/tools/task/main.js +233 -0
  41. package/build/tools/task/single-operations.js +469 -0
  42. package/build/tools/task/time-tracking.js +575 -0
  43. package/build/tools/task/utilities.js +310 -0
  44. package/build/tools/task/workspace-operations.js +258 -0
  45. package/build/tools/tool-enhancer.js +37 -0
  46. package/build/tools/utils.js +12 -0
  47. package/build/tools/workspace-helper.js +44 -0
  48. package/build/tools/workspace.js +73 -0
  49. package/build/utils/color-processor.js +183 -0
  50. package/build/utils/concurrency-utils.js +248 -0
  51. package/build/utils/date-utils.js +542 -0
  52. package/build/utils/resolver-utils.js +135 -0
  53. package/build/utils/sponsor-service.js +93 -0
  54. package/build/utils/token-utils.js +49 -0
  55. package/package.json +77 -0
  56. package/smithery.yaml +23 -0
@@ -0,0 +1,104 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * ClickUp Task Service
6
+ *
7
+ * Complete task service combining all task-related functionality
8
+ *
9
+ * REFACTORED: Now uses composition instead of linear inheritance.
10
+ * Extends TaskServiceCore and composes other services as properties.
11
+ */
12
+ import { TaskServiceCore } from './task-core.js';
13
+ import { TaskServiceSearch } from './task-search.js';
14
+ import { TaskServiceAttachments } from './task-attachments.js';
15
+ import { TaskServiceComments } from './task-comments.js';
16
+ import { TaskServiceTags } from './task-tags.js';
17
+ import { TaskServiceCustomFields } from './task-custom-fields.js';
18
+ /**
19
+ * Complete TaskService combining all task-related functionality
20
+ *
21
+ * This service uses composition to provide access to all task operations
22
+ * while maintaining clean separation of concerns and eliminating artificial
23
+ * dependencies between service modules.
24
+ */
25
+ export class TaskService extends TaskServiceCore {
26
+ constructor(apiKey, teamId, baseUrl, workspaceService) {
27
+ super(apiKey, teamId, baseUrl, workspaceService);
28
+ this.logOperation('constructor', { initialized: true });
29
+ // Initialize composed services with core as dependency
30
+ this.search = new TaskServiceSearch(this);
31
+ this.attachments = new TaskServiceAttachments(this);
32
+ this.comments = new TaskServiceComments(this);
33
+ this.tags = new TaskServiceTags(this);
34
+ this.customFields = new TaskServiceCustomFields(this);
35
+ }
36
+ // ===== DELEGATED SEARCH METHODS =====
37
+ async findTaskByName(listId, taskName) {
38
+ return this.search.findTaskByName(listId, taskName);
39
+ }
40
+ async getWorkspaceTasks(filters = {}) {
41
+ return this.search.getWorkspaceTasks(filters);
42
+ }
43
+ async getTaskSummaries(filters = {}) {
44
+ return this.search.getTaskSummaries(filters);
45
+ }
46
+ async getListViews(listId) {
47
+ return this.search.getListViews(listId);
48
+ }
49
+ async getTasksFromView(viewId, filters = {}) {
50
+ return this.search.getTasksFromView(viewId, filters);
51
+ }
52
+ async getTaskDetails(filters = {}) {
53
+ return this.search.getTaskDetails(filters);
54
+ }
55
+ async updateTaskByName(listId, taskName, updateData) {
56
+ return this.search.updateTaskByName(listId, taskName, updateData);
57
+ }
58
+ async findTaskByNameGlobally(taskName) {
59
+ return this.search.findTaskByNameGlobally(taskName);
60
+ }
61
+ async findTasks(params) {
62
+ return this.search.findTasks(params);
63
+ }
64
+ // ===== DELEGATED ATTACHMENT METHODS =====
65
+ async uploadTaskAttachment(taskId, fileData, fileName) {
66
+ return this.attachments.uploadTaskAttachment(taskId, fileData, fileName);
67
+ }
68
+ async uploadTaskAttachmentFromUrl(taskId, fileUrl, fileName, authHeader) {
69
+ return this.attachments.uploadTaskAttachmentFromUrl(taskId, fileUrl, fileName, authHeader);
70
+ }
71
+ // ===== DELEGATED COMMENT METHODS =====
72
+ async getTaskComments(taskId, start, startId) {
73
+ return this.comments.getTaskComments(taskId, start, startId);
74
+ }
75
+ async createTaskComment(taskId, commentText, notifyAll, assignee) {
76
+ return this.comments.createTaskComment(taskId, commentText, notifyAll, assignee);
77
+ }
78
+ // ===== DELEGATED TAG METHODS =====
79
+ async addTagToTask(taskId, tagName) {
80
+ return this.tags.addTagToTask(taskId, tagName);
81
+ }
82
+ async removeTagFromTask(taskId, tagName) {
83
+ return this.tags.removeTagFromTask(taskId, tagName);
84
+ }
85
+ async getTaskTags(taskId) {
86
+ return this.tags.getTaskTags(taskId);
87
+ }
88
+ async updateTaskTags(taskId, tagNames) {
89
+ return this.tags.updateTaskTags(taskId, tagNames);
90
+ }
91
+ // ===== DELEGATED CUSTOM FIELD METHODS =====
92
+ async setCustomFieldValue(taskId, fieldId, value) {
93
+ return this.customFields.setCustomFieldValue(taskId, fieldId, value);
94
+ }
95
+ async setCustomFieldValues(taskId, customFields) {
96
+ return this.customFields.setCustomFieldValues(taskId, customFields);
97
+ }
98
+ async getCustomFieldValues(taskId) {
99
+ return this.customFields.getCustomFieldValues(taskId);
100
+ }
101
+ async getCustomFieldValue(taskId, fieldId) {
102
+ return this.customFields.getCustomFieldValue(taskId, fieldId);
103
+ }
104
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * ClickUp Task Service - Tags Module
6
+ *
7
+ * Handles tag operations for ClickUp tasks, including:
8
+ * - Adding tags to a task
9
+ * - Removing tags from a task
10
+ *
11
+ * REFACTORED: Now uses composition instead of inheritance.
12
+ * Only depends on TaskServiceCore for getTask() and base functionality.
13
+ */
14
+ /**
15
+ * Tags functionality for the TaskService
16
+ *
17
+ * This service handles all tag-related operations for ClickUp tasks.
18
+ * It uses composition to access core functionality instead of inheritance.
19
+ */
20
+ export class TaskServiceTags {
21
+ constructor(core) {
22
+ this.core = core;
23
+ }
24
+ /**
25
+ * Add a tag to a task
26
+ *
27
+ * @param taskId ID of the task
28
+ * @param tagName Name of the tag to add
29
+ * @returns Success response
30
+ */
31
+ async addTagToTask(taskId, tagName) {
32
+ this.core.logOperation('addTagToTask', { taskId, tagName });
33
+ try {
34
+ const payload = {
35
+ tag_name: tagName,
36
+ };
37
+ await this.core.makeRequest(async () => {
38
+ return await this.core.client.post(`/task/${taskId}/tag/${encodeURIComponent(tagName)}`, payload);
39
+ });
40
+ return true;
41
+ }
42
+ catch (error) {
43
+ throw this.core.handleError(error, `Failed to add tag "${tagName}" to task`);
44
+ }
45
+ }
46
+ /**
47
+ * Remove a tag from a task
48
+ *
49
+ * @param taskId ID of the task
50
+ * @param tagName Name of the tag to remove
51
+ * @returns Success response
52
+ */
53
+ async removeTagFromTask(taskId, tagName) {
54
+ this.core.logOperation('removeTagFromTask', { taskId, tagName });
55
+ try {
56
+ await this.core.makeRequest(async () => {
57
+ return await this.core.client.delete(`/task/${taskId}/tag/${encodeURIComponent(tagName)}`);
58
+ });
59
+ return true;
60
+ }
61
+ catch (error) {
62
+ throw this.core.handleError(error, `Failed to remove tag "${tagName}" from task`);
63
+ }
64
+ }
65
+ /**
66
+ * Get all tags for a task
67
+ *
68
+ * @param taskId ID of the task
69
+ * @returns Array of task tags
70
+ */
71
+ async getTaskTags(taskId) {
72
+ this.core.logOperation('getTaskTags', { taskId });
73
+ try {
74
+ // We need to fetch the full task to get its tags
75
+ const task = await this.core.getTask(taskId);
76
+ return task.tags || [];
77
+ }
78
+ catch (error) {
79
+ throw this.core.handleError(error, 'Failed to get task tags');
80
+ }
81
+ }
82
+ /**
83
+ * Update all tags for a task (replaces existing tags)
84
+ *
85
+ * @param taskId ID of the task
86
+ * @param tagNames Array of tag names to set
87
+ * @returns Success response
88
+ */
89
+ async updateTaskTags(taskId, tagNames) {
90
+ this.core.logOperation('updateTaskTags', { taskId, tagNames });
91
+ try {
92
+ // First get existing tags
93
+ const existingTags = await this.getTaskTags(taskId);
94
+ const existingTagNames = existingTags.map(tag => tag.name);
95
+ // Remove tags that shouldn't be there
96
+ for (const tagName of existingTagNames) {
97
+ if (!tagNames.includes(tagName)) {
98
+ await this.removeTagFromTask(taskId, tagName);
99
+ }
100
+ }
101
+ // Add new tags
102
+ for (const tagName of tagNames) {
103
+ if (!existingTagNames.includes(tagName)) {
104
+ await this.addTagToTask(taskId, tagName);
105
+ }
106
+ }
107
+ return true;
108
+ }
109
+ catch (error) {
110
+ throw this.core.handleError(error, 'Failed to update task tags');
111
+ }
112
+ }
113
+ }
@@ -0,0 +1,244 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * Time tracking service for ClickUp tasks
6
+ *
7
+ * This service provides methods to manage time tracking for ClickUp tasks:
8
+ * - Get time entries for a task
9
+ * - Start time tracking on a task
10
+ * - Stop time tracking on a task
11
+ * - Add a manual time entry
12
+ * - Delete a time entry
13
+ */
14
+ import { BaseClickUpService, ErrorCode, ClickUpServiceError } from './base.js';
15
+ /**
16
+ * Time tracking service for ClickUp
17
+ */
18
+ export class TimeTrackingService extends BaseClickUpService {
19
+ /**
20
+ * Get all time entries for a task
21
+ * @param taskId ID of the task
22
+ * @param startDate Optional start date filter (Unix timestamp in milliseconds)
23
+ * @param endDate Optional end date filter (Unix timestamp in milliseconds)
24
+ * @returns List of time entries
25
+ */
26
+ async getTimeEntries(taskId, startDate, endDate) {
27
+ try {
28
+ this.logOperation('getTimeEntries', { taskId, startDate, endDate });
29
+ // Build query parameters
30
+ let query = {};
31
+ if (startDate)
32
+ query.start_date = startDate;
33
+ if (endDate)
34
+ query.end_date = endDate;
35
+ const path = `/task/${taskId}/time`;
36
+ this.traceRequest('GET', path, query);
37
+ const response = await this.makeRequest(() => this.client.get(path, {
38
+ params: query
39
+ }));
40
+ return {
41
+ success: true,
42
+ data: response.data.data
43
+ };
44
+ }
45
+ catch (error) {
46
+ if (error instanceof ClickUpServiceError) {
47
+ return {
48
+ success: false,
49
+ error: {
50
+ message: error.message,
51
+ code: error.code,
52
+ details: error.data
53
+ }
54
+ };
55
+ }
56
+ return {
57
+ success: false,
58
+ error: {
59
+ message: `Failed to get time entries: ${error.message}`,
60
+ code: ErrorCode.UNKNOWN
61
+ }
62
+ };
63
+ }
64
+ }
65
+ /**
66
+ * Start time tracking on a task
67
+ * @param data Task ID and optional parameters
68
+ * @returns The created time entry
69
+ */
70
+ async startTimeTracking(data) {
71
+ try {
72
+ this.logOperation('startTimeTracking', { taskId: data.tid });
73
+ const path = `/team/${this.teamId}/time_entries/start`;
74
+ this.traceRequest('POST', path, data);
75
+ const response = await this.makeRequest(() => this.client.post(path, data));
76
+ return {
77
+ success: true,
78
+ data: response.data.data
79
+ };
80
+ }
81
+ catch (error) {
82
+ if (error instanceof ClickUpServiceError) {
83
+ return {
84
+ success: false,
85
+ error: {
86
+ message: error.message,
87
+ code: error.code,
88
+ details: error.data
89
+ }
90
+ };
91
+ }
92
+ return {
93
+ success: false,
94
+ error: {
95
+ message: `Failed to start time tracking: ${error.message}`,
96
+ code: ErrorCode.UNKNOWN
97
+ }
98
+ };
99
+ }
100
+ }
101
+ /**
102
+ * Stop the currently running time tracker
103
+ * @param data Optional parameters for the stopped time entry
104
+ * @returns The completed time entry
105
+ */
106
+ async stopTimeTracking(data) {
107
+ try {
108
+ this.logOperation('stopTimeTracking', {});
109
+ const path = `/team/${this.teamId}/time_entries/stop`;
110
+ this.traceRequest('POST', path, data || {});
111
+ const response = await this.makeRequest(() => this.client.post(path, data || {}));
112
+ return {
113
+ success: true,
114
+ data: response.data.data
115
+ };
116
+ }
117
+ catch (error) {
118
+ if (error instanceof ClickUpServiceError) {
119
+ return {
120
+ success: false,
121
+ error: {
122
+ message: error.message,
123
+ code: error.code,
124
+ details: error.data
125
+ }
126
+ };
127
+ }
128
+ return {
129
+ success: false,
130
+ error: {
131
+ message: `Failed to stop time tracking: ${error.message}`,
132
+ code: ErrorCode.UNKNOWN
133
+ }
134
+ };
135
+ }
136
+ }
137
+ /**
138
+ * Add a manual time entry to a task
139
+ * @param data Time entry data including task ID, start time, and duration
140
+ * @returns The created time entry
141
+ */
142
+ async addTimeEntry(data) {
143
+ try {
144
+ this.logOperation('addTimeEntry', { taskId: data.tid, duration: data.duration });
145
+ const path = `/team/${this.teamId}/time_entries`;
146
+ this.traceRequest('POST', path, data);
147
+ const response = await this.makeRequest(() => this.client.post(path, data));
148
+ return {
149
+ success: true,
150
+ data: response.data.data
151
+ };
152
+ }
153
+ catch (error) {
154
+ if (error instanceof ClickUpServiceError) {
155
+ return {
156
+ success: false,
157
+ error: {
158
+ message: error.message,
159
+ code: error.code,
160
+ details: error.data
161
+ }
162
+ };
163
+ }
164
+ return {
165
+ success: false,
166
+ error: {
167
+ message: `Failed to add time entry: ${error.message}`,
168
+ code: ErrorCode.UNKNOWN
169
+ }
170
+ };
171
+ }
172
+ }
173
+ /**
174
+ * Delete a time entry
175
+ * @param timeEntryId ID of the time entry to delete
176
+ * @returns Success response
177
+ */
178
+ async deleteTimeEntry(timeEntryId) {
179
+ try {
180
+ this.logOperation('deleteTimeEntry', { timeEntryId });
181
+ const path = `/team/${this.teamId}/time_entries/${timeEntryId}`;
182
+ this.traceRequest('DELETE', path);
183
+ await this.makeRequest(() => this.client.delete(path));
184
+ return {
185
+ success: true,
186
+ data: true
187
+ };
188
+ }
189
+ catch (error) {
190
+ if (error instanceof ClickUpServiceError) {
191
+ return {
192
+ success: false,
193
+ error: {
194
+ message: error.message,
195
+ code: error.code,
196
+ details: error.data
197
+ }
198
+ };
199
+ }
200
+ return {
201
+ success: false,
202
+ error: {
203
+ message: `Failed to delete time entry: ${error.message}`,
204
+ code: ErrorCode.UNKNOWN
205
+ }
206
+ };
207
+ }
208
+ }
209
+ /**
210
+ * Get currently running time entry for the user
211
+ * @returns The currently running time entry or null if no timer is running
212
+ */
213
+ async getCurrentTimeEntry() {
214
+ try {
215
+ this.logOperation('getCurrentTimeEntry', {});
216
+ const path = `/team/${this.teamId}/time_entries/current`;
217
+ this.traceRequest('GET', path);
218
+ const response = await this.makeRequest(() => this.client.get(path));
219
+ return {
220
+ success: true,
221
+ data: response.data.data
222
+ };
223
+ }
224
+ catch (error) {
225
+ if (error instanceof ClickUpServiceError) {
226
+ return {
227
+ success: false,
228
+ error: {
229
+ message: error.message,
230
+ code: error.code,
231
+ details: error.data
232
+ }
233
+ };
234
+ }
235
+ return {
236
+ success: false,
237
+ error: {
238
+ message: `Failed to get current time entry: ${error.message}`,
239
+ code: ErrorCode.UNKNOWN
240
+ }
241
+ };
242
+ }
243
+ }
244
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * Common type definitions for ClickUp API entities
6
+ */
7
+ // Helper function to validate and convert priority values
8
+ export function toTaskPriority(value) {
9
+ if (value === null)
10
+ return null;
11
+ if (value === undefined)
12
+ return undefined;
13
+ if (value === "null")
14
+ return null;
15
+ // Convert string to number if needed
16
+ const numValue = typeof value === 'string' ? parseInt(value, 10) : value;
17
+ // Validate it's a valid priority number
18
+ if (typeof numValue === 'number' && !isNaN(numValue) && [1, 2, 3, 4].includes(numValue)) {
19
+ return numValue;
20
+ }
21
+ return undefined;
22
+ }
23
+ /**
24
+ * ClickUp parent container types
25
+ */
26
+ export var ClickUpParentType;
27
+ (function (ClickUpParentType) {
28
+ ClickUpParentType[ClickUpParentType["Space"] = 4] = "Space";
29
+ ClickUpParentType[ClickUpParentType["Folder"] = 5] = "Folder";
30
+ ClickUpParentType[ClickUpParentType["List"] = 6] = "List";
31
+ ClickUpParentType[ClickUpParentType["All"] = 7] = "All";
32
+ ClickUpParentType[ClickUpParentType["Workspace"] = 12] = "Workspace";
33
+ })(ClickUpParentType || (ClickUpParentType = {}));