@taazkareem/clickup-mcp-server 0.4.60 → 0.4.63

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,452 @@
1
+ /**
2
+ * ClickUp MCP List Tools
3
+ *
4
+ * This module defines list-related tools including creating, updating,
5
+ * retrieving, and deleting lists. It supports creating lists both in spaces
6
+ * and in folders.
7
+ */
8
+ import { createClickUpServices } from '../services/clickup/index.js';
9
+ import config from '../config.js';
10
+ // Initialize ClickUp services using the factory function
11
+ const services = createClickUpServices({
12
+ apiKey: config.clickupApiKey,
13
+ teamId: config.clickupTeamId
14
+ });
15
+ // Extract the services we need for list operations
16
+ const { list: listService, workspace: workspaceService } = services;
17
+ /**
18
+ * Tool definition for creating a list directly in a space
19
+ */
20
+ export const createListTool = {
21
+ name: "create_list",
22
+ description: "Create a new list directly in a ClickUp space (not in a folder). You MUST provide either spaceId or spaceName. For creating lists inside folders, use create_list_in_folder instead.",
23
+ inputSchema: {
24
+ type: "object",
25
+ properties: {
26
+ name: {
27
+ type: "string",
28
+ description: "Name of the list"
29
+ },
30
+ spaceId: {
31
+ type: "string",
32
+ description: "ID of the space to create the list in. Use this instead of spaceName if you have the ID."
33
+ },
34
+ spaceName: {
35
+ type: "string",
36
+ description: "Name of the space to create the list in. Alternative to spaceId - one of them MUST be provided."
37
+ },
38
+ content: {
39
+ type: "string",
40
+ description: "Description or content of the list"
41
+ },
42
+ dueDate: {
43
+ type: "string",
44
+ description: "Due date for the list (Unix timestamp in milliseconds)"
45
+ },
46
+ priority: {
47
+ type: "number",
48
+ description: "Priority level: 1 (urgent), 2 (high), 3 (normal), 4 (low)"
49
+ },
50
+ assignee: {
51
+ type: "number",
52
+ description: "User ID to assign the list to"
53
+ },
54
+ status: {
55
+ type: "string",
56
+ description: "Status of the list"
57
+ }
58
+ },
59
+ required: ["name"]
60
+ }
61
+ };
62
+ /**
63
+ * Tool definition for creating a list within a folder
64
+ */
65
+ export const createListInFolderTool = {
66
+ name: "create_list_in_folder",
67
+ description: "Create a new list within a ClickUp folder. You MUST provide either: 1) folderId alone, or 2) folderName WITH either spaceName or spaceId. Folder names may not be unique across spaces, which is why space information is required when using folderName.",
68
+ inputSchema: {
69
+ type: "object",
70
+ properties: {
71
+ name: {
72
+ type: "string",
73
+ description: "Name of the list"
74
+ },
75
+ folderId: {
76
+ type: "string",
77
+ description: "ID of the folder to create the list in. If you have this, you don't need folderName or space information."
78
+ },
79
+ folderName: {
80
+ type: "string",
81
+ description: "Name of the folder to create the list in. When using this, you MUST also provide either spaceName or spaceId."
82
+ },
83
+ spaceId: {
84
+ type: "string",
85
+ description: "ID of the space containing the folder. Required when using folderName instead of folderId."
86
+ },
87
+ spaceName: {
88
+ type: "string",
89
+ description: "Name of the space containing the folder. Required when using folderName instead of folderId."
90
+ },
91
+ content: {
92
+ type: "string",
93
+ description: "Description or content of the list"
94
+ },
95
+ status: {
96
+ type: "string",
97
+ description: "Status of the list (uses folder default if not specified)"
98
+ }
99
+ },
100
+ required: ["name"]
101
+ }
102
+ };
103
+ /**
104
+ * Tool definition for retrieving list details
105
+ */
106
+ export const getListTool = {
107
+ name: "get_list",
108
+ description: "Retrieve details about a specific ClickUp list. You MUST provide either listId or listName. Using listId is more reliable as list names might not be unique.",
109
+ inputSchema: {
110
+ type: "object",
111
+ properties: {
112
+ listId: {
113
+ type: "string",
114
+ description: "ID of the list to retrieve. Use this instead of listName if you have the ID."
115
+ },
116
+ listName: {
117
+ type: "string",
118
+ description: "Name of the list to retrieve. May be ambiguous if multiple lists have the same name."
119
+ }
120
+ },
121
+ required: []
122
+ }
123
+ };
124
+ /**
125
+ * Tool definition for updating a list
126
+ */
127
+ export const updateListTool = {
128
+ name: "update_list",
129
+ description: "Update an existing ClickUp list's properties. You MUST provide either listId or listName, and at least one field to update (name, content, or status).",
130
+ inputSchema: {
131
+ type: "object",
132
+ properties: {
133
+ listId: {
134
+ type: "string",
135
+ description: "ID of the list to update. Use this instead of listName if you have the ID."
136
+ },
137
+ listName: {
138
+ type: "string",
139
+ description: "Name of the list to update. May be ambiguous if multiple lists have the same name."
140
+ },
141
+ name: {
142
+ type: "string",
143
+ description: "New name for the list"
144
+ },
145
+ content: {
146
+ type: "string",
147
+ description: "New description or content for the list"
148
+ },
149
+ status: {
150
+ type: "string",
151
+ description: "New status for the list"
152
+ }
153
+ },
154
+ required: []
155
+ }
156
+ };
157
+ /**
158
+ * Tool definition for deleting a list
159
+ */
160
+ export const deleteListTool = {
161
+ name: "delete_list",
162
+ description: "Permanently delete a ClickUp list and all its tasks. You MUST provide either listId or listName. WARNING: This action cannot be undone.",
163
+ inputSchema: {
164
+ type: "object",
165
+ properties: {
166
+ listId: {
167
+ type: "string",
168
+ description: "ID of the list to delete. Use this instead of listName if you have the ID."
169
+ },
170
+ listName: {
171
+ type: "string",
172
+ description: "Name of the list to delete. May be ambiguous if multiple lists have the same name."
173
+ }
174
+ },
175
+ required: []
176
+ }
177
+ };
178
+ /**
179
+ * Helper function to find a list ID by name
180
+ * Uses the ClickUp service's global list search functionality
181
+ */
182
+ export async function findListIDByName(workspaceService, listName) {
183
+ // Use workspace service to find the list in the hierarchy
184
+ const hierarchy = await workspaceService.getWorkspaceHierarchy();
185
+ const listInfo = workspaceService.findIDByNameInHierarchy(hierarchy, listName, 'list');
186
+ if (!listInfo)
187
+ return null;
188
+ return { id: listInfo.id, name: listName };
189
+ }
190
+ /**
191
+ * Handler for the create_list tool
192
+ * Creates a new list directly in a space
193
+ */
194
+ export async function handleCreateList(parameters) {
195
+ const { name, spaceId, spaceName, content, dueDate, priority, assignee, status } = parameters;
196
+ // Validate required fields
197
+ if (!name) {
198
+ throw new Error("List name is required");
199
+ }
200
+ let targetSpaceId = spaceId;
201
+ // If no spaceId but spaceName is provided, look up the space ID
202
+ if (!targetSpaceId && spaceName) {
203
+ const spaceIdResult = await workspaceService.findSpaceIDByName(spaceName);
204
+ if (!spaceIdResult) {
205
+ throw new Error(`Space "${spaceName}" not found`);
206
+ }
207
+ targetSpaceId = spaceIdResult;
208
+ }
209
+ if (!targetSpaceId) {
210
+ throw new Error("Either spaceId or spaceName must be provided");
211
+ }
212
+ // Prepare list data
213
+ const listData = {
214
+ name
215
+ };
216
+ // Add optional fields if provided
217
+ if (content)
218
+ listData.content = content;
219
+ if (dueDate)
220
+ listData.due_date = parseInt(dueDate);
221
+ if (priority)
222
+ listData.priority = priority;
223
+ if (assignee)
224
+ listData.assignee = assignee;
225
+ if (status)
226
+ listData.status = status;
227
+ try {
228
+ // Create the list
229
+ const newList = await listService.createList(targetSpaceId, listData);
230
+ return {
231
+ content: [{
232
+ type: "text",
233
+ text: JSON.stringify({
234
+ id: newList.id,
235
+ name: newList.name,
236
+ content: newList.content,
237
+ space: {
238
+ id: newList.space.id,
239
+ name: newList.space.name
240
+ },
241
+ message: `List "${newList.name}" created successfully`
242
+ }, null, 2)
243
+ }]
244
+ };
245
+ }
246
+ catch (error) {
247
+ throw new Error(`Failed to create list: ${error.message}`);
248
+ }
249
+ }
250
+ /**
251
+ * Handler for the create_list_in_folder tool
252
+ * Creates a new list inside a folder
253
+ */
254
+ export async function handleCreateListInFolder(parameters) {
255
+ const { name, folderId, folderName, spaceId, spaceName, content, status } = parameters;
256
+ // Validate required fields
257
+ if (!name) {
258
+ throw new Error("List name is required");
259
+ }
260
+ let targetFolderId = folderId;
261
+ // If no folderId but folderName is provided, look up the folder ID
262
+ if (!targetFolderId && folderName) {
263
+ let targetSpaceId = spaceId;
264
+ // If no spaceId provided but spaceName is, look up the space ID first
265
+ if (!targetSpaceId && spaceName) {
266
+ const spaceIdResult = await workspaceService.findSpaceByName(spaceName);
267
+ if (!spaceIdResult) {
268
+ throw new Error(`Space "${spaceName}" not found`);
269
+ }
270
+ targetSpaceId = spaceIdResult.id;
271
+ }
272
+ if (!targetSpaceId) {
273
+ throw new Error("When using folderName to identify a folder, you must also provide either spaceId or spaceName to locate the correct folder. This is because folder names might not be unique across different spaces.");
274
+ }
275
+ // Find the folder in the workspace hierarchy
276
+ const hierarchy = await workspaceService.getWorkspaceHierarchy();
277
+ const folderInfo = workspaceService.findIDByNameInHierarchy(hierarchy, folderName, 'folder');
278
+ if (!folderInfo) {
279
+ throw new Error(`Folder "${folderName}" not found in space`);
280
+ }
281
+ targetFolderId = folderInfo.id;
282
+ }
283
+ if (!targetFolderId) {
284
+ throw new Error("Either folderId or folderName must be provided");
285
+ }
286
+ // Prepare list data
287
+ const listData = {
288
+ name
289
+ };
290
+ // Add optional fields if provided
291
+ if (content)
292
+ listData.content = content;
293
+ if (status)
294
+ listData.status = status;
295
+ try {
296
+ // Create the list in the folder
297
+ const newList = await listService.createListInFolder(targetFolderId, listData);
298
+ return {
299
+ content: [{
300
+ type: "text",
301
+ text: JSON.stringify({
302
+ id: newList.id,
303
+ name: newList.name,
304
+ content: newList.content,
305
+ space: {
306
+ id: newList.space.id,
307
+ name: newList.space.name
308
+ },
309
+ message: `List "${newList.name}" created successfully in folder`
310
+ }, null, 2)
311
+ }]
312
+ };
313
+ }
314
+ catch (error) {
315
+ throw new Error(`Failed to create list in folder: ${error.message}`);
316
+ }
317
+ }
318
+ /**
319
+ * Handler for the get_list tool
320
+ * Retrieves details about a specific list
321
+ */
322
+ export async function handleGetList(parameters) {
323
+ const { listId, listName } = parameters;
324
+ let targetListId = listId;
325
+ // If no listId provided but listName is, look up the list ID
326
+ if (!targetListId && listName) {
327
+ const listResult = await findListIDByName(workspaceService, listName);
328
+ if (!listResult) {
329
+ throw new Error(`List "${listName}" not found`);
330
+ }
331
+ targetListId = listResult.id;
332
+ }
333
+ if (!targetListId) {
334
+ throw new Error("Either listId or listName must be provided");
335
+ }
336
+ try {
337
+ // Get the list
338
+ const list = await listService.getList(targetListId);
339
+ return {
340
+ content: [{
341
+ type: "text",
342
+ text: JSON.stringify({
343
+ id: list.id,
344
+ name: list.name,
345
+ content: list.content,
346
+ space: {
347
+ id: list.space.id,
348
+ name: list.space.name
349
+ },
350
+ status: list.status,
351
+ url: `https://app.clickup.com/${config.clickupTeamId}/v/l/${list.id}`
352
+ }, null, 2)
353
+ }]
354
+ };
355
+ }
356
+ catch (error) {
357
+ throw new Error(`Failed to retrieve list: ${error.message}`);
358
+ }
359
+ }
360
+ /**
361
+ * Handler for the update_list tool
362
+ * Updates an existing list's properties
363
+ */
364
+ export async function handleUpdateList(parameters) {
365
+ const { listId, listName, name, content, status } = parameters;
366
+ let targetListId = listId;
367
+ // If no listId provided but listName is, look up the list ID
368
+ if (!targetListId && listName) {
369
+ const listResult = await findListIDByName(workspaceService, listName);
370
+ if (!listResult) {
371
+ throw new Error(`List "${listName}" not found`);
372
+ }
373
+ targetListId = listResult.id;
374
+ }
375
+ if (!targetListId) {
376
+ throw new Error("Either listId or listName must be provided");
377
+ }
378
+ // Ensure at least one update field is provided
379
+ if (!name && !content && !status) {
380
+ throw new Error("At least one of name, content, or status must be provided for update");
381
+ }
382
+ // Prepare update data
383
+ const updateData = {};
384
+ if (name)
385
+ updateData.name = name;
386
+ if (content)
387
+ updateData.content = content;
388
+ if (status)
389
+ updateData.status = status;
390
+ try {
391
+ // Update the list
392
+ const updatedList = await listService.updateList(targetListId, updateData);
393
+ return {
394
+ content: [{
395
+ type: "text",
396
+ text: JSON.stringify({
397
+ id: updatedList.id,
398
+ name: updatedList.name,
399
+ content: updatedList.content,
400
+ space: {
401
+ id: updatedList.space.id,
402
+ name: updatedList.space.name
403
+ },
404
+ status: updatedList.status,
405
+ url: `https://app.clickup.com/${config.clickupTeamId}/v/l/${updatedList.id}`,
406
+ message: `List "${updatedList.name}" updated successfully`
407
+ }, null, 2)
408
+ }]
409
+ };
410
+ }
411
+ catch (error) {
412
+ throw new Error(`Failed to update list: ${error.message}`);
413
+ }
414
+ }
415
+ /**
416
+ * Handler for the delete_list tool
417
+ * Permanently removes a list from the workspace
418
+ */
419
+ export async function handleDeleteList(parameters) {
420
+ const { listId, listName } = parameters;
421
+ let targetListId = listId;
422
+ // If no listId provided but listName is, look up the list ID
423
+ if (!targetListId && listName) {
424
+ const listResult = await findListIDByName(workspaceService, listName);
425
+ if (!listResult) {
426
+ throw new Error(`List "${listName}" not found`);
427
+ }
428
+ targetListId = listResult.id;
429
+ }
430
+ if (!targetListId) {
431
+ throw new Error("Either listId or listName must be provided");
432
+ }
433
+ try {
434
+ // Get list details before deletion for confirmation message
435
+ const list = await listService.getList(targetListId);
436
+ const listName = list.name;
437
+ // Delete the list
438
+ await listService.deleteList(targetListId);
439
+ return {
440
+ content: [{
441
+ type: "text",
442
+ text: JSON.stringify({
443
+ message: `List "${listName}" deleted successfully`,
444
+ url: `https://app.clickup.com/${config.clickupTeamId}/v/l/${targetListId}`
445
+ }, null, 2)
446
+ }]
447
+ };
448
+ }
449
+ catch (error) {
450
+ throw new Error(`Failed to delete list: ${error.message}`);
451
+ }
452
+ }