@taazkareem/clickup-mcp-server 0.4.54 → 0.4.57
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/Dockerfile +0 -4
- package/README.md +11 -1
- package/build/index.js +480 -62
- package/build/services/clickup.js +52 -24
- package/package.json +3 -5
package/Dockerfile
CHANGED
|
@@ -31,10 +31,6 @@ COPY --from=builder /app/node_modules ./node_modules
|
|
|
31
31
|
# Copy the entrypoint script if necessary
|
|
32
32
|
COPY --from=builder /app/package.json ./
|
|
33
33
|
|
|
34
|
-
# Set environment variables - don't hardcode values
|
|
35
|
-
ENV CLICKUP_API_KEY=""
|
|
36
|
-
ENV CLICKUP_TEAM_ID=""
|
|
37
|
-
|
|
38
34
|
# Expose the desired port (if the server binds to a port)
|
|
39
35
|
EXPOSE 8080
|
|
40
36
|
|
package/README.md
CHANGED
|
@@ -21,6 +21,9 @@ npx -y @taazkareem/clickup-mcp-server \
|
|
|
21
21
|
4. Replace the credentials and click Save
|
|
22
22
|
5. Use Natural Language to interact with your ClickUp Workspace!
|
|
23
23
|
|
|
24
|
+
## Smithery
|
|
25
|
+
[](https://smithery.ai/server/@TaazKareem/clickup-mcp-server)
|
|
26
|
+
|
|
24
27
|
## Features
|
|
25
28
|
|
|
26
29
|
- 🎯 **Task Management**
|
|
@@ -32,9 +35,10 @@ npx -y @taazkareem/clickup-mcp-server \
|
|
|
32
35
|
- 📂 **Workspace Organization**
|
|
33
36
|
- Complete workspace hierarchy (spaces, folders, lists)
|
|
34
37
|
- Tree structure with clear relationships
|
|
35
|
-
-
|
|
38
|
+
- Full CRUD operations for workspace components
|
|
36
39
|
- Efficient path-based navigation
|
|
37
40
|
|
|
41
|
+
|
|
38
42
|
- 🔄 **Integration Features**
|
|
39
43
|
- Name or ID-based item lookup
|
|
40
44
|
- Case-insensitive name matching
|
|
@@ -63,6 +67,12 @@ npx -y @taazkareem/clickup-mcp-server \
|
|
|
63
67
|
| [create_list](docs/api-reference.md#list-management) | Create list in space | `name`, `spaceId`/`spaceName` |
|
|
64
68
|
| [create_folder](docs/api-reference.md#folder-management) | Create folder | `name`, `spaceId`/`spaceName` |
|
|
65
69
|
| [create_list_in_folder](docs/api-reference.md#list-management) | Create list in folder | `name`, `folderId`/`folderName` |
|
|
70
|
+
| [get_folder](docs/api-reference.md#folder-management) | Get folder details | `folderId`/`folderName` |
|
|
71
|
+
| [update_folder](docs/api-reference.md#folder-management) | Update folder properties | `folderId`/`folderName` |
|
|
72
|
+
| [delete_folder](docs/api-reference.md#folder-management) | Delete folder | `folderId`/`folderName` |
|
|
73
|
+
| [get_list](docs/api-reference.md#list-management) | Get list details | `listId`/`listName` |
|
|
74
|
+
| [update_list](docs/api-reference.md#list-management) | Update list properties | `listId`/`listName` |
|
|
75
|
+
| [delete_list](docs/api-reference.md#list-management) | Delete list | `listId`/`listName` |
|
|
66
76
|
|
|
67
77
|
See [full documentation](docs/api-reference.md) for optional parameters and advanced usage.
|
|
68
78
|
|
package/build/index.js
CHANGED
|
@@ -72,17 +72,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
74
|
name: "create_task",
|
|
75
|
-
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.
|
|
75
|
+
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.",
|
|
76
76
|
inputSchema: {
|
|
77
77
|
type: "object",
|
|
78
78
|
properties: {
|
|
79
79
|
listId: {
|
|
80
80
|
type: "string",
|
|
81
|
-
description: "ID of the list to create the task in (optional if using listName instead)"
|
|
81
|
+
description: "ID of the list to create the task in (optional if using listName instead). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
82
82
|
},
|
|
83
83
|
listName: {
|
|
84
84
|
type: "string",
|
|
85
|
-
description: "Name of the list to create the task in - will automatically find the list by name (optional if using listId instead)"
|
|
85
|
+
description: "Name of the list to create the task in - will automatically find the list by name (optional if using listId instead). Only use this if you don't already have the list ID from previous responses."
|
|
86
86
|
},
|
|
87
87
|
name: {
|
|
88
88
|
type: "string",
|
|
@@ -109,22 +109,22 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
109
109
|
description: "Due date of the task (Unix timestamp in milliseconds). Convert dates to this format before submitting."
|
|
110
110
|
}
|
|
111
111
|
},
|
|
112
|
-
required: [
|
|
112
|
+
required: []
|
|
113
113
|
}
|
|
114
114
|
},
|
|
115
115
|
{
|
|
116
116
|
name: "create_bulk_tasks",
|
|
117
|
-
description: "Create multiple tasks in a ClickUp list simultaneously. Use this tool when you need to add several related tasks in one operation.
|
|
117
|
+
description: "Create multiple tasks in a ClickUp list simultaneously. Use this tool when you need to add several related tasks in one operation. Before calling, check if you already have the necessary list ID from previous responses in the conversation, as this avoids redundant lookups. More efficient than creating tasks one by one for batch operations.",
|
|
118
118
|
inputSchema: {
|
|
119
119
|
type: "object",
|
|
120
120
|
properties: {
|
|
121
121
|
listId: {
|
|
122
122
|
type: "string",
|
|
123
|
-
description: "ID of the list to create the tasks in (optional if using listName instead)"
|
|
123
|
+
description: "ID of the list to create the tasks in (optional if using listName instead). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
124
124
|
},
|
|
125
125
|
listName: {
|
|
126
126
|
type: "string",
|
|
127
|
-
description: "Name of the list to create the tasks in - will automatically find the list by name (optional if using listId instead)"
|
|
127
|
+
description: "Name of the list to create the tasks in - will automatically find the list by name (optional if using listId instead). Only use this if you don't already have the list ID from previous responses."
|
|
128
128
|
},
|
|
129
129
|
tasks: {
|
|
130
130
|
type: "array",
|
|
@@ -164,26 +164,26 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
164
164
|
description: "Array of user IDs to assign to the task"
|
|
165
165
|
}
|
|
166
166
|
},
|
|
167
|
-
required: [
|
|
167
|
+
required: []
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
},
|
|
171
|
-
required: [
|
|
171
|
+
required: []
|
|
172
172
|
}
|
|
173
173
|
},
|
|
174
174
|
{
|
|
175
175
|
name: "create_list",
|
|
176
|
-
description: "Create a new list directly in a ClickUp space. Use this tool when you need a top-level list not nested inside a folder.
|
|
176
|
+
description: "Create a new list directly in a ClickUp space. Use this tool when you need a top-level list not nested inside a folder. Before calling, check if you already have the necessary space ID from previous responses in the conversation, as this avoids redundant lookups. For creating lists inside folders, use create_list_in_folder instead.",
|
|
177
177
|
inputSchema: {
|
|
178
178
|
type: "object",
|
|
179
179
|
properties: {
|
|
180
180
|
spaceId: {
|
|
181
181
|
type: "string",
|
|
182
|
-
description: "ID of the space to create the list in (optional if using spaceName instead)"
|
|
182
|
+
description: "ID of the space to create the list in (optional if using spaceName instead). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
183
183
|
},
|
|
184
184
|
spaceName: {
|
|
185
185
|
type: "string",
|
|
186
|
-
description: "Name of the space to create the list in - will automatically find the space by name (optional if using spaceId instead)"
|
|
186
|
+
description: "Name of the space to create the list in - will automatically find the space by name (optional if using spaceId instead). Only use this if you don't already have the space ID from previous responses."
|
|
187
187
|
},
|
|
188
188
|
name: {
|
|
189
189
|
type: "string",
|
|
@@ -210,22 +210,22 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
210
210
|
description: "Status of the list"
|
|
211
211
|
}
|
|
212
212
|
},
|
|
213
|
-
required: [
|
|
213
|
+
required: []
|
|
214
214
|
}
|
|
215
215
|
},
|
|
216
216
|
{
|
|
217
217
|
name: "create_folder",
|
|
218
|
-
description: "Create a new folder in a ClickUp space for organizing related lists. Use this tool when you need to group multiple lists together.
|
|
218
|
+
description: "Create a new folder in a ClickUp space for organizing related lists. Use this tool when you need to group multiple lists together. Before calling, check if you already have the necessary space ID from previous responses in the conversation, as this avoids redundant lookups. After creating a folder, you can add lists to it using create_list_in_folder.",
|
|
219
219
|
inputSchema: {
|
|
220
220
|
type: "object",
|
|
221
221
|
properties: {
|
|
222
222
|
spaceId: {
|
|
223
223
|
type: "string",
|
|
224
|
-
description: "ID of the space to create the folder in (optional if using spaceName instead)"
|
|
224
|
+
description: "ID of the space to create the folder in (optional if using spaceName instead). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
225
225
|
},
|
|
226
226
|
spaceName: {
|
|
227
227
|
type: "string",
|
|
228
|
-
description: "Name of the space to create the folder in - will automatically find the space by name (optional if using spaceId instead)"
|
|
228
|
+
description: "Name of the space to create the folder in - will automatically find the space by name (optional if using spaceId instead). Only use this if you don't already have the space ID from previous responses."
|
|
229
229
|
},
|
|
230
230
|
name: {
|
|
231
231
|
type: "string",
|
|
@@ -236,30 +236,30 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
236
236
|
description: "Whether to override space statuses with folder-specific statuses"
|
|
237
237
|
}
|
|
238
238
|
},
|
|
239
|
-
required: [
|
|
239
|
+
required: []
|
|
240
240
|
}
|
|
241
241
|
},
|
|
242
242
|
{
|
|
243
243
|
name: "create_list_in_folder",
|
|
244
|
-
description: "Create a new list within a ClickUp folder. Use this tool when you need to add a list to an existing folder structure.
|
|
244
|
+
description: "Create a new list within a ClickUp folder. Use this tool when you need to add a list to an existing folder structure. Before calling, check if you already have the necessary folder ID and space ID from previous responses in the conversation, as this avoids redundant lookups. For top-level lists not in folders, use create_list instead.",
|
|
245
245
|
inputSchema: {
|
|
246
246
|
type: "object",
|
|
247
247
|
properties: {
|
|
248
248
|
folderId: {
|
|
249
249
|
type: "string",
|
|
250
|
-
description: "ID of the folder to create the list in (optional if using folderName instead)"
|
|
250
|
+
description: "ID of the folder to create the list in (optional if using folderName instead). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
251
251
|
},
|
|
252
252
|
folderName: {
|
|
253
253
|
type: "string",
|
|
254
|
-
description: "Name of the folder to create the list in - will automatically find the folder by name (optional if using folderId instead)"
|
|
254
|
+
description: "Name of the folder to create the list in - will automatically find the folder by name (optional if using folderId instead). Only use this if you don't already have the folder ID from previous responses."
|
|
255
255
|
},
|
|
256
256
|
spaceId: {
|
|
257
257
|
type: "string",
|
|
258
|
-
description: "ID of the space containing the folder (optional if using spaceName instead)"
|
|
258
|
+
description: "ID of the space containing the folder (optional if using spaceName instead). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
259
259
|
},
|
|
260
260
|
spaceName: {
|
|
261
261
|
type: "string",
|
|
262
|
-
description: "Name of the space containing the folder - will automatically find the space by name (optional if using spaceId instead)"
|
|
262
|
+
description: "Name of the space containing the folder - will automatically find the space by name (optional if using spaceId instead). Only use this if you don't already have the space ID from previous responses."
|
|
263
263
|
},
|
|
264
264
|
name: {
|
|
265
265
|
type: "string",
|
|
@@ -274,22 +274,22 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
274
274
|
description: "Status of the list (uses folder default if not specified)"
|
|
275
275
|
}
|
|
276
276
|
},
|
|
277
|
-
required: [
|
|
277
|
+
required: []
|
|
278
278
|
}
|
|
279
279
|
},
|
|
280
280
|
{
|
|
281
281
|
name: "move_task",
|
|
282
|
-
description: "Move an existing task from its current list to a different list. Use this tool when you need to relocate a task within your workspace hierarchy.
|
|
282
|
+
description: "Move an existing task from its current list to a different list. Use this tool when you need to relocate a task within your workspace hierarchy. Before calling, check if you already have the necessary task ID and list ID from previous responses in the conversation, as this avoids redundant lookups. Task statuses may be reset if the destination list uses different status options.",
|
|
283
283
|
inputSchema: {
|
|
284
284
|
type: "object",
|
|
285
285
|
properties: {
|
|
286
286
|
taskId: {
|
|
287
287
|
type: "string",
|
|
288
|
-
description: "ID of the task to move (optional if using taskName instead)"
|
|
288
|
+
description: "ID of the task to move (optional if using taskName instead). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
289
289
|
},
|
|
290
290
|
taskName: {
|
|
291
291
|
type: "string",
|
|
292
|
-
description: "Name of the task to move - will automatically find the task by name (optional if using taskId instead)"
|
|
292
|
+
description: "Name of the task to move - will automatically find the task by name (optional if using taskId instead). Only use this if you don't already have the task ID from previous responses."
|
|
293
293
|
},
|
|
294
294
|
sourceListName: {
|
|
295
295
|
type: "string",
|
|
@@ -297,29 +297,29 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
297
297
|
},
|
|
298
298
|
listId: {
|
|
299
299
|
type: "string",
|
|
300
|
-
description: "ID of the destination list (optional if using listName instead)"
|
|
300
|
+
description: "ID of the destination list (optional if using listName instead). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
301
301
|
},
|
|
302
302
|
listName: {
|
|
303
303
|
type: "string",
|
|
304
|
-
description: "Name of the destination list - will automatically find the list by name (optional if using listId instead)"
|
|
304
|
+
description: "Name of the destination list - will automatically find the list by name (optional if using listId instead). Only use this if you don't already have the list ID from previous responses."
|
|
305
305
|
}
|
|
306
306
|
},
|
|
307
|
-
required: [
|
|
307
|
+
required: []
|
|
308
308
|
}
|
|
309
309
|
},
|
|
310
310
|
{
|
|
311
311
|
name: "duplicate_task",
|
|
312
|
-
description: "Create a copy of an existing task in the same or different list. Use this tool when you need to replicate a task's content and properties.
|
|
312
|
+
description: "Create a copy of an existing task in the same or different list. Use this tool when you need to replicate a task's content and properties. Before calling, check if you already have the necessary task ID and list ID from previous responses in the conversation, as this avoids redundant lookups. The duplicate will preserve name, description, priority, and other attributes from the original task.",
|
|
313
313
|
inputSchema: {
|
|
314
314
|
type: "object",
|
|
315
315
|
properties: {
|
|
316
316
|
taskId: {
|
|
317
317
|
type: "string",
|
|
318
|
-
description: "ID of the task to duplicate (optional if using taskName instead)"
|
|
318
|
+
description: "ID of the task to duplicate (optional if using taskName instead). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
319
319
|
},
|
|
320
320
|
taskName: {
|
|
321
321
|
type: "string",
|
|
322
|
-
description: "Name of the task to duplicate - will automatically find the task by name (optional if using taskId instead)"
|
|
322
|
+
description: "Name of the task to duplicate - will automatically find the task by name (optional if using taskId instead). Only use this if you don't already have the task ID from previous responses."
|
|
323
323
|
},
|
|
324
324
|
sourceListName: {
|
|
325
325
|
type: "string",
|
|
@@ -327,29 +327,29 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
327
327
|
},
|
|
328
328
|
listId: {
|
|
329
329
|
type: "string",
|
|
330
|
-
description: "ID of the list to create the duplicate in (optional if using listName instead)"
|
|
330
|
+
description: "ID of the list to create the duplicate in (optional if using listName instead). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
331
331
|
},
|
|
332
332
|
listName: {
|
|
333
333
|
type: "string",
|
|
334
|
-
description: "Name of the list to create the duplicate in - will automatically find the list by name (optional if using listId instead)"
|
|
334
|
+
description: "Name of the list to create the duplicate in - will automatically find the list by name (optional if using listId instead). Only use this if you don't already have the list ID from previous responses."
|
|
335
335
|
}
|
|
336
336
|
},
|
|
337
|
-
required: [
|
|
337
|
+
required: []
|
|
338
338
|
}
|
|
339
339
|
},
|
|
340
340
|
{
|
|
341
341
|
name: "update_task",
|
|
342
|
-
description: "Modify the properties of an existing task. Use this tool when you need to change a task's name, description, status, priority, or due date.
|
|
342
|
+
description: "Modify the properties of an existing task. Use this tool when you need to change a task's name, description, status, priority, or due date. Before calling, check if you already have the necessary task ID from previous responses in the conversation, as this avoids redundant lookups. Only the fields you specify will be updated; other fields will remain unchanged.",
|
|
343
343
|
inputSchema: {
|
|
344
344
|
type: "object",
|
|
345
345
|
properties: {
|
|
346
346
|
taskId: {
|
|
347
347
|
type: "string",
|
|
348
|
-
description: "ID of the task to update (optional if using taskName instead)"
|
|
348
|
+
description: "ID of the task to update (optional if using taskName instead). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
349
349
|
},
|
|
350
350
|
taskName: {
|
|
351
351
|
type: "string",
|
|
352
|
-
description: "Name of the task to update - will automatically find the task by name (optional if using taskId instead)"
|
|
352
|
+
description: "Name of the task to update - will automatically find the task by name (optional if using taskId instead). Only use this if you don't already have the task ID from previous responses."
|
|
353
353
|
},
|
|
354
354
|
listName: {
|
|
355
355
|
type: "string",
|
|
@@ -363,35 +363,37 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
363
363
|
type: "string",
|
|
364
364
|
description: "New plain text description for the task"
|
|
365
365
|
},
|
|
366
|
+
markdown_description: {
|
|
367
|
+
type: "string",
|
|
368
|
+
description: "New markdown formatted description for the task. If provided, this takes precedence over description"
|
|
369
|
+
},
|
|
366
370
|
status: {
|
|
367
371
|
type: "string",
|
|
368
372
|
description: "New status for the task (must be a valid status in the task's list)"
|
|
369
373
|
},
|
|
370
374
|
priority: {
|
|
371
|
-
type: "number",
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
type: "string",
|
|
376
|
-
description: "New due date for the task (Unix timestamp in milliseconds). Convert dates to this format before submitting."
|
|
375
|
+
type: ["number", "null"],
|
|
376
|
+
enum: [1, 2, 3, 4, null],
|
|
377
|
+
description: "New priority for the task (1-4 or null), where 1 is urgent/highest priority and 4 is lowest priority. Set to null to clear priority.",
|
|
378
|
+
optional: true
|
|
377
379
|
}
|
|
378
380
|
},
|
|
379
|
-
required: [
|
|
381
|
+
required: []
|
|
380
382
|
}
|
|
381
383
|
},
|
|
382
384
|
{
|
|
383
385
|
name: "get_tasks",
|
|
384
|
-
description: "Retrieve tasks from a ClickUp list with optional filtering capabilities. Use this tool when you need to see existing tasks or analyze your current workload.
|
|
386
|
+
description: "Retrieve tasks from a ClickUp list with optional filtering capabilities. Use this tool when you need to see existing tasks or analyze your current workload. Before calling, check if you already have the necessary list ID from previous responses in the conversation, as this avoids redundant lookups. Results can be filtered by status, assignees, dates, and more.",
|
|
385
387
|
inputSchema: {
|
|
386
388
|
type: "object",
|
|
387
389
|
properties: {
|
|
388
390
|
listId: {
|
|
389
391
|
type: "string",
|
|
390
|
-
description: "ID of the list to get tasks from (optional if using listName instead)"
|
|
392
|
+
description: "ID of the list to get tasks from (optional if using listName instead). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
391
393
|
},
|
|
392
394
|
listName: {
|
|
393
395
|
type: "string",
|
|
394
|
-
description: "Name of the list to get tasks from - will automatically find the list by name (optional if using listId instead)"
|
|
396
|
+
description: "Name of the list to get tasks from - will automatically find the list by name (optional if using listId instead). Only use this if you don't already have the list ID from previous responses."
|
|
395
397
|
},
|
|
396
398
|
archived: {
|
|
397
399
|
type: "boolean",
|
|
@@ -456,51 +458,203 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
456
458
|
description: "Object with custom field IDs as keys and desired values for filtering"
|
|
457
459
|
}
|
|
458
460
|
},
|
|
459
|
-
required: [
|
|
461
|
+
required: []
|
|
460
462
|
}
|
|
461
463
|
},
|
|
462
464
|
{
|
|
463
465
|
name: "get_task",
|
|
464
|
-
description: "Retrieve comprehensive details about a specific ClickUp task. Use this tool when you need in-depth information about a particular task, including its description, custom fields, attachments, and other metadata.
|
|
466
|
+
description: "Retrieve comprehensive details about a specific ClickUp task. Use this tool when you need in-depth information about a particular task, including its description, custom fields, attachments, and other metadata. Before calling, check if you already have the necessary task ID from previous responses in the conversation, as this avoids redundant lookups.",
|
|
465
467
|
inputSchema: {
|
|
466
468
|
type: "object",
|
|
467
469
|
properties: {
|
|
468
470
|
taskId: {
|
|
469
471
|
type: "string",
|
|
470
|
-
description: "ID of the task to retrieve (optional if using taskName instead)"
|
|
472
|
+
description: "ID of the task to retrieve (optional if using taskName instead). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
471
473
|
},
|
|
472
474
|
taskName: {
|
|
473
475
|
type: "string",
|
|
474
|
-
description: "Name of the task to retrieve - will automatically find the task by name (optional if using taskId instead)"
|
|
476
|
+
description: "Name of the task to retrieve - will automatically find the task by name (optional if using taskId instead). Only use this if you don't already have the task ID from previous responses."
|
|
475
477
|
},
|
|
476
478
|
listName: {
|
|
477
479
|
type: "string",
|
|
478
480
|
description: "Optional: Name of the list to narrow down task search when multiple tasks have the same name"
|
|
479
481
|
}
|
|
480
482
|
},
|
|
481
|
-
required: [
|
|
483
|
+
required: []
|
|
482
484
|
}
|
|
483
485
|
},
|
|
484
486
|
{
|
|
485
487
|
name: "delete_task",
|
|
486
|
-
description: "Permanently remove a task from your ClickUp workspace. Use this tool with caution as deletion cannot be undone.
|
|
488
|
+
description: "Permanently remove a task from your ClickUp workspace. Use this tool with caution as deletion cannot be undone. Before calling, check if you already have the necessary task ID from previous responses in the conversation, as this avoids redundant lookups. For safety, the task ID is required.",
|
|
487
489
|
inputSchema: {
|
|
488
490
|
type: "object",
|
|
489
491
|
properties: {
|
|
490
492
|
taskId: {
|
|
491
493
|
type: "string",
|
|
492
|
-
description: "ID of the task to delete - this is required for safety to prevent accidental deletions"
|
|
494
|
+
description: "ID of the task to delete - this is required for safety to prevent accidental deletions. If you have this ID from a previous response, use it directly."
|
|
493
495
|
},
|
|
494
496
|
taskName: {
|
|
495
497
|
type: "string",
|
|
496
|
-
description: "Name of the task to delete - will automatically find the task by name (optional if using taskId instead)"
|
|
498
|
+
description: "Name of the task to delete - will automatically find the task by name (optional if using taskId instead). Only use this if you don't already have the task ID from previous responses."
|
|
497
499
|
},
|
|
498
500
|
listName: {
|
|
499
501
|
type: "string",
|
|
500
502
|
description: "Optional: Name of the list to narrow down task search when multiple tasks have the same name"
|
|
501
503
|
}
|
|
502
504
|
},
|
|
503
|
-
required: [
|
|
505
|
+
required: []
|
|
506
|
+
}
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
name: "get_folder",
|
|
510
|
+
description: "Retrieve details about a specific ClickUp folder including its name, status, and other metadata. Before calling, check if you already have the necessary folder ID from previous responses in the conversation history, as this avoids redundant lookups. Helps you understand folder structure before creating or updating lists.",
|
|
511
|
+
inputSchema: {
|
|
512
|
+
type: "object",
|
|
513
|
+
properties: {
|
|
514
|
+
folderId: {
|
|
515
|
+
type: "string",
|
|
516
|
+
description: "ID of the folder to retrieve (optional if using folderName instead). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
517
|
+
},
|
|
518
|
+
folderName: {
|
|
519
|
+
type: "string",
|
|
520
|
+
description: "Name of the folder to retrieve - will automatically find the folder by name (optional if using folderId instead). Only use this if you don't already have the folder ID from previous responses."
|
|
521
|
+
},
|
|
522
|
+
spaceId: {
|
|
523
|
+
type: "string",
|
|
524
|
+
description: "ID of the space containing the folder (optional if using spaceName instead, and only needed when using folderName). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
525
|
+
},
|
|
526
|
+
spaceName: {
|
|
527
|
+
type: "string",
|
|
528
|
+
description: "Name of the space containing the folder (optional if using spaceId instead, and only needed when using folderName). Only use this if you don't already have the space ID from previous responses."
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
required: []
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
name: "update_folder",
|
|
536
|
+
description: "Modify an existing ClickUp folder's properties, such as name or status settings. Before calling, check if you already have the necessary folder ID from previous responses in the conversation history, as this avoids redundant lookups. Use when reorganizing or renaming workspace elements.",
|
|
537
|
+
inputSchema: {
|
|
538
|
+
type: "object",
|
|
539
|
+
properties: {
|
|
540
|
+
folderId: {
|
|
541
|
+
type: "string",
|
|
542
|
+
description: "ID of the folder to update (optional if using folderName instead). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
543
|
+
},
|
|
544
|
+
folderName: {
|
|
545
|
+
type: "string",
|
|
546
|
+
description: "Name of the folder to update - will automatically find the folder by name (optional if using folderId instead). Only use this if you don't already have the folder ID from previous responses."
|
|
547
|
+
},
|
|
548
|
+
spaceId: {
|
|
549
|
+
type: "string",
|
|
550
|
+
description: "ID of the space containing the folder (optional if using spaceName instead, and only needed when using folderName). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
551
|
+
},
|
|
552
|
+
spaceName: {
|
|
553
|
+
type: "string",
|
|
554
|
+
description: "Name of the space containing the folder (optional if using spaceId instead, and only needed when using folderName). Only use this if you don't already have the space ID from previous responses."
|
|
555
|
+
},
|
|
556
|
+
name: {
|
|
557
|
+
type: "string",
|
|
558
|
+
description: "New name for the folder"
|
|
559
|
+
},
|
|
560
|
+
override_statuses: {
|
|
561
|
+
type: "boolean",
|
|
562
|
+
description: "Whether to override space statuses with folder-specific statuses"
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
required: []
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
name: "delete_folder",
|
|
570
|
+
description: "Permanently remove a folder from your ClickUp workspace. Use with caution as deletion cannot be undone and will remove all lists and tasks within the folder. Before calling, check if you already have the necessary folder ID from previous responses in the conversation history, as this avoids redundant lookups.",
|
|
571
|
+
inputSchema: {
|
|
572
|
+
type: "object",
|
|
573
|
+
properties: {
|
|
574
|
+
folderId: {
|
|
575
|
+
type: "string",
|
|
576
|
+
description: "ID of the folder to delete (optional if using folderName instead). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
577
|
+
},
|
|
578
|
+
folderName: {
|
|
579
|
+
type: "string",
|
|
580
|
+
description: "Name of the folder to delete - will automatically find the folder by name (optional if using folderId instead). Only use this if you don't already have the folder ID from previous responses."
|
|
581
|
+
},
|
|
582
|
+
spaceId: {
|
|
583
|
+
type: "string",
|
|
584
|
+
description: "ID of the space containing the folder (optional if using spaceName instead, and only needed when using folderName). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
585
|
+
},
|
|
586
|
+
spaceName: {
|
|
587
|
+
type: "string",
|
|
588
|
+
description: "Name of the space containing the folder (optional if using spaceId instead, and only needed when using folderName). Only use this if you don't already have the space ID from previous responses."
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
required: []
|
|
592
|
+
}
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
name: "get_list",
|
|
596
|
+
description: "Retrieve details about a specific ClickUp list including its name, content, status options, and other metadata. Before calling, check if you already have the necessary list ID from previous responses in the conversation history, as this avoids redundant lookups. Useful to understand list structure before creating or updating tasks.",
|
|
597
|
+
inputSchema: {
|
|
598
|
+
type: "object",
|
|
599
|
+
properties: {
|
|
600
|
+
listId: {
|
|
601
|
+
type: "string",
|
|
602
|
+
description: "ID of the list to retrieve (optional if using listName instead). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
603
|
+
},
|
|
604
|
+
listName: {
|
|
605
|
+
type: "string",
|
|
606
|
+
description: "Name of the list to retrieve - will automatically find the list by name (optional if using listId instead). Only use this if you don't already have the list ID from previous responses."
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
required: []
|
|
610
|
+
}
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
name: "update_list",
|
|
614
|
+
description: "Modify an existing ClickUp list's properties, such as name, content, or status options. Before calling, check if you already have the necessary list ID from previous responses in the conversation history, as this avoids redundant lookups. Use when reorganizing or renaming workspace elements.",
|
|
615
|
+
inputSchema: {
|
|
616
|
+
type: "object",
|
|
617
|
+
properties: {
|
|
618
|
+
listId: {
|
|
619
|
+
type: "string",
|
|
620
|
+
description: "ID of the list to update (optional if using listName instead). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
621
|
+
},
|
|
622
|
+
listName: {
|
|
623
|
+
type: "string",
|
|
624
|
+
description: "Name of the list to update - will automatically find the list by name (optional if using listId instead). Only use this if you don't already have the list ID from previous responses."
|
|
625
|
+
},
|
|
626
|
+
name: {
|
|
627
|
+
type: "string",
|
|
628
|
+
description: "New name for the list"
|
|
629
|
+
},
|
|
630
|
+
content: {
|
|
631
|
+
type: "string",
|
|
632
|
+
description: "New description or content for the list"
|
|
633
|
+
},
|
|
634
|
+
status: {
|
|
635
|
+
type: "string",
|
|
636
|
+
description: "New status for the list"
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
required: []
|
|
640
|
+
}
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
name: "delete_list",
|
|
644
|
+
description: "Permanently remove a list from your ClickUp workspace. Use with caution as deletion cannot be undone and will remove all tasks within the list. Before calling, check if you already have the necessary list ID from previous responses in the conversation history, as this avoids redundant lookups.",
|
|
645
|
+
inputSchema: {
|
|
646
|
+
type: "object",
|
|
647
|
+
properties: {
|
|
648
|
+
listId: {
|
|
649
|
+
type: "string",
|
|
650
|
+
description: "ID of the list to delete (optional if using listName instead). If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
651
|
+
},
|
|
652
|
+
listName: {
|
|
653
|
+
type: "string",
|
|
654
|
+
description: "Name of the list to delete - will automatically find the list by name (optional if using listId instead). Only use this if you don't already have the list ID from previous responses."
|
|
655
|
+
}
|
|
656
|
+
},
|
|
657
|
+
required: []
|
|
504
658
|
}
|
|
505
659
|
}
|
|
506
660
|
]
|
|
@@ -578,12 +732,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
578
732
|
}
|
|
579
733
|
case "create_bulk_tasks": {
|
|
580
734
|
const args = request.params.arguments;
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
}
|
|
584
|
-
if (!args.tasks || args.tasks.length === 0) {
|
|
735
|
+
// First validate tasks array
|
|
736
|
+
if (!args.tasks || !Array.isArray(args.tasks) || args.tasks.length === 0) {
|
|
585
737
|
throw new Error("tasks array is required and must not be empty");
|
|
586
738
|
}
|
|
739
|
+
// Validate each task has required fields
|
|
740
|
+
args.tasks.forEach((task, index) => {
|
|
741
|
+
if (!task.name) {
|
|
742
|
+
throw new Error(`Task at index ${index} is missing required field 'name'`);
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
// Get listId from name if needed
|
|
587
746
|
let listId = args.listId;
|
|
588
747
|
if (!listId && args.listName) {
|
|
589
748
|
const result = await clickup.findListIDByName(args.listName);
|
|
@@ -592,12 +751,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
592
751
|
}
|
|
593
752
|
listId = result.id;
|
|
594
753
|
}
|
|
754
|
+
// Now validate we have a listId
|
|
755
|
+
if (!listId) {
|
|
756
|
+
throw new Error("Either listId or listName must be provided");
|
|
757
|
+
}
|
|
595
758
|
const { listId: _, listName: __, tasks } = args;
|
|
596
759
|
const createdTasks = await clickup.createBulkTasks(listId, { tasks });
|
|
597
760
|
return {
|
|
598
761
|
content: [{
|
|
599
762
|
type: "text",
|
|
600
|
-
text:
|
|
763
|
+
text: JSON.stringify({
|
|
764
|
+
message: `Created ${createdTasks.length} tasks`,
|
|
765
|
+
tasks: createdTasks.map(task => ({
|
|
766
|
+
id: task.id,
|
|
767
|
+
name: task.name,
|
|
768
|
+
url: task.url
|
|
769
|
+
}))
|
|
770
|
+
}, null, 2)
|
|
601
771
|
}]
|
|
602
772
|
};
|
|
603
773
|
}
|
|
@@ -750,9 +920,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
750
920
|
}
|
|
751
921
|
case "update_task": {
|
|
752
922
|
const args = request.params.arguments;
|
|
923
|
+
// Require either taskId or taskName
|
|
753
924
|
if (!args.taskId && !args.taskName) {
|
|
754
925
|
throw new Error("Either taskId or taskName is required");
|
|
755
926
|
}
|
|
927
|
+
// Get taskId from taskName if needed
|
|
756
928
|
let taskId = args.taskId;
|
|
757
929
|
if (!taskId && args.taskName) {
|
|
758
930
|
const result = await clickup.findTaskByName(args.taskName, undefined, args.listName);
|
|
@@ -761,7 +933,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
761
933
|
}
|
|
762
934
|
taskId = result.id;
|
|
763
935
|
}
|
|
936
|
+
// Remove helper fields before updating
|
|
764
937
|
const { taskId: _, taskName: __, listName: ___, ...updateData } = args;
|
|
938
|
+
// Ensure priority is properly handled
|
|
939
|
+
if (updateData.priority !== undefined && updateData.priority !== null) {
|
|
940
|
+
const priority = Number(updateData.priority);
|
|
941
|
+
if (isNaN(priority) || ![1, 2, 3, 4].includes(priority)) {
|
|
942
|
+
throw new Error("Priority must be a number between 1 and 4, or null to clear priority");
|
|
943
|
+
}
|
|
944
|
+
updateData.priority = priority;
|
|
945
|
+
}
|
|
765
946
|
const task = await clickup.updateTask(taskId, updateData);
|
|
766
947
|
return {
|
|
767
948
|
content: [{
|
|
@@ -841,6 +1022,243 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
841
1022
|
}]
|
|
842
1023
|
};
|
|
843
1024
|
}
|
|
1025
|
+
case "get_folder": {
|
|
1026
|
+
const args = request.params.arguments;
|
|
1027
|
+
if (!args.folderId && !args.folderName) {
|
|
1028
|
+
throw new Error("Either folderId or folderName is required");
|
|
1029
|
+
}
|
|
1030
|
+
let folderId = args.folderId;
|
|
1031
|
+
if (!folderId && args.folderName) {
|
|
1032
|
+
// If we need to look up by name, we might need the space
|
|
1033
|
+
let spaceId = args.spaceId;
|
|
1034
|
+
if (!spaceId && args.spaceName) {
|
|
1035
|
+
const foundId = await clickup.findSpaceIDByName(args.spaceName);
|
|
1036
|
+
if (!foundId) {
|
|
1037
|
+
throw new Error(`Space with name "${args.spaceName}" not found`);
|
|
1038
|
+
}
|
|
1039
|
+
spaceId = foundId;
|
|
1040
|
+
}
|
|
1041
|
+
if (!spaceId) {
|
|
1042
|
+
// Try to find folder directly by name (will search across all spaces)
|
|
1043
|
+
const result = await clickup.findFolderIDByName(args.folderName);
|
|
1044
|
+
if (!result) {
|
|
1045
|
+
throw new Error(`Folder with name "${args.folderName}" not found`);
|
|
1046
|
+
}
|
|
1047
|
+
folderId = result.id;
|
|
1048
|
+
}
|
|
1049
|
+
else {
|
|
1050
|
+
// Look in a specific space
|
|
1051
|
+
const folder = await clickup.findFolderByName(spaceId, args.folderName);
|
|
1052
|
+
if (!folder) {
|
|
1053
|
+
throw new Error(`Folder with name "${args.folderName}" not found in specified space`);
|
|
1054
|
+
}
|
|
1055
|
+
folderId = folder.id;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
// Ensure folderId is defined at this point
|
|
1059
|
+
if (!folderId) {
|
|
1060
|
+
throw new Error("Failed to determine folder ID");
|
|
1061
|
+
}
|
|
1062
|
+
const folder = await clickup.getFolder(folderId);
|
|
1063
|
+
return {
|
|
1064
|
+
content: [{
|
|
1065
|
+
type: "text",
|
|
1066
|
+
text: JSON.stringify(folder, null, 2)
|
|
1067
|
+
}]
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
case "update_folder": {
|
|
1071
|
+
const args = request.params.arguments;
|
|
1072
|
+
if (!args.folderId && !args.folderName) {
|
|
1073
|
+
throw new Error("Either folderId or folderName is required");
|
|
1074
|
+
}
|
|
1075
|
+
let folderId = args.folderId;
|
|
1076
|
+
if (!folderId && args.folderName) {
|
|
1077
|
+
// If we need to look up by name, we might need the space
|
|
1078
|
+
let spaceId = args.spaceId;
|
|
1079
|
+
if (!spaceId && args.spaceName) {
|
|
1080
|
+
const foundId = await clickup.findSpaceIDByName(args.spaceName);
|
|
1081
|
+
if (!foundId) {
|
|
1082
|
+
throw new Error(`Space with name "${args.spaceName}" not found`);
|
|
1083
|
+
}
|
|
1084
|
+
spaceId = foundId;
|
|
1085
|
+
}
|
|
1086
|
+
if (!spaceId) {
|
|
1087
|
+
// Try to find folder directly by name (will search across all spaces)
|
|
1088
|
+
const result = await clickup.findFolderIDByName(args.folderName);
|
|
1089
|
+
if (!result) {
|
|
1090
|
+
throw new Error(`Folder with name "${args.folderName}" not found`);
|
|
1091
|
+
}
|
|
1092
|
+
folderId = result.id;
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
1095
|
+
// Look in a specific space
|
|
1096
|
+
const folder = await clickup.findFolderByName(spaceId, args.folderName);
|
|
1097
|
+
if (!folder) {
|
|
1098
|
+
throw new Error(`Folder with name "${args.folderName}" not found in specified space`);
|
|
1099
|
+
}
|
|
1100
|
+
folderId = folder.id;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
// Ensure folderId is defined at this point
|
|
1104
|
+
if (!folderId) {
|
|
1105
|
+
throw new Error("Failed to determine folder ID");
|
|
1106
|
+
}
|
|
1107
|
+
// Extract update data
|
|
1108
|
+
const { folderId: _, folderName: __, spaceId: ___, spaceName: ____, ...updateData } = args;
|
|
1109
|
+
// Call the updateFolder method
|
|
1110
|
+
const updatedFolder = await clickup.updateFolder(folderId, updateData);
|
|
1111
|
+
return {
|
|
1112
|
+
content: [{
|
|
1113
|
+
type: "text",
|
|
1114
|
+
text: `Updated folder ${updatedFolder.id}: ${updatedFolder.name}`
|
|
1115
|
+
}]
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
case "delete_folder": {
|
|
1119
|
+
const args = request.params.arguments;
|
|
1120
|
+
if (!args.folderId && !args.folderName) {
|
|
1121
|
+
throw new Error("Either folderId or folderName is required");
|
|
1122
|
+
}
|
|
1123
|
+
let folderId = args.folderId;
|
|
1124
|
+
if (!folderId && args.folderName) {
|
|
1125
|
+
// If we need to look up by name, we might need the space
|
|
1126
|
+
let spaceId = args.spaceId;
|
|
1127
|
+
if (!spaceId && args.spaceName) {
|
|
1128
|
+
const foundId = await clickup.findSpaceIDByName(args.spaceName);
|
|
1129
|
+
if (!foundId) {
|
|
1130
|
+
throw new Error(`Space with name "${args.spaceName}" not found`);
|
|
1131
|
+
}
|
|
1132
|
+
spaceId = foundId;
|
|
1133
|
+
}
|
|
1134
|
+
if (!spaceId) {
|
|
1135
|
+
// Try to find folder directly by name (will search across all spaces)
|
|
1136
|
+
const result = await clickup.findFolderIDByName(args.folderName);
|
|
1137
|
+
if (!result) {
|
|
1138
|
+
throw new Error(`Folder with name "${args.folderName}" not found`);
|
|
1139
|
+
}
|
|
1140
|
+
folderId = result.id;
|
|
1141
|
+
}
|
|
1142
|
+
else {
|
|
1143
|
+
// Look in a specific space
|
|
1144
|
+
const folder = await clickup.findFolderByName(spaceId, args.folderName);
|
|
1145
|
+
if (!folder) {
|
|
1146
|
+
throw new Error(`Folder with name "${args.folderName}" not found in specified space`);
|
|
1147
|
+
}
|
|
1148
|
+
folderId = folder.id;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
// Ensure folderId is defined at this point
|
|
1152
|
+
if (!folderId) {
|
|
1153
|
+
throw new Error("Failed to determine folder ID");
|
|
1154
|
+
}
|
|
1155
|
+
// Store the folder name before deletion for the response message
|
|
1156
|
+
let folderName = args.folderName;
|
|
1157
|
+
if (!folderName) {
|
|
1158
|
+
try {
|
|
1159
|
+
const folderDetails = await clickup.getFolder(folderId);
|
|
1160
|
+
folderName = folderDetails.name;
|
|
1161
|
+
}
|
|
1162
|
+
catch (error) {
|
|
1163
|
+
// If we can't get the folder details, just use the ID in the response
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
await clickup.deleteFolder(folderId);
|
|
1167
|
+
return {
|
|
1168
|
+
content: [{
|
|
1169
|
+
type: "text",
|
|
1170
|
+
text: `Successfully deleted folder ${folderName || folderId}`
|
|
1171
|
+
}]
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
case "get_list": {
|
|
1175
|
+
const args = request.params.arguments;
|
|
1176
|
+
if (!args.listId && !args.listName) {
|
|
1177
|
+
throw new Error("Either listId or listName is required");
|
|
1178
|
+
}
|
|
1179
|
+
let listId = args.listId;
|
|
1180
|
+
if (!listId && args.listName) {
|
|
1181
|
+
const result = await clickup.findListIDByName(args.listName);
|
|
1182
|
+
if (!result) {
|
|
1183
|
+
throw new Error(`List with name "${args.listName}" not found`);
|
|
1184
|
+
}
|
|
1185
|
+
listId = result.id;
|
|
1186
|
+
}
|
|
1187
|
+
// Ensure listId is defined at this point
|
|
1188
|
+
if (!listId) {
|
|
1189
|
+
throw new Error("Failed to determine list ID");
|
|
1190
|
+
}
|
|
1191
|
+
const listDetails = await clickup.getList(listId);
|
|
1192
|
+
return {
|
|
1193
|
+
content: [{
|
|
1194
|
+
type: "text",
|
|
1195
|
+
text: JSON.stringify(listDetails, null, 2)
|
|
1196
|
+
}]
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
case "update_list": {
|
|
1200
|
+
const args = request.params.arguments;
|
|
1201
|
+
if (!args.listId && !args.listName) {
|
|
1202
|
+
throw new Error("Either listId or listName is required");
|
|
1203
|
+
}
|
|
1204
|
+
let listId = args.listId;
|
|
1205
|
+
if (!listId && args.listName) {
|
|
1206
|
+
const result = await clickup.findListIDByName(args.listName);
|
|
1207
|
+
if (!result) {
|
|
1208
|
+
throw new Error(`List with name "${args.listName}" not found`);
|
|
1209
|
+
}
|
|
1210
|
+
listId = result.id;
|
|
1211
|
+
}
|
|
1212
|
+
// Ensure listId is defined at this point
|
|
1213
|
+
if (!listId) {
|
|
1214
|
+
throw new Error("Failed to determine list ID");
|
|
1215
|
+
}
|
|
1216
|
+
// Extract update data
|
|
1217
|
+
const { listId: _, listName: __, ...updateData } = args;
|
|
1218
|
+
const updatedList = await clickup.updateList(listId, updateData);
|
|
1219
|
+
return {
|
|
1220
|
+
content: [{
|
|
1221
|
+
type: "text",
|
|
1222
|
+
text: `Updated list ${updatedList.id}: ${updatedList.name}`
|
|
1223
|
+
}]
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
case "delete_list": {
|
|
1227
|
+
const args = request.params.arguments;
|
|
1228
|
+
if (!args.listId && !args.listName) {
|
|
1229
|
+
throw new Error("Either listId or listName is required");
|
|
1230
|
+
}
|
|
1231
|
+
let listId = args.listId;
|
|
1232
|
+
if (!listId && args.listName) {
|
|
1233
|
+
const result = await clickup.findListIDByName(args.listName);
|
|
1234
|
+
if (!result) {
|
|
1235
|
+
throw new Error(`List with name "${args.listName}" not found`);
|
|
1236
|
+
}
|
|
1237
|
+
listId = result.id;
|
|
1238
|
+
}
|
|
1239
|
+
// Ensure listId is defined at this point
|
|
1240
|
+
if (!listId) {
|
|
1241
|
+
throw new Error("Failed to determine list ID");
|
|
1242
|
+
}
|
|
1243
|
+
// Store the list name before deletion for the response message
|
|
1244
|
+
let listName = args.listName;
|
|
1245
|
+
if (!listName) {
|
|
1246
|
+
try {
|
|
1247
|
+
const listDetails = await clickup.getList(listId);
|
|
1248
|
+
listName = listDetails.name;
|
|
1249
|
+
}
|
|
1250
|
+
catch (error) {
|
|
1251
|
+
// If we can't get the list details, just use the ID in the response
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
await clickup.deleteList(listId);
|
|
1255
|
+
return {
|
|
1256
|
+
content: [{
|
|
1257
|
+
type: "text",
|
|
1258
|
+
text: `Successfully deleted list ${listName || listId}`
|
|
1259
|
+
}]
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
844
1262
|
default:
|
|
845
1263
|
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
846
1264
|
}
|
|
@@ -59,16 +59,7 @@ export class ClickUpService {
|
|
|
59
59
|
*/
|
|
60
60
|
async makeRequest(requestFn) {
|
|
61
61
|
await this.checkRateLimit();
|
|
62
|
-
|
|
63
|
-
return await requestFn();
|
|
64
|
-
}
|
|
65
|
-
catch (error) {
|
|
66
|
-
if (error.response?.status === 429) {
|
|
67
|
-
// Let the interceptor handle it
|
|
68
|
-
throw error;
|
|
69
|
-
}
|
|
70
|
-
throw error;
|
|
71
|
-
}
|
|
62
|
+
return await requestFn();
|
|
72
63
|
}
|
|
73
64
|
/**
|
|
74
65
|
* Initializes the ClickUpService singleton instance.
|
|
@@ -183,10 +174,15 @@ export class ClickUpService {
|
|
|
183
174
|
async createTask(listId, data) {
|
|
184
175
|
return this.makeRequest(async () => {
|
|
185
176
|
const taskData = { ...data };
|
|
186
|
-
|
|
187
|
-
|
|
177
|
+
// If markdown_description is provided, it takes precedence
|
|
178
|
+
if (taskData.markdown_description) {
|
|
179
|
+
// Ensure we don't send both to avoid confusion
|
|
188
180
|
delete taskData.description;
|
|
189
181
|
}
|
|
182
|
+
else if (taskData.description) {
|
|
183
|
+
// Only use description as-is, don't auto-convert to markdown
|
|
184
|
+
taskData.description = taskData.description.trim();
|
|
185
|
+
}
|
|
190
186
|
const response = await this.client.post(`/list/${listId}/task`, taskData);
|
|
191
187
|
return response.data;
|
|
192
188
|
});
|
|
@@ -200,10 +196,15 @@ export class ClickUpService {
|
|
|
200
196
|
for (const taskData of data.tasks) {
|
|
201
197
|
await this.makeRequest(async () => {
|
|
202
198
|
const processedTask = { ...taskData };
|
|
203
|
-
|
|
204
|
-
|
|
199
|
+
// If markdown_description is provided, it takes precedence
|
|
200
|
+
if (processedTask.markdown_description) {
|
|
201
|
+
// Ensure we don't send both to avoid confusion
|
|
205
202
|
delete processedTask.description;
|
|
206
203
|
}
|
|
204
|
+
else if (processedTask.description) {
|
|
205
|
+
// Only use description as-is, don't auto-convert to markdown
|
|
206
|
+
processedTask.description = processedTask.description.trim();
|
|
207
|
+
}
|
|
207
208
|
const response = await this.client.post(`/list/${listId}/task`, processedTask);
|
|
208
209
|
createdTasks.push(response.data);
|
|
209
210
|
});
|
|
@@ -216,7 +217,21 @@ export class ClickUpService {
|
|
|
216
217
|
*/
|
|
217
218
|
async updateTask(taskId, data) {
|
|
218
219
|
return this.makeRequest(async () => {
|
|
219
|
-
const
|
|
220
|
+
const updateData = { ...data };
|
|
221
|
+
// If markdown_description is provided, it takes precedence
|
|
222
|
+
if (updateData.markdown_description) {
|
|
223
|
+
// Ensure we don't send both to avoid confusion
|
|
224
|
+
delete updateData.description;
|
|
225
|
+
}
|
|
226
|
+
else if (updateData.description) {
|
|
227
|
+
// Only use description as-is, don't auto-convert to markdown
|
|
228
|
+
updateData.description = updateData.description.trim();
|
|
229
|
+
}
|
|
230
|
+
// Handle null priority explicitly
|
|
231
|
+
if (updateData.priority === null) {
|
|
232
|
+
updateData.priority = null;
|
|
233
|
+
}
|
|
234
|
+
const response = await this.client.put(`/task/${taskId}`, updateData);
|
|
220
235
|
return response.data;
|
|
221
236
|
});
|
|
222
237
|
}
|
|
@@ -309,9 +324,16 @@ export class ClickUpService {
|
|
|
309
324
|
return response.data;
|
|
310
325
|
});
|
|
311
326
|
}
|
|
312
|
-
|
|
327
|
+
/**
|
|
328
|
+
* Updates an existing folder with new data.
|
|
329
|
+
* @param folderId - ID of the folder to update
|
|
330
|
+
* @param data - Data to update the folder with (name, override_statuses)
|
|
331
|
+
* @returns Promise resolving to the updated ClickUpFolder
|
|
332
|
+
* @throws Error if the API request fails
|
|
333
|
+
*/
|
|
334
|
+
async updateFolder(folderId, data) {
|
|
313
335
|
return this.makeRequest(async () => {
|
|
314
|
-
const response = await this.client.
|
|
336
|
+
const response = await this.client.put(`/folder/${folderId}`, data);
|
|
315
337
|
return response.data;
|
|
316
338
|
});
|
|
317
339
|
}
|
|
@@ -337,6 +359,19 @@ export class ClickUpService {
|
|
|
337
359
|
const folders = await this.getFolders(spaceId);
|
|
338
360
|
return folders.find(folder => folder.name.toLowerCase() === folderName.toLowerCase()) || null;
|
|
339
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Creates a new folder in a space.
|
|
364
|
+
* @param spaceId - ID of the space to create the folder in
|
|
365
|
+
* @param data - Folder creation data (name, override_statuses)
|
|
366
|
+
* @returns Promise resolving to the created ClickUpFolder
|
|
367
|
+
* @throws Error if the API request fails
|
|
368
|
+
*/
|
|
369
|
+
async createFolder(spaceId, data) {
|
|
370
|
+
return this.makeRequest(async () => {
|
|
371
|
+
const response = await this.client.post(`/space/${spaceId}/folder`, data);
|
|
372
|
+
return response.data;
|
|
373
|
+
});
|
|
374
|
+
}
|
|
340
375
|
// Additional helper methods
|
|
341
376
|
/**
|
|
342
377
|
* Moves a task to a different list.
|
|
@@ -710,11 +745,4 @@ export class ClickUpService {
|
|
|
710
745
|
: `${task.space.name} > ${task.list.name} > ${task.name}`;
|
|
711
746
|
return { id: task.id, path };
|
|
712
747
|
}
|
|
713
|
-
async getTaskStatuses(listId) {
|
|
714
|
-
const response = await this.getTasks(listId);
|
|
715
|
-
const statuses = [...new Set(response.tasks
|
|
716
|
-
.filter((task) => task.status !== undefined)
|
|
717
|
-
.map((task) => task.status.status))];
|
|
718
|
-
return statuses;
|
|
719
|
-
}
|
|
720
748
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taazkareem/clickup-mcp-server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.57",
|
|
4
4
|
"description": "ClickUp MCP Server - Integrate ClickUp tasks with AI through Model Context Protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"license": "MIT",
|
|
37
37
|
"repository": {
|
|
38
38
|
"type": "git",
|
|
39
|
-
"url": "https://github.com/taazkareem/clickup-mcp-server.git"
|
|
39
|
+
"url": "git+https://github.com/taazkareem/clickup-mcp-server.git"
|
|
40
40
|
},
|
|
41
41
|
"bugs": {
|
|
42
42
|
"url": "https://github.com/taazkareem/clickup-mcp-server/issues"
|
|
@@ -44,10 +44,8 @@
|
|
|
44
44
|
"homepage": "https://github.com/taazkareem/clickup-mcp-server#readme",
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@modelcontextprotocol/sdk": "0.6.0",
|
|
47
|
-
"@types/express": "^5.0.0",
|
|
48
47
|
"axios": "^1.6.7",
|
|
49
|
-
"dotenv": "^16.4.1"
|
|
50
|
-
"express": "^4.21.2"
|
|
48
|
+
"dotenv": "^16.4.1"
|
|
51
49
|
},
|
|
52
50
|
"devDependencies": {
|
|
53
51
|
"@types/node": "^20.11.16",
|