@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 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 and update tasks
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 their IDs
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
- ## Usage
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. **list_spaces**
71
- - Lists all spaces and their lists with IDs
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. **create_list**
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
- 4. **create_folder**
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
- 5. **create_list_in_folder**
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
- 6. **move_task**
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
- - `listId`: ID of the destination list
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
- 7. **duplicate_task**
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
- - `listId`: ID of the destination list
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
- 8. **update_task**
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: "list_spaces",
105
- description: "List all spaces and their lists with IDs",
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: ["listId", "name"]
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", "listId"]
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 to create the duplicate in"
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", "listId"]
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 "list_spaces": {
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 = "Available Spaces and Lists:\n\n";
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
- const spaceLists = allLists.filter(list => list.space.id === space.id);
339
- for (const list of spaceLists) {
340
- const { statuses } = await clickup.getTasks(list.id);
341
- output += ` └─ List: ${list.name} (ID: ${list.id})\n`;
342
- output += ` Available Statuses: ${statuses.join(', ')}\n`;
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
- if (!args.listId || !args.name) {
366
- throw new Error("listId and name are required");
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 when using folderName");
584
+ throw new Error("Either spaceId or spaceName must be provided");
444
585
  }
445
- const folder = await clickup.findFolderByName(spaceId, args.folderName);
446
- if (!folder) {
447
- throw new Error(`Folder with name "${args.folderName}" not found`);
448
- }
449
- folderId = folder.id;
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 (with space information) must be provided");
596
+ throw new Error("Either folderId or folderName must be provided");
453
597
  }
454
- const { folderId: _, folderName: __, spaceId: ___, spaceName: ____, ...listData } = args;
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 || !args.listId) {
466
- throw new Error("taskId and listId are required");
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
- const task = await clickup.moveTask(args.taskId, args.listId);
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 ${args.listId}`
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 || !args.listId) {
479
- throw new Error("taskId and listId are required");
633
+ if (!args.taskId) {
634
+ throw new Error("taskId is required");
480
635
  }
481
- const task = await clickup.duplicateTask(args.taskId, args.listId);
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 ${args.listId}`
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("taskId is required");
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 { taskId, ...updateData } = args;
495
- const task = await clickup.updateTask(taskId, updateData);
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 handling tool call:', error);
678
+ console.error('Error executing tool:', error);
509
679
  throw error;
510
680
  }
511
681
  });
512
682
  /**
513
- * Add handlers for listing and getting prompts.
514
- * Prompts include summarizing tasks, analyzing priorities, and generating task descriptions.
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 in a list",
691
+ description: "Summarize all ClickUp tasks"
522
692
  },
523
693
  {
524
- name: "analyze_priorities",
525
- description: "Analyze task priorities and suggest optimizations",
694
+ name: "analyze_task_priorities",
695
+ description: "Analyze task priorities"
526
696
  },
527
697
  {
528
- name: "generate_description",
529
- description: "Generate a detailed description for a task",
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
- const tasks = [];
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: listTasks } = await clickup.getTasks(list.id);
545
- tasks.push(...listTasks.map((task) => ({
546
- type: "resource",
547
- resource: {
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
- messages: [
557
- {
558
- role: "user",
559
- content: {
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 "analyze_priorities": {
730
+ case "analyze_task_priorities": {
579
731
  const spaces = await clickup.getSpaces(config.teamId);
580
- const tasks = [];
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: listTasks } = await clickup.getTasks(list.id);
585
- tasks.push(...listTasks.map((task) => ({
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
- messages: [
597
- {
598
- role: "user",
599
- content: {
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 "generate_description": {
619
- return {
620
- messages: [
621
- {
622
- role: "user",
623
- content: {
624
- type: "text",
625
- text: "Please help me generate a detailed description for a ClickUp task. The description should include:\n1. Clear objective\n2. Success criteria\n3. Required resources\n4. Dependencies\n5. Potential risks\n\nPlease ask me about the task details."
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("Unknown prompt");
779
+ throw new Error("Prompt not found");
633
780
  }
634
781
  }
635
782
  catch (error) {
636
- console.error('Error handling prompt:', error);
783
+ console.error('Error getting prompt:', error);
637
784
  throw error;
638
785
  }
639
786
  });
640
- /**
641
- * Start the server using stdio transport.
642
- * This allows the server to communicate via standard input/output streams.
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.2.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": "0.6.0",
44
+ "@modelcontextprotocol/sdk": "^1.4.1",
45
45
  "axios": "^1.6.7",
46
46
  "dotenv": "^16.4.1"
47
47
  },