@taazkareem/clickup-mcp-server 0.4.72 → 0.4.73

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.
@@ -0,0 +1,331 @@
1
+ /**
2
+ * ClickUp MCP Single Task Operations
3
+ *
4
+ * This module defines tools for single task operations including creating,
5
+ * updating, moving, duplicating, and deleting tasks, as well as retrieving
6
+ * task details and comments.
7
+ */
8
+ import { clickUpServices } from '../../services/shared.js';
9
+ // Use shared services instance
10
+ const { task: taskService } = clickUpServices;
11
+ //=============================================================================
12
+ // COMMON VALIDATION UTILITIES
13
+ //=============================================================================
14
+ // Common validation functions
15
+ const validateTaskName = (name) => {
16
+ if (!name || typeof name !== 'string') {
17
+ throw new Error("A task name is required");
18
+ }
19
+ const trimmedName = name.trim();
20
+ if (trimmedName.length === 0) {
21
+ throw new Error("Task name cannot be empty or only whitespace");
22
+ }
23
+ return trimmedName;
24
+ };
25
+ const validatePriority = (priority) => {
26
+ if (priority !== undefined && (typeof priority !== 'number' || priority < 1 || priority > 4)) {
27
+ throw new Error("Priority must be a number between 1 and 4");
28
+ }
29
+ };
30
+ const validateDueDate = (dueDate) => {
31
+ if (dueDate && typeof dueDate !== 'string') {
32
+ throw new Error("Due date must be a string in timestamp format or natural language");
33
+ }
34
+ };
35
+ // Common error handler
36
+ const handleOperationError = (operation, error) => {
37
+ console.error(`Error ${operation}:`, error);
38
+ throw error;
39
+ };
40
+ //=============================================================================
41
+ // SINGLE TASK OPERATION TOOLS
42
+ //=============================================================================
43
+ /**
44
+ * Tool definition for creating a single task
45
+ */
46
+ export const createTaskTool = {
47
+ name: "create_task",
48
+ description: "Create a single task in a ClickUp list. Use this tool for individual task creation only. For multiple tasks, use create_bulk_tasks instead. Before calling this tool, check if you already have the necessary list ID from previous responses in the conversation history, as this avoids redundant lookups. When creating a task, you MUST provide either a listId or listName.",
49
+ inputSchema: {
50
+ type: "object",
51
+ properties: {
52
+ name: {
53
+ type: "string",
54
+ description: "REQUIRED: Name of the task. Put a relevant emoji followed by a blank space before the name."
55
+ },
56
+ listId: {
57
+ type: "string",
58
+ description: "REQUIRED (unless listName provided): ID of the list to create the task in. If you have this ID from a previous response, use it directly rather than looking up by name."
59
+ },
60
+ listName: {
61
+ type: "string",
62
+ description: "REQUIRED (unless listId provided): Name of the list to create the task in - will automatically find the list by name."
63
+ },
64
+ description: {
65
+ type: "string",
66
+ description: "Optional plain text description for the task"
67
+ },
68
+ markdown_description: {
69
+ type: "string",
70
+ description: "Optional markdown formatted description for the task. If provided, this takes precedence over description"
71
+ },
72
+ status: {
73
+ type: "string",
74
+ description: "Optional: Override the default ClickUp status. In most cases, you should omit this to use ClickUp defaults"
75
+ },
76
+ priority: {
77
+ type: "number",
78
+ description: "Optional priority of the task (1-4), where 1 is urgent/highest priority and 4 is lowest priority. Only set this when explicitly requested."
79
+ },
80
+ dueDate: {
81
+ type: "string",
82
+ description: "Optional due date. Supports Unix timestamps (ms) or natural language like '1 hour from now', 'tomorrow', 'next week', etc."
83
+ }
84
+ }
85
+ }
86
+ };
87
+ /**
88
+ * Tool definition for updating a task
89
+ */
90
+ export const updateTaskTool = {
91
+ name: "update_task",
92
+ description: "Modify an existing task's properties. Valid parameter combinations:\n1. Use taskId alone (preferred if you have it)\n2. Use taskName + listName (listName is REQUIRED when using taskName, not optional)\n\nAt least one update field (name, description, status, priority) must be provided. Only specified fields will be updated.",
93
+ inputSchema: {
94
+ type: "object",
95
+ properties: {
96
+ taskId: {
97
+ type: "string",
98
+ description: "ID of the task to update (preferred). Use this instead of taskName if you have it from a previous response."
99
+ },
100
+ taskName: {
101
+ type: "string",
102
+ description: "Name of the task to update. Only use this if you don't have taskId. When using this parameter, you MUST also provide listName."
103
+ },
104
+ listName: {
105
+ type: "string",
106
+ description: "Name of the list containing the task. REQUIRED when using taskName."
107
+ },
108
+ name: {
109
+ type: "string",
110
+ description: "New name for the task. Include emoji prefix if appropriate."
111
+ },
112
+ description: {
113
+ type: "string",
114
+ description: "New plain text description. Will be ignored if markdown_description is provided."
115
+ },
116
+ markdown_description: {
117
+ type: "string",
118
+ description: "New markdown description. Takes precedence over plain text description."
119
+ },
120
+ status: {
121
+ type: "string",
122
+ description: "New status. Must be valid for the task's current list."
123
+ },
124
+ priority: {
125
+ type: ["number", "null"],
126
+ enum: [1, 2, 3, 4, null],
127
+ description: "New priority: 1 (urgent) to 4 (low). Set null to clear priority."
128
+ },
129
+ dueDate: {
130
+ type: "string",
131
+ description: "New due date. Supports both Unix timestamps (in milliseconds) and natural language expressions like '1 hour from now', 'tomorrow', 'next week', or '3 days from now'."
132
+ }
133
+ },
134
+ required: []
135
+ }
136
+ };
137
+ /**
138
+ * Tool definition for moving a task
139
+ */
140
+ export const moveTaskTool = {
141
+ name: "move_task",
142
+ description: "Move a task to a different list. Valid parameter combinations:\n1. Use taskId + (listId or listName) - preferred\n2. Use taskName + sourceListName + (listId or listName)\n\nWARNING: When using taskName, sourceListName is ABSOLUTELY REQUIRED - the system cannot find a task by name without knowing which list to search in. Task statuses may reset if destination list has different status options.",
143
+ inputSchema: {
144
+ type: "object",
145
+ properties: {
146
+ taskId: {
147
+ type: "string",
148
+ description: "ID of the task to move (preferred). Use this instead of taskName if you have it."
149
+ },
150
+ taskName: {
151
+ type: "string",
152
+ description: "Name of the task to move. When using this, you MUST also provide sourceListName."
153
+ },
154
+ sourceListName: {
155
+ type: "string",
156
+ description: "REQUIRED with taskName: Current list containing the task."
157
+ },
158
+ listId: {
159
+ type: "string",
160
+ description: "ID of destination list (preferred). Use this instead of listName if you have it."
161
+ },
162
+ listName: {
163
+ type: "string",
164
+ description: "Name of destination list. Only use if you don't have listId."
165
+ }
166
+ },
167
+ required: []
168
+ }
169
+ };
170
+ /**
171
+ * Tool definition for duplicating a task
172
+ */
173
+ export const duplicateTaskTool = {
174
+ name: "duplicate_task",
175
+ description: "Create a copy of a task in the same or different list. Valid parameter combinations:\n1. Use taskId + optional (listId or listName) - preferred\n2. Use taskName + sourceListName + optional (listId or listName)\n\nWARNING: When using taskName, sourceListName is ABSOLUTELY REQUIRED - the system cannot find a task by name without knowing which list to search in. The duplicate preserves the original task's properties.",
176
+ inputSchema: {
177
+ type: "object",
178
+ properties: {
179
+ taskId: {
180
+ type: "string",
181
+ description: "ID of task to duplicate (preferred). Use this instead of taskName if you have it."
182
+ },
183
+ taskName: {
184
+ type: "string",
185
+ description: "Name of task to duplicate. When using this, you MUST provide sourceListName."
186
+ },
187
+ sourceListName: {
188
+ type: "string",
189
+ description: "REQUIRED with taskName: List containing the original task."
190
+ },
191
+ listId: {
192
+ type: "string",
193
+ description: "ID of list for the duplicate (optional). Defaults to same list as original."
194
+ },
195
+ listName: {
196
+ type: "string",
197
+ description: "Name of list for the duplicate. Only use if you don't have listId."
198
+ }
199
+ },
200
+ required: []
201
+ }
202
+ };
203
+ /**
204
+ * Tool definition for retrieving a task
205
+ */
206
+ export const getTaskTool = {
207
+ name: "get_task",
208
+ description: "Retrieve detailed information about a specific task. Valid parameter combinations:\n1. Use taskId alone (preferred)\n2. Use taskName + listName (listName is REQUIRED when using taskName). Task names are only unique within a list, so the system needs to know which list to search in.",
209
+ inputSchema: {
210
+ type: "object",
211
+ properties: {
212
+ taskId: {
213
+ type: "string",
214
+ description: "ID of task to retrieve (preferred). Use this instead of taskName if you have it."
215
+ },
216
+ taskName: {
217
+ type: "string",
218
+ description: "Name of task to retrieve. When using this parameter, you MUST also provide listName."
219
+ },
220
+ listName: {
221
+ type: "string",
222
+ description: "Name of list containing the task. REQUIRED when using taskName."
223
+ }
224
+ },
225
+ required: []
226
+ }
227
+ };
228
+ /**
229
+ * Tool definition for retrieving tasks from a list
230
+ */
231
+ export const getTasksTool = {
232
+ name: "get_tasks",
233
+ description: "Retrieve tasks from a list with optional filtering. You MUST provide either:\n1. listId (preferred)\n2. listName\n\nUse filters to narrow down results by status, dates, etc.",
234
+ inputSchema: {
235
+ type: "object",
236
+ properties: {
237
+ listId: {
238
+ type: "string",
239
+ description: "ID of list to get tasks from (preferred). Use this instead of listName if you have it."
240
+ },
241
+ listName: {
242
+ type: "string",
243
+ description: "Name of list to get tasks from. Only use if you don't have listId."
244
+ },
245
+ archived: {
246
+ type: "boolean",
247
+ description: "Include archived tasks"
248
+ },
249
+ page: {
250
+ type: "number",
251
+ description: "Page number for pagination (starts at 0)"
252
+ },
253
+ order_by: {
254
+ type: "string",
255
+ description: "Sort field: due_date, created, updated"
256
+ },
257
+ reverse: {
258
+ type: "boolean",
259
+ description: "Reverse sort order (descending)"
260
+ },
261
+ subtasks: {
262
+ type: "boolean",
263
+ description: "Include subtasks"
264
+ },
265
+ statuses: {
266
+ type: "array",
267
+ items: {
268
+ type: "string"
269
+ },
270
+ description: "Filter by status names (e.g. ['To Do', 'In Progress'])"
271
+ }
272
+ },
273
+ required: []
274
+ }
275
+ };
276
+ /**
277
+ * Tool definition for deleting a task
278
+ */
279
+ export const deleteTaskTool = {
280
+ name: "delete_task",
281
+ description: "⚠️ PERMANENTLY DELETE a task. This action cannot be undone. Valid parameter combinations:\n1. Use taskId alone (preferred and safest)\n2. Use taskName + optional listName (use with caution).",
282
+ inputSchema: {
283
+ type: "object",
284
+ properties: {
285
+ taskId: {
286
+ type: "string",
287
+ description: "ID of task to delete (preferred). Use this instead of taskName for safety."
288
+ },
289
+ taskName: {
290
+ type: "string",
291
+ description: "Name of task to delete. Use with extreme caution as names may not be unique."
292
+ },
293
+ listName: {
294
+ type: "string",
295
+ description: "Name of list containing the task. Helps ensure correct task deletion when using taskName."
296
+ }
297
+ }
298
+ }
299
+ };
300
+ /**
301
+ * Tool definition for retrieving task comments
302
+ */
303
+ export const getTaskCommentsTool = {
304
+ name: "get_task_comments",
305
+ description: "Retrieve comments for a ClickUp task. You can identify the task by either taskId or taskName. If using taskName, you can optionally provide listName to help locate the correct task if multiple tasks have the same name.",
306
+ inputSchema: {
307
+ type: "object",
308
+ properties: {
309
+ taskId: {
310
+ type: "string",
311
+ description: "ID of task to retrieve comments for (preferred). Use this instead of taskName if you have it."
312
+ },
313
+ taskName: {
314
+ type: "string",
315
+ description: "Name of task to retrieve comments for. Warning: Task names may not be unique."
316
+ },
317
+ listName: {
318
+ type: "string",
319
+ description: "Name of list containing the task. Helps find the right task when using taskName."
320
+ },
321
+ start: {
322
+ type: "number",
323
+ description: "Timestamp (in milliseconds) to start retrieving comments from. Used for pagination."
324
+ },
325
+ startId: {
326
+ type: "string",
327
+ description: "Comment ID to start from. Used together with start for pagination."
328
+ }
329
+ }
330
+ }
331
+ };
@@ -0,0 +1,163 @@
1
+ /**
2
+ * ClickUp MCP Task Utilities
3
+ *
4
+ * This module provides utility functions for task-related operations including
5
+ * data formatting, validation, and resolution of IDs from names.
6
+ */
7
+ import { formatDueDate } from '../utils.js';
8
+ import { clickUpServices } from '../../services/shared.js';
9
+ // Use shared services instance for ID resolution
10
+ const { workspace: workspaceService } = clickUpServices;
11
+ //=============================================================================
12
+ // DATA FORMATTING UTILITIES
13
+ //=============================================================================
14
+ /**
15
+ * Formats task data for response
16
+ */
17
+ export function formatTaskData(task, additional = {}) {
18
+ return {
19
+ id: task.id,
20
+ name: task.name,
21
+ url: task.url,
22
+ status: task.status?.status || "Unknown",
23
+ due_date: task.due_date ? formatDueDate(Number(task.due_date)) : undefined,
24
+ list: task.list.name,
25
+ space: task.space.name,
26
+ folder: task.folder?.name,
27
+ ...additional
28
+ };
29
+ }
30
+ //=============================================================================
31
+ // VALIDATION UTILITIES
32
+ //=============================================================================
33
+ /**
34
+ * Validates task identification parameters
35
+ * Ensures either taskId is provided or both taskName and listName are provided
36
+ */
37
+ export function validateTaskIdentification(taskId, taskName, listName) {
38
+ if (!taskId && !taskName) {
39
+ throw new Error("Either taskId or taskName must be provided");
40
+ }
41
+ if (!taskId && taskName && !listName) {
42
+ throw new Error("When using taskName, listName is required to locate the task");
43
+ }
44
+ }
45
+ /**
46
+ * Validates list identification parameters
47
+ * Ensures either listId or listName is provided
48
+ */
49
+ export function validateListIdentification(listId, listName) {
50
+ if (!listId && !listName) {
51
+ throw new Error("Either listId or listName must be provided");
52
+ }
53
+ }
54
+ /**
55
+ * Validates task update data
56
+ * Ensures at least one update field is provided
57
+ */
58
+ export function validateTaskUpdateData(updateData) {
59
+ const hasUpdates = Object.keys(updateData).length > 0;
60
+ if (!hasUpdates) {
61
+ throw new Error("At least one field to update must be provided");
62
+ }
63
+ }
64
+ /**
65
+ * Validate bulk operation tasks array
66
+ */
67
+ export function validateBulkTasks(tasks) {
68
+ if (!tasks || !Array.isArray(tasks) || tasks.length === 0) {
69
+ throw new Error('You must provide a non-empty array of tasks');
70
+ }
71
+ }
72
+ /**
73
+ * Parse options for bulk operations
74
+ */
75
+ export function parseBulkOptions(rawOptions) {
76
+ if (typeof rawOptions === 'string') {
77
+ try {
78
+ return JSON.parse(rawOptions);
79
+ }
80
+ catch (error) {
81
+ return undefined;
82
+ }
83
+ }
84
+ return rawOptions;
85
+ }
86
+ //=============================================================================
87
+ // ID RESOLUTION UTILITIES
88
+ //=============================================================================
89
+ /**
90
+ * Resolves a task ID from either direct ID or name+list combination
91
+ * Handles validation and throws appropriate errors
92
+ */
93
+ export async function resolveTaskIdWithValidation(taskId, taskName, listName) {
94
+ // Validate parameters
95
+ validateTaskIdentification(taskId, taskName, listName);
96
+ // If taskId is provided, use it directly
97
+ if (taskId)
98
+ return taskId;
99
+ // At this point we know we have taskName and listName (validation ensures this)
100
+ // Find the list ID from its name
101
+ const listId = await resolveListIdWithValidation(undefined, listName);
102
+ // Find the task in the list
103
+ const { task: taskService } = clickUpServices;
104
+ const foundTask = await taskService.findTaskByName(listId, taskName);
105
+ if (!foundTask) {
106
+ throw new Error(`Task "${taskName}" not found in list "${listName}"`);
107
+ }
108
+ return foundTask.id;
109
+ }
110
+ /**
111
+ * Resolves a list ID from either direct ID or name
112
+ * Handles validation and throws appropriate errors
113
+ */
114
+ export async function resolveListIdWithValidation(listId, listName) {
115
+ // Validate parameters
116
+ validateListIdentification(listId, listName);
117
+ // If listId is provided, use it directly
118
+ if (listId)
119
+ return listId;
120
+ // At this point we know we have listName (validation ensures this)
121
+ // Find the list ID from its name
122
+ const hierarchy = await workspaceService.getWorkspaceHierarchy();
123
+ const listInfo = workspaceService.findIDByNameInHierarchy(hierarchy, listName, 'list');
124
+ if (!listInfo) {
125
+ throw new Error(`List "${listName}" not found`);
126
+ }
127
+ return listInfo.id;
128
+ }
129
+ //=============================================================================
130
+ // PATH EXTRACTION HELPER FUNCTIONS
131
+ //=============================================================================
132
+ /**
133
+ * Extract path from node to root
134
+ */
135
+ export function extractPath(node) {
136
+ if (!node)
137
+ return '';
138
+ if (!node.parent)
139
+ return node.name;
140
+ return `${extractPath(node.parent)} > ${node.name}`;
141
+ }
142
+ /**
143
+ * Extract path from root to a specific node
144
+ */
145
+ export function extractTreePath(root, targetId) {
146
+ if (!root)
147
+ return [];
148
+ // If this node is the target, return it in an array
149
+ if (root.id === targetId) {
150
+ return [root];
151
+ }
152
+ // Check children if they exist
153
+ if (root.children) {
154
+ for (const child of root.children) {
155
+ const path = extractTreePath(child, targetId);
156
+ if (path.length > 0) {
157
+ return [root, ...path];
158
+ }
159
+ }
160
+ }
161
+ // Not found in this branch
162
+ return [];
163
+ }