@taazkareem/clickup-mcp-server 0.2.0 → 0.4.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.
- package/README.md +58 -38
- package/build/index.js +273 -135
- package/build/services/clickup.js +41 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,18 +16,24 @@ A Model Context Protocol (MCP) server for integrating ClickUp tasks with AI appl
|
|
|
16
16
|
- Flexible identification using IDs or names
|
|
17
17
|
|
|
18
18
|
- ✨ **Task Operations**
|
|
19
|
-
- Create
|
|
19
|
+
- Create single or bulk tasks
|
|
20
20
|
- Move tasks between lists
|
|
21
21
|
- Duplicate tasks
|
|
22
22
|
- Set priorities and due dates
|
|
23
23
|
- Assign team members
|
|
24
24
|
|
|
25
25
|
- 📊 **Information Retrieval**
|
|
26
|
-
- Get spaces and lists with
|
|
26
|
+
- Get complete hierarchy of spaces, folders, and lists with IDs
|
|
27
27
|
- List available statuses
|
|
28
28
|
- Find items by name (case-insensitive)
|
|
29
29
|
- View task relationships
|
|
30
30
|
|
|
31
|
+
- 🔍 **Smart Name Resolution**
|
|
32
|
+
- Use names instead of IDs for lists and folders
|
|
33
|
+
- Global search across all spaces
|
|
34
|
+
- Case-insensitive matching
|
|
35
|
+
- Automatic location of items
|
|
36
|
+
|
|
31
37
|
- 📝 **AI Integration**
|
|
32
38
|
- Generate task descriptions with AI
|
|
33
39
|
- Summarize tasks and analyze priorities
|
|
@@ -58,33 +64,64 @@ CLICKUP_API_KEY=your_api_key_here
|
|
|
58
64
|
TEAM_ID=your_team_id_here
|
|
59
65
|
```
|
|
60
66
|
|
|
61
|
-
##
|
|
67
|
+
## Using with Cursor AI Composer
|
|
68
|
+
|
|
69
|
+
To add this server to Cursor AI Composer, follow these steps:
|
|
70
|
+
|
|
71
|
+
1. Go to the Features section in the settings.
|
|
72
|
+
2. Add the following command under MCP Servers:
|
|
62
73
|
|
|
63
|
-
### Starting the Server
|
|
64
74
|
```bash
|
|
65
|
-
clickup-mcp-server
|
|
75
|
+
npx -y @taazkareem/clickup-mcp-server \
|
|
76
|
+
--env CLICKUP_API_KEY=your_api_key_here \
|
|
77
|
+
--env TEAM_ID=your_team_id_here
|
|
66
78
|
```
|
|
79
|
+
3. Replace `your_api_key_here` and `your_team_id_here` with your actual ClickUp credentials.
|
|
80
|
+
4. Click on 'Save' to add the server.
|
|
81
|
+
|
|
82
|
+
You can get these values from:
|
|
83
|
+
- `CLICKUP_API_KEY`: Get from [ClickUp Settings > Apps](https://app.clickup.com/settings/apps)
|
|
84
|
+
- `TEAM_ID`: Your ClickUp Team ID (found in the URL when viewing your workspace or via API)
|
|
85
|
+
|
|
86
|
+
> **Security Note**: Your API key will be stored securely and will not be exposed to AI models.
|
|
67
87
|
|
|
68
88
|
### Available Tools
|
|
69
89
|
|
|
70
|
-
1. **
|
|
71
|
-
- Lists
|
|
90
|
+
1. **workspace_hierarchy**
|
|
91
|
+
- Lists complete hierarchy of the ClickUp workspace
|
|
92
|
+
- Shows spaces, folders, and lists with their IDs
|
|
72
93
|
- Shows available statuses for each list
|
|
94
|
+
- Provides a tree view of your workspace organization
|
|
73
95
|
- No parameters required
|
|
74
96
|
|
|
75
97
|
2. **create_task**
|
|
76
98
|
- Creates a new task in ClickUp
|
|
77
99
|
- Required parameters:
|
|
78
|
-
- `listId`: ID of the list to create the task in
|
|
79
100
|
- `name`: Name of the task
|
|
80
101
|
- Optional parameters:
|
|
102
|
+
- `listId`: ID of the list (optional if listName provided)
|
|
103
|
+
- `listName`: Name of the list (optional if listId provided)
|
|
81
104
|
- `description`: Task description
|
|
82
105
|
- `status`: Task status
|
|
83
106
|
- `priority`: Priority level (1-4)
|
|
84
107
|
- `dueDate`: Due date (ISO string)
|
|
85
108
|
- `assignees`: Array of user IDs
|
|
86
109
|
|
|
87
|
-
3. **
|
|
110
|
+
3. **create_bulk_tasks**
|
|
111
|
+
- Creates multiple tasks simultaneously in a list
|
|
112
|
+
- Required parameters:
|
|
113
|
+
- `tasks`: Array of task objects, each containing:
|
|
114
|
+
- `name`: Name of the task (required)
|
|
115
|
+
- `description`: Task description (optional)
|
|
116
|
+
- `status`: Task status (optional)
|
|
117
|
+
- `priority`: Priority level 1-4 (optional)
|
|
118
|
+
- `dueDate`: Due date ISO string (optional)
|
|
119
|
+
- `assignees`: Array of user IDs (optional)
|
|
120
|
+
- Optional parameters:
|
|
121
|
+
- `listId`: ID of the list (optional if listName provided)
|
|
122
|
+
- `listName`: Name of the list (optional if listId provided)
|
|
123
|
+
|
|
124
|
+
4. **create_list**
|
|
88
125
|
- Creates a new list in a space
|
|
89
126
|
- Required parameters:
|
|
90
127
|
- `name`: Name of the list
|
|
@@ -96,7 +133,7 @@ clickup-mcp-server
|
|
|
96
133
|
- `priority`: Priority level (1-4)
|
|
97
134
|
- `dueDate`: Due date (ISO string)
|
|
98
135
|
|
|
99
|
-
|
|
136
|
+
5. **create_folder**
|
|
100
137
|
- Creates a new folder in a space
|
|
101
138
|
- Required parameters:
|
|
102
139
|
- `name`: Name of the folder
|
|
@@ -105,7 +142,7 @@ clickup-mcp-server
|
|
|
105
142
|
- `spaceName`: Name of the space (optional if spaceId provided)
|
|
106
143
|
- `override_statuses`: Whether to override space statuses
|
|
107
144
|
|
|
108
|
-
|
|
145
|
+
6. **create_list_in_folder**
|
|
109
146
|
- Creates a new list within a folder
|
|
110
147
|
- Required parameters:
|
|
111
148
|
- `name`: Name of the list
|
|
@@ -117,19 +154,23 @@ clickup-mcp-server
|
|
|
117
154
|
- `content`: List description
|
|
118
155
|
- `status`: List status
|
|
119
156
|
|
|
120
|
-
|
|
157
|
+
7. **move_task**
|
|
121
158
|
- Moves a task to a different list
|
|
122
159
|
- Required parameters:
|
|
123
160
|
- `taskId`: ID of the task to move
|
|
124
|
-
|
|
161
|
+
- Optional parameters:
|
|
162
|
+
- `listId`: ID of destination list (optional if listName provided)
|
|
163
|
+
- `listName`: Name of destination list (optional if listId provided)
|
|
125
164
|
|
|
126
|
-
|
|
165
|
+
8. **duplicate_task**
|
|
127
166
|
- Creates a copy of a task in a specified list
|
|
128
167
|
- Required parameters:
|
|
129
168
|
- `taskId`: ID of the task to duplicate
|
|
130
|
-
|
|
169
|
+
- Optional parameters:
|
|
170
|
+
- `listId`: ID of destination list (optional if listName provided)
|
|
171
|
+
- `listName`: Name of destination list (optional if listId provided)
|
|
131
172
|
|
|
132
|
-
|
|
173
|
+
9. **update_task**
|
|
133
174
|
- Updates an existing task
|
|
134
175
|
- Required parameters:
|
|
135
176
|
- `taskId`: ID of the task to update
|
|
@@ -181,27 +222,6 @@ The server provides clear error messages for common scenarios:
|
|
|
181
222
|
- Permission issues
|
|
182
223
|
- API errors
|
|
183
224
|
|
|
184
|
-
## Using with Cursor AI Composer
|
|
185
|
-
|
|
186
|
-
To add this server to Cursor AI Composer, follow these steps:
|
|
187
|
-
|
|
188
|
-
1. Go to the Features section in the settings.
|
|
189
|
-
2. Add the following command under MCP Servers:
|
|
190
|
-
|
|
191
|
-
```bash
|
|
192
|
-
npx -y @taazkareem/clickup-mcp-server \
|
|
193
|
-
--env CLICKUP_API_KEY=your_api_key_here \
|
|
194
|
-
--env TEAM_ID=your_team_id_here
|
|
195
|
-
```
|
|
196
|
-
3. Replace `your_api_key_here` and `your_team_id_here` with your actual ClickUp credentials.
|
|
197
|
-
4. Click on 'Save' to add the server.
|
|
198
|
-
|
|
199
|
-
You can get these values from:
|
|
200
|
-
- `CLICKUP_API_KEY`: Get from [ClickUp Settings > Apps](https://app.clickup.com/settings/apps)
|
|
201
|
-
- `TEAM_ID`: Your ClickUp Team ID (found in the URL when viewing your workspace or via API)
|
|
202
|
-
|
|
203
|
-
> **Security Note**: Your API key will be stored securely and will not be exposed to AI models.
|
|
204
|
-
|
|
205
225
|
## Development
|
|
206
226
|
|
|
207
227
|
1. Clone the repository:
|
|
@@ -226,4 +246,4 @@ Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md)
|
|
|
226
246
|
|
|
227
247
|
## License
|
|
228
248
|
|
|
229
|
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
249
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
package/build/index.js
CHANGED
|
@@ -101,8 +101,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
101
101
|
return {
|
|
102
102
|
tools: [
|
|
103
103
|
{
|
|
104
|
-
name: "
|
|
105
|
-
description: "List
|
|
104
|
+
name: "workspace_hierarchy",
|
|
105
|
+
description: "List complete hierarchy of the ClickUp workspace, including spaces, folders, lists, and their IDs and available statuses",
|
|
106
106
|
inputSchema: {
|
|
107
107
|
type: "object",
|
|
108
108
|
properties: {},
|
|
@@ -117,7 +117,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
117
117
|
properties: {
|
|
118
118
|
listId: {
|
|
119
119
|
type: "string",
|
|
120
|
-
description: "ID of the list to create the task in"
|
|
120
|
+
description: "ID of the list to create the task in (optional if listName is provided)"
|
|
121
|
+
},
|
|
122
|
+
listName: {
|
|
123
|
+
type: "string",
|
|
124
|
+
description: "Name of the list to create the task in (optional if listId is provided)"
|
|
121
125
|
},
|
|
122
126
|
name: {
|
|
123
127
|
type: "string",
|
|
@@ -140,7 +144,62 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
140
144
|
description: "Due date of the task (ISO string)"
|
|
141
145
|
}
|
|
142
146
|
},
|
|
143
|
-
required: ["
|
|
147
|
+
required: ["name"]
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: "create_bulk_tasks",
|
|
152
|
+
description: "Create multiple tasks in a ClickUp list",
|
|
153
|
+
inputSchema: {
|
|
154
|
+
type: "object",
|
|
155
|
+
properties: {
|
|
156
|
+
listId: {
|
|
157
|
+
type: "string",
|
|
158
|
+
description: "ID of the list to create the tasks in (optional if listName is provided)"
|
|
159
|
+
},
|
|
160
|
+
listName: {
|
|
161
|
+
type: "string",
|
|
162
|
+
description: "Name of the list to create the tasks in (optional if listId is provided)"
|
|
163
|
+
},
|
|
164
|
+
tasks: {
|
|
165
|
+
type: "array",
|
|
166
|
+
description: "Array of tasks to create",
|
|
167
|
+
items: {
|
|
168
|
+
type: "object",
|
|
169
|
+
properties: {
|
|
170
|
+
name: {
|
|
171
|
+
type: "string",
|
|
172
|
+
description: "Name of the task"
|
|
173
|
+
},
|
|
174
|
+
description: {
|
|
175
|
+
type: "string",
|
|
176
|
+
description: "Description of the task"
|
|
177
|
+
},
|
|
178
|
+
status: {
|
|
179
|
+
type: "string",
|
|
180
|
+
description: "Status of the task"
|
|
181
|
+
},
|
|
182
|
+
priority: {
|
|
183
|
+
type: "number",
|
|
184
|
+
description: "Priority level (1-4)"
|
|
185
|
+
},
|
|
186
|
+
dueDate: {
|
|
187
|
+
type: "string",
|
|
188
|
+
description: "Due date (ISO string)"
|
|
189
|
+
},
|
|
190
|
+
assignees: {
|
|
191
|
+
type: "array",
|
|
192
|
+
items: {
|
|
193
|
+
type: "number"
|
|
194
|
+
},
|
|
195
|
+
description: "Array of user IDs to assign to the task"
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
required: ["name"]
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
required: ["tasks"]
|
|
144
203
|
}
|
|
145
204
|
},
|
|
146
205
|
{
|
|
@@ -261,10 +320,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
261
320
|
},
|
|
262
321
|
listId: {
|
|
263
322
|
type: "string",
|
|
264
|
-
description: "ID of the destination list"
|
|
323
|
+
description: "ID of the destination list (optional if listName is provided)"
|
|
324
|
+
},
|
|
325
|
+
listName: {
|
|
326
|
+
type: "string",
|
|
327
|
+
description: "Name of the destination list (optional if listId is provided)"
|
|
265
328
|
}
|
|
266
329
|
},
|
|
267
|
-
required: ["taskId"
|
|
330
|
+
required: ["taskId"]
|
|
268
331
|
}
|
|
269
332
|
},
|
|
270
333
|
{
|
|
@@ -279,10 +342,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
279
342
|
},
|
|
280
343
|
listId: {
|
|
281
344
|
type: "string",
|
|
282
|
-
description: "ID of the list
|
|
345
|
+
description: "ID of the destination list (optional if listName is provided)"
|
|
346
|
+
},
|
|
347
|
+
listName: {
|
|
348
|
+
type: "string",
|
|
349
|
+
description: "Name of the destination list (optional if listId is provided)"
|
|
283
350
|
}
|
|
284
351
|
},
|
|
285
|
-
required: ["taskId"
|
|
352
|
+
required: ["taskId"]
|
|
286
353
|
}
|
|
287
354
|
},
|
|
288
355
|
{
|
|
@@ -329,17 +396,35 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
329
396
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
330
397
|
try {
|
|
331
398
|
switch (request.params.name) {
|
|
332
|
-
case "
|
|
399
|
+
case "workspace_hierarchy": {
|
|
333
400
|
const spaces = await clickup.getSpaces(config.teamId);
|
|
334
401
|
const allLists = await clickup.getAllLists(config.teamId);
|
|
335
|
-
let output = "
|
|
402
|
+
let output = "ClickUp Workspace Hierarchy:\n\n";
|
|
336
403
|
for (const space of spaces) {
|
|
337
404
|
output += `Space: ${space.name} (ID: ${space.id})\n`;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
output += `
|
|
342
|
-
|
|
405
|
+
// Get folders in this space
|
|
406
|
+
const folders = await clickup.getFolders(space.id);
|
|
407
|
+
for (const folder of folders) {
|
|
408
|
+
output += ` ├─ Folder: ${folder.name} (ID: ${folder.id})\n`;
|
|
409
|
+
// Get lists in this folder
|
|
410
|
+
const folderLists = folder.lists || [];
|
|
411
|
+
for (const list of folderLists) {
|
|
412
|
+
const { statuses } = await clickup.getTasks(list.id);
|
|
413
|
+
output += ` │ └─ List: ${list.name} (ID: ${list.id})\n`;
|
|
414
|
+
output += ` │ Available Statuses: ${statuses.join(', ')}\n`;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// Get lists directly in space (not in folders)
|
|
418
|
+
const spaceLists = allLists.filter(list => list.space &&
|
|
419
|
+
list.space.id === space.id &&
|
|
420
|
+
!folders.some(folder => folder.lists?.some(fl => fl.id === list.id)));
|
|
421
|
+
if (spaceLists.length > 0) {
|
|
422
|
+
output += " ├─ Lists (not in folders):\n";
|
|
423
|
+
for (const list of spaceLists) {
|
|
424
|
+
const { statuses } = await clickup.getTasks(list.id);
|
|
425
|
+
output += ` │ └─ List: ${list.name} (ID: ${list.id})\n`;
|
|
426
|
+
output += ` │ Available Statuses: ${statuses.join(', ')}\n`;
|
|
427
|
+
}
|
|
343
428
|
}
|
|
344
429
|
output += "\n";
|
|
345
430
|
}
|
|
@@ -362,10 +447,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
362
447
|
}
|
|
363
448
|
case "create_task": {
|
|
364
449
|
const args = request.params.arguments;
|
|
365
|
-
|
|
366
|
-
|
|
450
|
+
let listId = args.listId;
|
|
451
|
+
if (!listId && args.listName) {
|
|
452
|
+
const result = await clickup.findListByNameGlobally(config.teamId, args.listName);
|
|
453
|
+
if (!result) {
|
|
454
|
+
throw new Error(`List with name "${args.listName}" not found`);
|
|
455
|
+
}
|
|
456
|
+
listId = result.list.id;
|
|
457
|
+
}
|
|
458
|
+
if (!listId) {
|
|
459
|
+
throw new Error("Either listId or listName is required");
|
|
367
460
|
}
|
|
368
|
-
const { listId, ...taskData } = args;
|
|
461
|
+
const { listId: _, listName: __, ...taskData } = args;
|
|
369
462
|
const task = await clickup.createTask(listId, taskData);
|
|
370
463
|
return {
|
|
371
464
|
content: [{
|
|
@@ -374,11 +467,59 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
374
467
|
}]
|
|
375
468
|
};
|
|
376
469
|
}
|
|
470
|
+
case "create_bulk_tasks": {
|
|
471
|
+
const args = request.params.arguments;
|
|
472
|
+
let listId = args.listId;
|
|
473
|
+
if (!listId && args.listName) {
|
|
474
|
+
const result = await clickup.findListByNameGlobally(config.teamId, args.listName);
|
|
475
|
+
if (!result) {
|
|
476
|
+
throw new Error(`List with name "${args.listName}" not found`);
|
|
477
|
+
}
|
|
478
|
+
listId = result.list.id;
|
|
479
|
+
}
|
|
480
|
+
if (!listId) {
|
|
481
|
+
throw new Error("Either listId or listName is required");
|
|
482
|
+
}
|
|
483
|
+
if (!args.tasks || !args.tasks.length) {
|
|
484
|
+
throw new Error("At least one task is required");
|
|
485
|
+
}
|
|
486
|
+
const { listId: _, listName: __, tasks } = args;
|
|
487
|
+
const createdTasks = await clickup.createBulkTasks(listId, { tasks });
|
|
488
|
+
return {
|
|
489
|
+
content: [{
|
|
490
|
+
type: "text",
|
|
491
|
+
text: `Created ${createdTasks.length} tasks:\n${createdTasks.map(task => `- ${task.id}: ${task.name}`).join('\n')}`
|
|
492
|
+
}]
|
|
493
|
+
};
|
|
494
|
+
}
|
|
377
495
|
case "create_list": {
|
|
378
496
|
const args = request.params.arguments;
|
|
379
497
|
if (!args.name) {
|
|
380
498
|
throw new Error("name is required");
|
|
381
499
|
}
|
|
500
|
+
// If folder is specified, create list in folder
|
|
501
|
+
if (args.folderName || args.folderId) {
|
|
502
|
+
let folderId = args.folderId;
|
|
503
|
+
if (!folderId && args.folderName) {
|
|
504
|
+
const result = await clickup.findFolderByNameGlobally(config.teamId, args.folderName);
|
|
505
|
+
if (!result) {
|
|
506
|
+
throw new Error(`Folder with name "${args.folderName}" not found`);
|
|
507
|
+
}
|
|
508
|
+
folderId = result.folder.id;
|
|
509
|
+
}
|
|
510
|
+
if (!folderId) {
|
|
511
|
+
throw new Error("Either folderId or folderName must be provided");
|
|
512
|
+
}
|
|
513
|
+
const { spaceId: _, spaceName: __, folderName: ___, folderId: ____, ...listData } = args;
|
|
514
|
+
const list = await clickup.createListInFolder(folderId, listData);
|
|
515
|
+
return {
|
|
516
|
+
content: [{
|
|
517
|
+
type: "text",
|
|
518
|
+
text: `Created list ${list.id}: ${list.name} in folder`
|
|
519
|
+
}]
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
// Otherwise, create list in space
|
|
382
523
|
let spaceId = args.spaceId;
|
|
383
524
|
if (!spaceId && args.spaceName) {
|
|
384
525
|
const space = await clickup.findSpaceByName(config.teamId, args.spaceName);
|
|
@@ -390,7 +531,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
390
531
|
if (!spaceId) {
|
|
391
532
|
throw new Error("Either spaceId or spaceName must be provided");
|
|
392
533
|
}
|
|
393
|
-
const { spaceId: _, spaceName: __, ...listData } = args;
|
|
534
|
+
const { spaceId: _, spaceName: __, folderName: ___, folderId: ____, ...listData } = args;
|
|
394
535
|
const list = await clickup.createList(spaceId, listData);
|
|
395
536
|
return {
|
|
396
537
|
content: [{
|
|
@@ -440,18 +581,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
440
581
|
spaceId = space.id;
|
|
441
582
|
}
|
|
442
583
|
if (!spaceId) {
|
|
443
|
-
throw new Error("Either spaceId or spaceName must be provided
|
|
584
|
+
throw new Error("Either spaceId or spaceName must be provided");
|
|
444
585
|
}
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
586
|
+
const { spaceId: _, spaceName: ___, ...listData } = args;
|
|
587
|
+
const list = await clickup.createListInFolder(spaceId, listData);
|
|
588
|
+
return {
|
|
589
|
+
content: [{
|
|
590
|
+
type: "text",
|
|
591
|
+
text: `Created list ${list.id}: ${list.name} in folder`
|
|
592
|
+
}]
|
|
593
|
+
};
|
|
450
594
|
}
|
|
451
595
|
if (!folderId) {
|
|
452
|
-
throw new Error("Either folderId or folderName
|
|
596
|
+
throw new Error("Either folderId or folderName must be provided");
|
|
453
597
|
}
|
|
454
|
-
const {
|
|
598
|
+
const { spaceId: _, spaceName: ___, ...listData } = args;
|
|
455
599
|
const list = await clickup.createListInFolder(folderId, listData);
|
|
456
600
|
return {
|
|
457
601
|
content: [{
|
|
@@ -462,37 +606,64 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
462
606
|
}
|
|
463
607
|
case "move_task": {
|
|
464
608
|
const args = request.params.arguments;
|
|
465
|
-
if (!args.taskId
|
|
466
|
-
throw new Error("taskId
|
|
609
|
+
if (!args.taskId) {
|
|
610
|
+
throw new Error("taskId is required");
|
|
611
|
+
}
|
|
612
|
+
let listId = args.listId;
|
|
613
|
+
if (!listId && args.listName) {
|
|
614
|
+
const result = await clickup.findListByNameGlobally(config.teamId, args.listName);
|
|
615
|
+
if (!result) {
|
|
616
|
+
throw new Error(`List with name "${args.listName}" not found`);
|
|
617
|
+
}
|
|
618
|
+
listId = result.list.id;
|
|
467
619
|
}
|
|
468
|
-
|
|
620
|
+
if (!listId) {
|
|
621
|
+
throw new Error("Either listId or listName is required");
|
|
622
|
+
}
|
|
623
|
+
const task = await clickup.moveTask(args.taskId, listId);
|
|
469
624
|
return {
|
|
470
625
|
content: [{
|
|
471
626
|
type: "text",
|
|
472
|
-
text: `Moved task ${task.id} to list ${
|
|
627
|
+
text: `Moved task ${task.id} to list ${listId}`
|
|
473
628
|
}]
|
|
474
629
|
};
|
|
475
630
|
}
|
|
476
631
|
case "duplicate_task": {
|
|
477
632
|
const args = request.params.arguments;
|
|
478
|
-
if (!args.taskId
|
|
479
|
-
throw new Error("taskId
|
|
633
|
+
if (!args.taskId) {
|
|
634
|
+
throw new Error("taskId is required");
|
|
480
635
|
}
|
|
481
|
-
|
|
636
|
+
let listId = args.listId;
|
|
637
|
+
if (!listId && args.listName) {
|
|
638
|
+
const result = await clickup.findListByNameGlobally(config.teamId, args.listName);
|
|
639
|
+
if (!result) {
|
|
640
|
+
throw new Error(`List with name "${args.listName}" not found`);
|
|
641
|
+
}
|
|
642
|
+
listId = result.list.id;
|
|
643
|
+
}
|
|
644
|
+
if (!listId) {
|
|
645
|
+
throw new Error("Either listId or listName is required");
|
|
646
|
+
}
|
|
647
|
+
const task = await clickup.duplicateTask(args.taskId, listId);
|
|
482
648
|
return {
|
|
483
649
|
content: [{
|
|
484
650
|
type: "text",
|
|
485
|
-
text: `Duplicated task ${args.taskId} to new task ${task.id} in list ${
|
|
651
|
+
text: `Duplicated task ${args.taskId} to new task ${task.id} in list ${listId}`
|
|
486
652
|
}]
|
|
487
653
|
};
|
|
488
654
|
}
|
|
489
655
|
case "update_task": {
|
|
490
656
|
const args = request.params.arguments;
|
|
491
|
-
if (!args.taskId) {
|
|
492
|
-
throw new Error("
|
|
657
|
+
if (!args.taskId || !args.name || !args.description || !args.status || !args.priority || !args.due_date) {
|
|
658
|
+
throw new Error("All arguments are required");
|
|
493
659
|
}
|
|
494
|
-
const
|
|
495
|
-
|
|
660
|
+
const task = await clickup.updateTask(args.taskId, {
|
|
661
|
+
name: args.name,
|
|
662
|
+
description: args.description,
|
|
663
|
+
status: args.status,
|
|
664
|
+
priority: args.priority,
|
|
665
|
+
due_date: args.due_date ? new Date(args.due_date).getTime() : undefined
|
|
666
|
+
});
|
|
496
667
|
return {
|
|
497
668
|
content: [{
|
|
498
669
|
type: "text",
|
|
@@ -500,152 +671,119 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
500
671
|
}]
|
|
501
672
|
};
|
|
502
673
|
}
|
|
503
|
-
default:
|
|
504
|
-
throw new Error("Unknown tool");
|
|
505
674
|
}
|
|
675
|
+
throw new Error("Unknown tool");
|
|
506
676
|
}
|
|
507
677
|
catch (error) {
|
|
508
|
-
console.error('Error
|
|
678
|
+
console.error('Error executing tool:', error);
|
|
509
679
|
throw error;
|
|
510
680
|
}
|
|
511
681
|
});
|
|
512
682
|
/**
|
|
513
|
-
*
|
|
514
|
-
*
|
|
683
|
+
* Handler for listing available prompts.
|
|
684
|
+
* Exposes prompts for summarizing/analyzing ClickUp tasks.
|
|
515
685
|
*/
|
|
516
686
|
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
517
687
|
return {
|
|
518
688
|
prompts: [
|
|
519
689
|
{
|
|
520
690
|
name: "summarize_tasks",
|
|
521
|
-
description: "Summarize all tasks
|
|
691
|
+
description: "Summarize all ClickUp tasks"
|
|
522
692
|
},
|
|
523
693
|
{
|
|
524
|
-
name: "
|
|
525
|
-
description: "Analyze task priorities
|
|
694
|
+
name: "analyze_task_priorities",
|
|
695
|
+
description: "Analyze task priorities"
|
|
526
696
|
},
|
|
527
697
|
{
|
|
528
|
-
name: "
|
|
529
|
-
description: "Generate
|
|
698
|
+
name: "generate_task_descriptions",
|
|
699
|
+
description: "Generate detailed descriptions for tasks"
|
|
530
700
|
}
|
|
531
701
|
]
|
|
532
702
|
};
|
|
533
703
|
});
|
|
704
|
+
/**
|
|
705
|
+
* Handler for getting a specific prompt.
|
|
706
|
+
* Takes a prompt name and returns the prompt content.
|
|
707
|
+
*/
|
|
534
708
|
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
535
709
|
try {
|
|
536
710
|
switch (request.params.name) {
|
|
537
711
|
case "summarize_tasks": {
|
|
538
712
|
const spaces = await clickup.getSpaces(config.teamId);
|
|
539
|
-
|
|
540
|
-
// Gather all tasks
|
|
713
|
+
let output = "Summarized Tasks:\n\n";
|
|
541
714
|
for (const space of spaces) {
|
|
542
715
|
const lists = await clickup.getLists(space.id);
|
|
543
716
|
for (const list of lists) {
|
|
544
|
-
const { tasks
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
uri: `clickup://task/${task.id}`,
|
|
549
|
-
mimeType: "application/json",
|
|
550
|
-
text: JSON.stringify(task, null, 2)
|
|
551
|
-
}
|
|
552
|
-
})));
|
|
717
|
+
const { tasks } = await clickup.getTasks(list.id);
|
|
718
|
+
for (const task of tasks) {
|
|
719
|
+
output += `- ${task.name}: ${task.description}\n`;
|
|
720
|
+
}
|
|
553
721
|
}
|
|
554
722
|
}
|
|
555
723
|
return {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
type: "text",
|
|
561
|
-
text: "Please provide a summary of the following ClickUp tasks:"
|
|
562
|
-
}
|
|
563
|
-
},
|
|
564
|
-
...tasks.map(task => ({
|
|
565
|
-
role: "user",
|
|
566
|
-
content: task
|
|
567
|
-
})),
|
|
568
|
-
{
|
|
569
|
-
role: "user",
|
|
570
|
-
content: {
|
|
571
|
-
type: "text",
|
|
572
|
-
text: "Please provide:\n1. A high-level overview of all tasks\n2. Group them by status\n3. Highlight any urgent or high-priority items\n4. Suggest any task dependencies or relationships"
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
]
|
|
724
|
+
content: [{
|
|
725
|
+
type: "text",
|
|
726
|
+
text: output
|
|
727
|
+
}]
|
|
576
728
|
};
|
|
577
729
|
}
|
|
578
|
-
case "
|
|
730
|
+
case "analyze_task_priorities": {
|
|
579
731
|
const spaces = await clickup.getSpaces(config.teamId);
|
|
580
|
-
const
|
|
732
|
+
const allTasks = [];
|
|
581
733
|
for (const space of spaces) {
|
|
582
734
|
const lists = await clickup.getLists(space.id);
|
|
583
735
|
for (const list of lists) {
|
|
584
|
-
const { tasks
|
|
585
|
-
|
|
586
|
-
type: "resource",
|
|
587
|
-
resource: {
|
|
588
|
-
uri: `clickup://task/${task.id}`,
|
|
589
|
-
mimeType: "application/json",
|
|
590
|
-
text: JSON.stringify(task, null, 2)
|
|
591
|
-
}
|
|
592
|
-
})));
|
|
736
|
+
const { tasks } = await clickup.getTasks(list.id);
|
|
737
|
+
allTasks.push(...tasks);
|
|
593
738
|
}
|
|
594
739
|
}
|
|
740
|
+
const priorities = allTasks.map((task) => task.priority?.priority);
|
|
741
|
+
const uniquePriorities = [...new Set(priorities.filter(p => p !== undefined))];
|
|
742
|
+
const priorityCounts = uniquePriorities.map(priority => ({
|
|
743
|
+
priority: priority,
|
|
744
|
+
count: priorities.filter((p) => p === priority).length
|
|
745
|
+
}));
|
|
746
|
+
let output = "Task Priorities Analysis:\n\n";
|
|
747
|
+
output += "Available Priorities: " + uniquePriorities.join(', ') + "\n\n";
|
|
748
|
+
output += "Priority Counts:\n";
|
|
749
|
+
for (const priority of priorityCounts) {
|
|
750
|
+
output += `- Priority ${priority.priority}: ${priority.count}\n`;
|
|
751
|
+
}
|
|
595
752
|
return {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
type: "text",
|
|
601
|
-
text: "Please analyze the priorities of the following ClickUp tasks:"
|
|
602
|
-
}
|
|
603
|
-
},
|
|
604
|
-
...tasks.map(task => ({
|
|
605
|
-
role: "user",
|
|
606
|
-
content: task
|
|
607
|
-
})),
|
|
608
|
-
{
|
|
609
|
-
role: "user",
|
|
610
|
-
content: {
|
|
611
|
-
type: "text",
|
|
612
|
-
text: "Please provide:\n1. Analysis of current priority distribution\n2. Identify any misaligned priorities\n3. Suggest priority adjustments\n4. Recommend task sequencing based on priorities"
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
]
|
|
753
|
+
content: [{
|
|
754
|
+
type: "text",
|
|
755
|
+
text: output
|
|
756
|
+
}]
|
|
616
757
|
};
|
|
617
758
|
}
|
|
618
|
-
case "
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
}
|
|
759
|
+
case "generate_task_descriptions": {
|
|
760
|
+
const spaces = await clickup.getSpaces(config.teamId);
|
|
761
|
+
let output = "Generated Task Descriptions:\n\n";
|
|
762
|
+
for (const space of spaces) {
|
|
763
|
+
const lists = await clickup.getLists(space.id);
|
|
764
|
+
for (const list of lists) {
|
|
765
|
+
const { tasks } = await clickup.getTasks(list.id);
|
|
766
|
+
for (const task of tasks) {
|
|
767
|
+
output += `- ${task.name}: ${task.description}\n`;
|
|
627
768
|
}
|
|
628
|
-
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return {
|
|
772
|
+
content: [{
|
|
773
|
+
type: "text",
|
|
774
|
+
text: output
|
|
775
|
+
}]
|
|
629
776
|
};
|
|
630
777
|
}
|
|
631
778
|
default:
|
|
632
|
-
throw new Error("
|
|
779
|
+
throw new Error("Prompt not found");
|
|
633
780
|
}
|
|
634
781
|
}
|
|
635
782
|
catch (error) {
|
|
636
|
-
console.error('Error
|
|
783
|
+
console.error('Error getting prompt:', error);
|
|
637
784
|
throw error;
|
|
638
785
|
}
|
|
639
786
|
});
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
*/
|
|
644
|
-
async function main() {
|
|
645
|
-
const transport = new StdioServerTransport();
|
|
646
|
-
await server.connect(transport);
|
|
647
|
-
}
|
|
648
|
-
main().catch((error) => {
|
|
649
|
-
console.error("Server error:", error);
|
|
650
|
-
process.exit(1);
|
|
651
|
-
});
|
|
787
|
+
// Start the server
|
|
788
|
+
const transport = new StdioServerTransport();
|
|
789
|
+
transport.start();
|
|
@@ -38,6 +38,10 @@ export class ClickUpService {
|
|
|
38
38
|
const response = await this.client.post(`/list/${listId}/task`, data);
|
|
39
39
|
return response.data;
|
|
40
40
|
}
|
|
41
|
+
async createBulkTasks(listId, data) {
|
|
42
|
+
const tasks = await Promise.all(data.tasks.map(taskData => this.createTask(listId, taskData)));
|
|
43
|
+
return tasks;
|
|
44
|
+
}
|
|
41
45
|
async updateTask(taskId, data) {
|
|
42
46
|
const response = await this.client.put(`/task/${taskId}`, data);
|
|
43
47
|
return response.data;
|
|
@@ -106,6 +110,43 @@ export class ClickUpService {
|
|
|
106
110
|
});
|
|
107
111
|
return response.data;
|
|
108
112
|
}
|
|
113
|
+
async findListByNameGlobally(teamId, listName) {
|
|
114
|
+
const spaces = await this.getSpaces(teamId);
|
|
115
|
+
for (const space of spaces) {
|
|
116
|
+
// Check lists in folders
|
|
117
|
+
const folders = await this.getFolders(space.id);
|
|
118
|
+
for (const folder of folders) {
|
|
119
|
+
const folderList = folder.lists?.find(list => list.name.toLowerCase() === listName.toLowerCase());
|
|
120
|
+
if (folderList) {
|
|
121
|
+
return { list: folderList, space, folder };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Check lists directly in space
|
|
125
|
+
const spaceLists = await this.getLists(space.id);
|
|
126
|
+
const spaceList = spaceLists.find(list => list.name.toLowerCase() === listName.toLowerCase());
|
|
127
|
+
if (spaceList) {
|
|
128
|
+
return { list: spaceList, space };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Check lists without spaces
|
|
132
|
+
const allLists = await this.getAllLists(teamId);
|
|
133
|
+
const list = allLists.find(list => list.name.toLowerCase() === listName.toLowerCase());
|
|
134
|
+
if (list) {
|
|
135
|
+
return { list };
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
async findFolderByNameGlobally(teamId, folderName) {
|
|
140
|
+
const spaces = await this.getSpaces(teamId);
|
|
141
|
+
for (const space of spaces) {
|
|
142
|
+
const folders = await this.getFolders(space.id);
|
|
143
|
+
const folder = folders.find(folder => folder.name.toLowerCase() === folderName.toLowerCase());
|
|
144
|
+
if (folder) {
|
|
145
|
+
return { folder, space };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
109
150
|
async duplicateTask(taskId, listId) {
|
|
110
151
|
const response = await this.client.post(`/task/${taskId}/duplicate`, {
|
|
111
152
|
list: listId
|
|
@@ -119,8 +160,4 @@ export class ClickUpService {
|
|
|
119
160
|
const response = await this.client.put(`/list/${listId}`, data);
|
|
120
161
|
return response.data;
|
|
121
162
|
}
|
|
122
|
-
async findListByName(spaceId, listName) {
|
|
123
|
-
const lists = await this.getLists(spaceId);
|
|
124
|
-
return lists.find(list => list.name.toLowerCase() === listName.toLowerCase()) || null;
|
|
125
|
-
}
|
|
126
163
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taazkareem/clickup-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"homepage": "https://github.com/taazkareem/clickup-mcp-server#readme",
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@modelcontextprotocol/sdk": "
|
|
44
|
+
"@modelcontextprotocol/sdk": "^1.4.1",
|
|
45
45
|
"axios": "^1.6.7",
|
|
46
46
|
"dotenv": "^16.4.1"
|
|
47
47
|
},
|