@taazkareem/clickup-mcp-server 0.4.40 → 0.4.41

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 taazkareem
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -88,6 +88,8 @@ To add this server to Roo Code:
88
88
  **Note:** Do not use the example settings and credentials provided here; use your own.
89
89
  5. Save the `cline_mcp_settings.json` file.
90
90
 
91
+
92
+
91
93
  > **Security Note**: Your API key will be stored securely and will not be exposed to AI models.
92
94
 
93
95
  ### Available Tools
package/build/config.js CHANGED
@@ -1,13 +1,23 @@
1
1
  import dotenv from 'dotenv';
2
+ // Function to format messages as JSON-RPC 2.0
3
+ function logMessage(method, params) {
4
+ const jsonRpcMessage = {
5
+ jsonrpc: "2.0",
6
+ method,
7
+ params,
8
+ id: Date.now()
9
+ };
10
+ console.log(JSON.stringify(jsonRpcMessage));
11
+ }
2
12
  // Load environment variables from .env file
3
13
  dotenv.config();
4
- console.log('Environment variables received:', {
14
+ logMessage('config.env.received', {
5
15
  CLICKUP_API_KEY: process.env.CLICKUP_API_KEY,
6
16
  TEAM_ID: process.env.TEAM_ID
7
17
  });
8
18
  // Parse command line arguments for --env flags
9
19
  const args = process.argv.slice(2);
10
- console.log('Command line arguments:', args);
20
+ logMessage('config.args.received', args);
11
21
  const envArgs = {};
12
22
  for (let i = 0; i < args.length; i++) {
13
23
  if (args[i] === '--env' && i + 1 < args.length) {
@@ -19,12 +29,12 @@ for (let i = 0; i < args.length; i++) {
19
29
  i++; // Skip the next argument since we used it
20
30
  }
21
31
  }
22
- console.log('Parsed environment arguments:', envArgs);
32
+ logMessage('config.args.parsed', envArgs);
23
33
  const configuration = {
24
34
  clickupApiKey: envArgs.clickupApiKey || process.env.CLICKUP_API_KEY || '',
25
35
  teamId: envArgs.teamId || process.env.TEAM_ID || ''
26
36
  };
27
- console.log('Final configuration:', configuration);
37
+ logMessage('config.final', configuration);
28
38
  // Check for missing environment variables
29
39
  const missingEnvVars = Object.entries(configuration)
30
40
  .filter(([_, value]) => !value)
@@ -1,4 +1,28 @@
1
+ /**
2
+ * Prompt handlers for ClickUp MCP server.
3
+ * Implements handlers for AI-powered analysis and summarization of ClickUp data.
4
+ * Each handler processes ClickUp data and returns formatted text output.
5
+ * @module handlers/prompts
6
+ */
1
7
  import { getAllTasks } from '../utils/resolvers.js';
8
+ /**
9
+ * Generates a summary of all tasks in the workspace
10
+ * Lists each task with its name and description, providing a quick overview
11
+ * of all work items across spaces and lists.
12
+ *
13
+ * @param clickup - ClickUp service instance for API operations
14
+ * @param teamId - Team ID to fetch tasks for
15
+ * @returns Promise resolving to formatted text summary of all tasks
16
+ *
17
+ * @example
18
+ * Output format:
19
+ * ```
20
+ * Summarized Tasks:
21
+ *
22
+ * - Task Name 1: Description of first task
23
+ * - Task Name 2: Description of second task
24
+ * ```
25
+ */
2
26
  export async function handleSummarizeTasks(clickup, teamId) {
3
27
  const { tasks } = await getAllTasks(clickup, teamId);
4
28
  let output = "Summarized Tasks:\n\n";
@@ -7,6 +31,35 @@ export async function handleSummarizeTasks(clickup, teamId) {
7
31
  }
8
32
  return output;
9
33
  }
34
+ /**
35
+ * Analyzes task priorities across the workspace
36
+ * Provides a statistical breakdown of task priorities, showing the distribution
37
+ * of priority levels and identifying potential workload patterns.
38
+ *
39
+ * @param clickup - ClickUp service instance for API operations
40
+ * @param teamId - Team ID to analyze tasks for
41
+ * @returns Promise resolving to formatted text analysis of task priorities
42
+ *
43
+ * @example
44
+ * Output format:
45
+ * ```
46
+ * Task Priorities Analysis:
47
+ *
48
+ * Available Priorities: 1, 2, 3, 4
49
+ *
50
+ * Priority Counts:
51
+ * - Priority 1: 5
52
+ * - Priority 2: 3
53
+ * - Priority 3: 8
54
+ * - Priority 4: 2
55
+ * ```
56
+ *
57
+ * Priority levels:
58
+ * 1 - Urgent
59
+ * 2 - High
60
+ * 3 - Normal
61
+ * 4 - Low
62
+ */
10
63
  export async function handleAnalyzeTaskPriorities(clickup, teamId) {
11
64
  const { tasks } = await getAllTasks(clickup, teamId);
12
65
  const priorities = tasks.map(task => task.priority?.priority);
@@ -1,38 +1,311 @@
1
- import { resolveListId } from '../utils/resolvers.js';
1
+ /**
2
+ * Tool handlers for ClickUp MCP server.
3
+ * Implements the business logic for each tool operation.
4
+ * @module handlers/tools
5
+ */
6
+ import { resolveListId, resolveSpaceId, resolveFolderId } from '../utils/resolvers.js';
7
+ import { logError } from '../utils/logger.js';
8
+ /**
9
+ * Handles the workspace_hierarchy tool request
10
+ * Returns a formatted tree view of the ClickUp workspace structure
11
+ * @param clickup - ClickUp service instance
12
+ * @param teamId - Team ID to fetch hierarchy for
13
+ * @returns Promise resolving to MCP-formatted response
14
+ */
2
15
  export async function handleWorkspaceHierarchy(clickup, teamId) {
3
- const spaces = await clickup.getSpaces(teamId);
4
- const allLists = await clickup.getAllLists(teamId);
5
- let output = "ClickUp Workspace Hierarchy:\n\n";
6
- for (const space of spaces) {
7
- output += `Space: ${space.name} (ID: ${space.id})\n`;
8
- const folders = await clickup.getFolders(space.id);
9
- for (const folder of folders) {
10
- output += ` ├─ Folder: ${folder.name} (ID: ${folder.id})\n`;
11
- const folderLists = folder.lists || [];
12
- for (const list of folderLists) {
13
- const { statuses } = await clickup.getTasks(list.id);
14
- output += ` │ └─ List: ${list.name} (ID: ${list.id})\n`;
15
- output += ` │ Available Statuses: ${statuses.join(', ')}\n`;
16
+ try {
17
+ const spaces = await clickup.getSpaces(teamId);
18
+ const allLists = await clickup.getAllLists(teamId);
19
+ let output = "ClickUp Workspace Hierarchy:\n\n";
20
+ for (const space of spaces) {
21
+ output += `Space: ${space.name} (ID: ${space.id})\n`;
22
+ const folders = await clickup.getFolders(space.id);
23
+ for (const folder of folders) {
24
+ output += ` ├─ Folder: ${folder.name} (ID: ${folder.id})\n`;
25
+ const folderLists = folder.lists || [];
26
+ for (const list of folderLists) {
27
+ const { statuses } = await clickup.getTasks(list.id);
28
+ output += ` │ └─ List: ${list.name} (ID: ${list.id})\n`;
29
+ output += ` │ Available Statuses: ${statuses.join(', ')}\n`;
30
+ }
16
31
  }
17
- }
18
- const spaceLists = allLists.filter(list => list.space &&
19
- list.space.id === space.id &&
20
- !folders.some(folder => folder.lists?.some(fl => fl.id === list.id)));
21
- if (spaceLists.length > 0) {
22
- output += " ├─ Lists (not in folders):\n";
23
- for (const list of spaceLists) {
24
- const { statuses } = await clickup.getTasks(list.id);
25
- output += ` │ └─ List: ${list.name} (ID: ${list.id})\n`;
26
- output += ` │ Available Statuses: ${statuses.join(', ')}\n`;
32
+ const spaceLists = allLists.filter(list => list.space &&
33
+ list.space.id === space.id &&
34
+ !folders.some(folder => folder.lists?.some(fl => fl.id === list.id)));
35
+ if (spaceLists.length > 0) {
36
+ output += " ├─ Lists (not in folders):\n";
37
+ for (const list of spaceLists) {
38
+ const { statuses } = await clickup.getTasks(list.id);
39
+ output += ` │ └─ List: ${list.name} (ID: ${list.id})\n`;
40
+ output += ` │ Available Statuses: ${statuses.join(', ')}\n`;
41
+ }
27
42
  }
43
+ output += "\n";
28
44
  }
29
- output += "\n";
45
+ return {
46
+ content: [{
47
+ type: 'text',
48
+ text: output
49
+ }]
50
+ };
51
+ }
52
+ catch (error) {
53
+ logError('tool.workspace_hierarchy', error);
54
+ const errorMessage = error instanceof Error ? error.message : String(error);
55
+ return {
56
+ content: [{
57
+ type: 'error',
58
+ text: `Failed to fetch workspace hierarchy: ${errorMessage}`
59
+ }]
60
+ };
30
61
  }
31
- return output;
32
62
  }
63
+ /**
64
+ * Handles the create_task tool request
65
+ * Creates a new task in the specified list
66
+ * @param clickup - ClickUp service instance
67
+ * @param teamId - Team ID to create task in
68
+ * @param args - Task creation arguments
69
+ * @returns Promise resolving to MCP-formatted response
70
+ */
33
71
  export async function handleCreateTask(clickup, teamId, args) {
34
- const listId = await resolveListId(clickup, teamId, args.listId, args.listName);
35
- const { listId: _, listName: __, ...taskData } = args;
36
- return await clickup.createTask(listId, taskData);
72
+ try {
73
+ const listId = await resolveListId(clickup, teamId, args.listId, args.listName);
74
+ const { listId: _, listName: __, ...taskData } = args;
75
+ const task = await clickup.createTask(listId, taskData);
76
+ return {
77
+ content: [{
78
+ type: 'text',
79
+ text: `Successfully created task "${task.name}" (ID: ${task.id})`
80
+ }]
81
+ };
82
+ }
83
+ catch (error) {
84
+ logError('tool.create_task', error);
85
+ const errorMessage = error instanceof Error ? error.message : String(error);
86
+ return {
87
+ content: [{
88
+ type: 'error',
89
+ text: `Failed to create task: ${errorMessage}`
90
+ }]
91
+ };
92
+ }
93
+ }
94
+ /**
95
+ * Handles the create_bulk_tasks tool request
96
+ * Creates multiple tasks in the specified list
97
+ * @param clickup - ClickUp service instance
98
+ * @param teamId - Team ID to create tasks in
99
+ * @param args - Bulk task creation arguments
100
+ * @returns Promise resolving to MCP-formatted response
101
+ */
102
+ export async function handleCreateBulkTasks(clickup, teamId, args) {
103
+ try {
104
+ const listId = await resolveListId(clickup, teamId, args.listId, args.listName);
105
+ const { listId: _, listName: __, tasks } = args;
106
+ const createdTasks = await Promise.all(tasks.map(taskData => clickup.createTask(listId, taskData)));
107
+ return {
108
+ content: [{
109
+ type: 'text',
110
+ text: `Successfully created ${createdTasks.length} tasks in list ${listId}`
111
+ }]
112
+ };
113
+ }
114
+ catch (error) {
115
+ logError('tool.create_bulk_tasks', error);
116
+ const errorMessage = error instanceof Error ? error.message : String(error);
117
+ return {
118
+ content: [{
119
+ type: 'error',
120
+ text: `Failed to create bulk tasks: ${errorMessage}`
121
+ }]
122
+ };
123
+ }
124
+ }
125
+ /**
126
+ * Handles the create_list tool request
127
+ * Creates a new list in the specified space
128
+ * @param clickup - ClickUp service instance
129
+ * @param teamId - Team ID to create list in
130
+ * @param args - List creation arguments
131
+ * @returns Promise resolving to MCP-formatted response
132
+ */
133
+ export async function handleCreateList(clickup, teamId, args) {
134
+ try {
135
+ const spaceId = await resolveSpaceId(clickup, teamId, args.spaceId, args.spaceName);
136
+ const { spaceId: _, spaceName: __, ...listData } = args;
137
+ const list = await clickup.createList(spaceId, listData);
138
+ return {
139
+ content: [{
140
+ type: 'text',
141
+ text: `Successfully created list "${list.name}" (ID: ${list.id})`
142
+ }]
143
+ };
144
+ }
145
+ catch (error) {
146
+ logError('tool.create_list', error);
147
+ const errorMessage = error instanceof Error ? error.message : String(error);
148
+ return {
149
+ content: [{
150
+ type: 'error',
151
+ text: `Failed to create list: ${errorMessage}`
152
+ }]
153
+ };
154
+ }
155
+ }
156
+ /**
157
+ * Handles the create_folder tool request
158
+ * Creates a new folder in the specified space
159
+ * @param clickup - ClickUp service instance
160
+ * @param teamId - Team ID to create folder in
161
+ * @param args - Folder creation arguments
162
+ * @returns Promise resolving to MCP-formatted response
163
+ */
164
+ export async function handleCreateFolder(clickup, teamId, args) {
165
+ try {
166
+ const spaceId = await resolveSpaceId(clickup, teamId, args.spaceId, args.spaceName);
167
+ const { spaceId: _, spaceName: __, ...folderData } = args;
168
+ const folder = await clickup.createFolder(spaceId, folderData);
169
+ return {
170
+ content: [{
171
+ type: 'text',
172
+ text: `Successfully created folder "${folder.name}" (ID: ${folder.id})`
173
+ }]
174
+ };
175
+ }
176
+ catch (error) {
177
+ logError('tool.create_folder', error);
178
+ const errorMessage = error instanceof Error ? error.message : String(error);
179
+ return {
180
+ content: [{
181
+ type: 'error',
182
+ text: `Failed to create folder: ${errorMessage}`
183
+ }]
184
+ };
185
+ }
186
+ }
187
+ /**
188
+ * Handles the create_list_in_folder tool request
189
+ * Creates a new list within a specified folder
190
+ * @param clickup - ClickUp service instance
191
+ * @param teamId - Team ID to create list in
192
+ * @param args - List creation arguments
193
+ * @returns Promise resolving to MCP-formatted response
194
+ */
195
+ export async function handleCreateListInFolder(clickup, teamId, args) {
196
+ try {
197
+ let folderId = args.folderId;
198
+ if (!folderId) {
199
+ const spaceId = await resolveSpaceId(clickup, teamId, args.spaceId, args.spaceName);
200
+ folderId = await resolveFolderId(clickup, teamId, undefined, args.folderName);
201
+ }
202
+ const { folderId: _, folderName: __, spaceId: ___, spaceName: ____, ...listData } = args;
203
+ const list = await clickup.createListInFolder(folderId, listData);
204
+ return {
205
+ content: [{
206
+ type: 'text',
207
+ text: `Successfully created list "${list.name}" (ID: ${list.id}) in folder`
208
+ }]
209
+ };
210
+ }
211
+ catch (error) {
212
+ logError('tool.create_list_in_folder', error);
213
+ const errorMessage = error instanceof Error ? error.message : String(error);
214
+ return {
215
+ content: [{
216
+ type: 'error',
217
+ text: `Failed to create list in folder: ${errorMessage}`
218
+ }]
219
+ };
220
+ }
221
+ }
222
+ /**
223
+ * Handles the move_task tool request
224
+ * Moves a task to a different list
225
+ * @param clickup - ClickUp service instance
226
+ * @param teamId - Team ID
227
+ * @param args - Task move arguments
228
+ * @returns Promise resolving to MCP-formatted response
229
+ */
230
+ export async function handleMoveTask(clickup, teamId, args) {
231
+ try {
232
+ const listId = await resolveListId(clickup, teamId, args.listId, args.listName);
233
+ await clickup.moveTask(args.taskId, listId);
234
+ return {
235
+ content: [{
236
+ type: 'text',
237
+ text: `Successfully moved task ${args.taskId} to list ${listId}`
238
+ }]
239
+ };
240
+ }
241
+ catch (error) {
242
+ logError('tool.move_task', error);
243
+ const errorMessage = error instanceof Error ? error.message : String(error);
244
+ return {
245
+ content: [{
246
+ type: 'error',
247
+ text: `Failed to move task: ${errorMessage}`
248
+ }]
249
+ };
250
+ }
251
+ }
252
+ /**
253
+ * Handles the duplicate_task tool request
254
+ * Creates a copy of a task in a specified list
255
+ * @param clickup - ClickUp service instance
256
+ * @param teamId - Team ID
257
+ * @param args - Task duplication arguments
258
+ * @returns Promise resolving to MCP-formatted response
259
+ */
260
+ export async function handleDuplicateTask(clickup, teamId, args) {
261
+ try {
262
+ const listId = await resolveListId(clickup, teamId, args.listId, args.listName);
263
+ const task = await clickup.duplicateTask(args.taskId, listId);
264
+ return {
265
+ content: [{
266
+ type: 'text',
267
+ text: `Successfully duplicated task "${task.name}" (ID: ${task.id})`
268
+ }]
269
+ };
270
+ }
271
+ catch (error) {
272
+ logError('tool.duplicate_task', error);
273
+ const errorMessage = error instanceof Error ? error.message : String(error);
274
+ return {
275
+ content: [{
276
+ type: 'error',
277
+ text: `Failed to duplicate task: ${errorMessage}`
278
+ }]
279
+ };
280
+ }
281
+ }
282
+ /**
283
+ * Handles the update_task tool request
284
+ * Updates an existing task with new data
285
+ * @param clickup - ClickUp service instance
286
+ * @param teamId - Team ID
287
+ * @param args - Task update arguments
288
+ * @returns Promise resolving to MCP-formatted response
289
+ */
290
+ export async function handleUpdateTask(clickup, teamId, args) {
291
+ try {
292
+ const { taskId, ...updateData } = args;
293
+ const task = await clickup.updateTask(taskId, updateData);
294
+ return {
295
+ content: [{
296
+ type: 'text',
297
+ text: `Successfully updated task "${task.name}" (ID: ${task.id})`
298
+ }]
299
+ };
300
+ }
301
+ catch (error) {
302
+ logError('tool.update_task', error);
303
+ const errorMessage = error instanceof Error ? error.message : String(error);
304
+ return {
305
+ content: [{
306
+ type: 'error',
307
+ text: `Failed to update task: ${errorMessage}`
308
+ }]
309
+ };
310
+ }
37
311
  }
38
- // Add other handler functions for each tool...