@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.
@@ -1,7 +1,25 @@
1
+ /**
2
+ * ClickUp API Service Module
3
+ * Provides a singleton service for interacting with the ClickUp API.
4
+ * Handles all API operations including tasks, lists, spaces, and folders management.
5
+ * Implements caching and connection pooling through axios instance.
6
+ * @module services/clickup
7
+ */
1
8
  import axios from 'axios';
9
+ /**
10
+ * ClickUp Service Class
11
+ * Singleton service that manages all interactions with the ClickUp API.
12
+ * Provides methods for CRUD operations on tasks, lists, spaces, and folders.
13
+ * Implements helper methods for searching and managing ClickUp resources.
14
+ */
2
15
  export class ClickUpService {
3
16
  client;
4
17
  static instance;
18
+ /**
19
+ * Private constructor to enforce singleton pattern
20
+ * Creates an axios instance with base configuration for ClickUp API
21
+ * @param apiKey - ClickUp API key for authentication
22
+ */
5
23
  constructor(apiKey) {
6
24
  this.client = axios.create({
7
25
  baseURL: 'https://api.clickup.com/api/v2',
@@ -11,105 +29,231 @@ export class ClickUpService {
11
29
  }
12
30
  });
13
31
  }
32
+ /**
33
+ * Initializes the ClickUpService singleton
34
+ * Creates a new instance if one doesn't exist
35
+ * @param apiKey - ClickUp API key for authentication
36
+ * @returns The singleton ClickUpService instance
37
+ * @throws Error if API key is invalid
38
+ */
14
39
  static initialize(apiKey) {
15
40
  if (!ClickUpService.instance) {
16
41
  ClickUpService.instance = new ClickUpService(apiKey);
17
42
  }
18
43
  return ClickUpService.instance;
19
44
  }
45
+ /**
46
+ * Gets the existing ClickUpService instance
47
+ * @returns The singleton ClickUpService instance
48
+ * @throws Error if service hasn't been initialized
49
+ */
20
50
  static getInstance() {
21
51
  if (!ClickUpService.instance) {
22
52
  throw new Error('ClickUpService not initialized. Call initialize() first.');
23
53
  }
24
54
  return ClickUpService.instance;
25
55
  }
26
- // Tasks
56
+ // Task Operations
57
+ /**
58
+ * Retrieves all tasks from a specific list
59
+ * Also returns available statuses in the list
60
+ * @param listId - ID of the list to fetch tasks from
61
+ * @returns Promise resolving to tasks array and unique statuses
62
+ */
27
63
  async getTasks(listId) {
28
64
  const response = await this.client.get(`/list/${listId}/task`);
29
65
  const tasks = response.data.tasks;
30
66
  const statuses = [...new Set(tasks.map((task) => task.status.status))];
31
67
  return { tasks, statuses };
32
68
  }
69
+ /**
70
+ * Retrieves a specific task by ID
71
+ * @param taskId - ID of the task to fetch
72
+ * @returns Promise resolving to task details
73
+ */
33
74
  async getTask(taskId) {
34
75
  const response = await this.client.get(`/task/${taskId}`);
35
76
  return response.data;
36
77
  }
78
+ /**
79
+ * Creates a new task in a specified list
80
+ * @param listId - ID of the list to create task in
81
+ * @param data - Task creation data
82
+ * @returns Promise resolving to created task
83
+ */
37
84
  async createTask(listId, data) {
38
85
  const response = await this.client.post(`/list/${listId}/task`, data);
39
86
  return response.data;
40
87
  }
88
+ /**
89
+ * Creates multiple tasks in a list simultaneously
90
+ * @param listId - ID of the list to create tasks in
91
+ * @param data - Bulk task creation data
92
+ * @returns Promise resolving to array of created tasks
93
+ */
41
94
  async createBulkTasks(listId, data) {
42
95
  const tasks = await Promise.all(data.tasks.map(taskData => this.createTask(listId, taskData)));
43
96
  return tasks;
44
97
  }
98
+ /**
99
+ * Updates an existing task
100
+ * @param taskId - ID of the task to update
101
+ * @param data - Task update data
102
+ * @returns Promise resolving to updated task
103
+ */
45
104
  async updateTask(taskId, data) {
46
105
  const response = await this.client.put(`/task/${taskId}`, data);
47
106
  return response.data;
48
107
  }
108
+ /**
109
+ * Deletes a task
110
+ * @param taskId - ID of the task to delete
111
+ */
49
112
  async deleteTask(taskId) {
50
113
  await this.client.delete(`/task/${taskId}`);
51
114
  }
52
- // Lists
115
+ // List Operations
116
+ /**
117
+ * Retrieves all lists in a space
118
+ * @param spaceId - ID of the space to fetch lists from
119
+ * @returns Promise resolving to array of lists
120
+ */
53
121
  async getLists(spaceId) {
54
122
  const response = await this.client.get(`/space/${spaceId}/list`);
55
123
  return response.data.lists;
56
124
  }
125
+ /**
126
+ * Retrieves all lists across all spaces in a team
127
+ * @param teamId - ID of the team to fetch lists from
128
+ * @returns Promise resolving to array of lists
129
+ */
57
130
  async getAllLists(teamId) {
58
131
  const response = await this.client.get(`/team/${teamId}/list`);
59
132
  return response.data.lists;
60
133
  }
134
+ /**
135
+ * Retrieves a specific list by ID
136
+ * @param listId - ID of the list to fetch
137
+ * @returns Promise resolving to list details
138
+ */
61
139
  async getList(listId) {
62
140
  const response = await this.client.get(`/list/${listId}`);
63
141
  return response.data;
64
142
  }
65
- // Spaces
143
+ // Space Operations
144
+ /**
145
+ * Retrieves all spaces in a team
146
+ * @param teamId - ID of the team to fetch spaces from
147
+ * @returns Promise resolving to array of spaces
148
+ */
66
149
  async getSpaces(teamId) {
67
150
  const response = await this.client.get(`/team/${teamId}/space`);
68
151
  return response.data.spaces;
69
152
  }
153
+ /**
154
+ * Retrieves a specific space by ID
155
+ * @param spaceId - ID of the space to fetch
156
+ * @returns Promise resolving to space details
157
+ */
70
158
  async getSpace(spaceId) {
71
159
  const response = await this.client.get(`/space/${spaceId}`);
72
160
  return response.data;
73
161
  }
162
+ /**
163
+ * Finds a space by name (case-insensitive)
164
+ * @param teamId - ID of the team to search in
165
+ * @param spaceName - Name of the space to find
166
+ * @returns Promise resolving to space if found, null otherwise
167
+ */
74
168
  async findSpaceByName(teamId, spaceName) {
75
169
  const spaces = await this.getSpaces(teamId);
76
170
  return spaces.find(space => space.name.toLowerCase() === spaceName.toLowerCase()) || null;
77
171
  }
172
+ /**
173
+ * Creates a new list in a space
174
+ * @param spaceId - ID of the space to create list in
175
+ * @param data - List creation data
176
+ * @returns Promise resolving to created list
177
+ */
78
178
  async createList(spaceId, data) {
79
179
  const response = await this.client.post(`/space/${spaceId}/list`, data);
80
180
  return response.data;
81
181
  }
82
- // Folders
182
+ // Folder Operations
183
+ /**
184
+ * Retrieves all folders in a space
185
+ * @param spaceId - ID of the space to fetch folders from
186
+ * @returns Promise resolving to array of folders
187
+ */
83
188
  async getFolders(spaceId) {
84
189
  const response = await this.client.get(`/space/${spaceId}/folder`);
85
190
  return response.data.folders;
86
191
  }
192
+ /**
193
+ * Retrieves a specific folder by ID
194
+ * @param folderId - ID of the folder to fetch
195
+ * @returns Promise resolving to folder details
196
+ */
87
197
  async getFolder(folderId) {
88
198
  const response = await this.client.get(`/folder/${folderId}`);
89
199
  return response.data;
90
200
  }
201
+ /**
202
+ * Creates a new folder in a space
203
+ * @param spaceId - ID of the space to create folder in
204
+ * @param data - Folder creation data
205
+ * @returns Promise resolving to created folder
206
+ */
91
207
  async createFolder(spaceId, data) {
92
208
  const response = await this.client.post(`/space/${spaceId}/folder`, data);
93
209
  return response.data;
94
210
  }
211
+ /**
212
+ * Deletes a folder
213
+ * @param folderId - ID of the folder to delete
214
+ */
95
215
  async deleteFolder(folderId) {
96
216
  await this.client.delete(`/folder/${folderId}`);
97
217
  }
218
+ /**
219
+ * Creates a new list within a folder
220
+ * @param folderId - ID of the folder to create list in
221
+ * @param data - List creation data
222
+ * @returns Promise resolving to created list
223
+ */
98
224
  async createListInFolder(folderId, data) {
99
225
  const response = await this.client.post(`/folder/${folderId}/list`, data);
100
226
  return response.data;
101
227
  }
228
+ /**
229
+ * Finds a folder by name within a space (case-insensitive)
230
+ * @param spaceId - ID of the space to search in
231
+ * @param folderName - Name of the folder to find
232
+ * @returns Promise resolving to folder if found, null otherwise
233
+ */
102
234
  async findFolderByName(spaceId, folderName) {
103
235
  const folders = await this.getFolders(spaceId);
104
236
  return folders.find(folder => folder.name.toLowerCase() === folderName.toLowerCase()) || null;
105
237
  }
106
- // Additional helper methods
238
+ // Helper Methods
239
+ /**
240
+ * Moves a task to a different list
241
+ * @param taskId - ID of the task to move
242
+ * @param listId - ID of the destination list
243
+ * @returns Promise resolving to updated task
244
+ */
107
245
  async moveTask(taskId, listId) {
108
246
  const response = await this.client.post(`/task/${taskId}`, {
109
247
  list: listId
110
248
  });
111
249
  return response.data;
112
250
  }
251
+ /**
252
+ * Finds a list by name across all spaces and folders (case-insensitive)
253
+ * @param teamId - ID of the team to search in
254
+ * @param listName - Name of the list to find
255
+ * @returns Promise resolving to list details with context if found, null otherwise
256
+ */
113
257
  async findListByNameGlobally(teamId, listName) {
114
258
  const spaces = await this.getSpaces(teamId);
115
259
  for (const space of spaces) {
@@ -136,6 +280,12 @@ export class ClickUpService {
136
280
  }
137
281
  return null;
138
282
  }
283
+ /**
284
+ * Finds a folder by name across all spaces (case-insensitive)
285
+ * @param teamId - ID of the team to search in
286
+ * @param folderName - Name of the folder to find
287
+ * @returns Promise resolving to folder details with context if found, null otherwise
288
+ */
139
289
  async findFolderByNameGlobally(teamId, folderName) {
140
290
  const spaces = await this.getSpaces(teamId);
141
291
  for (const space of spaces) {
@@ -147,15 +297,31 @@ export class ClickUpService {
147
297
  }
148
298
  return null;
149
299
  }
300
+ /**
301
+ * Creates a duplicate of a task in a specified list
302
+ * @param taskId - ID of the task to duplicate
303
+ * @param listId - ID of the destination list
304
+ * @returns Promise resolving to duplicated task
305
+ */
150
306
  async duplicateTask(taskId, listId) {
151
307
  const response = await this.client.post(`/task/${taskId}/duplicate`, {
152
308
  list: listId
153
309
  });
154
310
  return response.data;
155
311
  }
312
+ /**
313
+ * Deletes a list
314
+ * @param listId - ID of the list to delete
315
+ */
156
316
  async deleteList(listId) {
157
317
  await this.client.delete(`/list/${listId}`);
158
318
  }
319
+ /**
320
+ * Updates an existing list
321
+ * @param listId - ID of the list to update
322
+ * @param data - List update data
323
+ * @returns Promise resolving to updated list
324
+ */
159
325
  async updateList(listId, data) {
160
326
  const response = await this.client.put(`/list/${listId}`, data);
161
327
  return response.data;
@@ -1 +1,8 @@
1
+ /**
2
+ * Type definitions for ClickUp API entities and operations.
3
+ * Defines interfaces for tasks, lists, spaces, folders and their associated
4
+ * creation/update data structures. These types match the ClickUp API's
5
+ * expected request/response formats.
6
+ * @module types/clickup
7
+ */
1
8
  export {};
@@ -1,41 +1,47 @@
1
- class Logger {
2
- logLevel = 'info';
3
- setLogLevel(level) {
4
- this.logLevel = level;
5
- }
6
- shouldLog(level) {
7
- const levels = {
8
- debug: 0,
9
- info: 1,
10
- warn: 2,
11
- error: 3
12
- };
13
- return levels[level] >= levels[this.logLevel];
14
- }
15
- formatMessage(level, message, ...args) {
16
- const timestamp = new Date().toISOString();
17
- const formattedArgs = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg).join(' ');
18
- return `[${timestamp}] ${level.toUpperCase()}: ${message} ${formattedArgs}`.trim();
19
- }
20
- debug(message, ...args) {
21
- if (this.shouldLog('debug')) {
22
- console.debug(this.formatMessage('debug', message, ...args));
23
- }
24
- }
25
- info(message, ...args) {
26
- if (this.shouldLog('info')) {
27
- console.info(this.formatMessage('info', message, ...args));
28
- }
29
- }
30
- warn(message, ...args) {
31
- if (this.shouldLog('warn')) {
32
- console.warn(this.formatMessage('warn', message, ...args));
33
- }
34
- }
35
- error(message, ...args) {
36
- if (this.shouldLog('error')) {
37
- console.error(this.formatMessage('error', message, ...args));
38
- }
1
+ /**
2
+ * Logger module for MCP server that provides structured logging capabilities.
3
+ * All logs are formatted as JSON-RPC 2.0 messages and written to stderr.
4
+ * @module logger
5
+ */
6
+ /**
7
+ * Creates a JSON-RPC 2.0 formatted log message
8
+ * @param method - The logging method/category (e.g., 'server.error', 'mcp.info')
9
+ * @param params - Additional data to include in the log message
10
+ */
11
+ export function logMessage(method, params) {
12
+ const jsonRpcMessage = {
13
+ jsonrpc: "2.0",
14
+ method,
15
+ params,
16
+ id: Date.now() // Using timestamp as a unique ID
17
+ };
18
+ console.error(JSON.stringify(jsonRpcMessage)); // Use stderr for logging
19
+ }
20
+ /**
21
+ * Logs an error with optional stack trace
22
+ * @param context - The context where the error occurred (e.g., 'server', 'mcp')
23
+ * @param error - The error object or error message
24
+ */
25
+ export function logError(context, error) {
26
+ const errorMessage = error instanceof Error ? error.message : String(error);
27
+ logMessage(`${context}.error`, { error: errorMessage });
28
+ if (error instanceof Error && error.stack) {
29
+ logMessage(`${context}.stack`, { stack: error.stack });
39
30
  }
40
31
  }
41
- export const logger = new Logger();
32
+ /**
33
+ * Logs debug information
34
+ * @param context - The context of the debug message
35
+ * @param data - Debug data to log
36
+ */
37
+ export function logDebug(context, data) {
38
+ logMessage(`${context}.debug`, { data });
39
+ }
40
+ /**
41
+ * Logs informational messages
42
+ * @param context - The context of the info message
43
+ * @param data - Information to log
44
+ */
45
+ export function logInfo(context, data) {
46
+ logMessage(`${context}.info`, { data });
47
+ }
@@ -1,3 +1,18 @@
1
+ /**
2
+ * Resolver module for ClickUp entity resolution.
3
+ * Provides utilities to resolve IDs from names and fetch entities from the ClickUp API.
4
+ * @module resolvers
5
+ */
6
+ import { logMessage } from '../utils/logger.js';
7
+ /**
8
+ * Resolves a ClickUp list ID from either a direct ID or list name
9
+ * @param clickup - ClickUp service instance
10
+ * @param teamId - Team ID to search within
11
+ * @param listId - Optional direct list ID
12
+ * @param listName - Optional list name to search for
13
+ * @returns Promise resolving to the list ID
14
+ * @throws Error if neither listId nor listName is provided, or if list is not found
15
+ */
1
16
  export async function resolveListId(clickup, teamId, listId, listName) {
2
17
  if (listId)
3
18
  return listId;
@@ -10,6 +25,15 @@ export async function resolveListId(clickup, teamId, listId, listName) {
10
25
  }
11
26
  return result.list.id;
12
27
  }
28
+ /**
29
+ * Resolves a ClickUp space ID from either a direct ID or space name
30
+ * @param clickup - ClickUp service instance
31
+ * @param teamId - Team ID to search within
32
+ * @param spaceId - Optional direct space ID
33
+ * @param spaceName - Optional space name to search for
34
+ * @returns Promise resolving to the space ID
35
+ * @throws Error if neither spaceId nor spaceName is provided, or if space is not found
36
+ */
13
37
  export async function resolveSpaceId(clickup, teamId, spaceId, spaceName) {
14
38
  if (spaceId)
15
39
  return spaceId;
@@ -22,6 +46,15 @@ export async function resolveSpaceId(clickup, teamId, spaceId, spaceName) {
22
46
  }
23
47
  return space.id;
24
48
  }
49
+ /**
50
+ * Resolves a ClickUp folder ID from either a direct ID or folder name
51
+ * @param clickup - ClickUp service instance
52
+ * @param teamId - Team ID to search within
53
+ * @param folderId - Optional direct folder ID
54
+ * @param folderName - Optional folder name to search for
55
+ * @returns Promise resolving to the folder ID
56
+ * @throws Error if neither folderId nor folderName is provided, or if folder is not found
57
+ */
25
58
  export async function resolveFolderId(clickup, teamId, folderId, folderName) {
26
59
  if (folderId)
27
60
  return folderId;
@@ -34,6 +67,12 @@ export async function resolveFolderId(clickup, teamId, folderId, folderName) {
34
67
  }
35
68
  return result.folder.id;
36
69
  }
70
+ /**
71
+ * Retrieves all tasks across all spaces in a team
72
+ * @param clickup - ClickUp service instance
73
+ * @param teamId - Team ID to fetch tasks for
74
+ * @returns Promise resolving to an object containing all tasks and spaces
75
+ */
37
76
  export async function getAllTasks(clickup, teamId) {
38
77
  const spaces = await clickup.getSpaces(teamId);
39
78
  const spacePromises = spaces.map(async (space) => {
@@ -46,3 +85,14 @@ export async function getAllTasks(clickup, teamId) {
46
85
  const allTasks = tasksPerSpace.flat();
47
86
  return { tasks: allTasks, spaces };
48
87
  }
88
+ /**
89
+ * Generic error handler that formats and logs errors before re-throwing
90
+ * @param context - Context where the error occurred
91
+ * @param error - The error to handle
92
+ * @throws The original error after logging
93
+ */
94
+ export function handleError(context, error) {
95
+ const errorMessage = error instanceof Error ? error.message : String(error);
96
+ logMessage(`Error in ${context}`, { error: errorMessage });
97
+ throw error;
98
+ }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@taazkareem/clickup-mcp-server",
3
- "version": "0.4.40",
3
+ "version": "0.4.41",
4
4
  "description": "ClickUp MCP Server - Integrate ClickUp tasks with AI through Model Context Protocol",
5
5
  "type": "module",
6
- "main": "build/index.js",
6
+ "main": "./build/index.js",
7
7
  "bin": {
8
8
  "clickup-mcp-server": "build/index.js"
9
9
  },
@@ -35,16 +35,18 @@
35
35
  "license": "MIT",
36
36
  "repository": {
37
37
  "type": "git",
38
- "url": "https://github.com/taazkareem/clickup-mcp-server.git"
38
+ "url": "git+https://github.com/taazkareem/clickup-mcp-server.git"
39
39
  },
40
40
  "bugs": {
41
41
  "url": "https://github.com/taazkareem/clickup-mcp-server/issues"
42
42
  },
43
43
  "homepage": "https://github.com/taazkareem/clickup-mcp-server#readme",
44
44
  "dependencies": {
45
- "@modelcontextprotocol/sdk": "^1.4.1",
45
+ "@modelcontextprotocol/sdk": "^1.0.1",
46
+ "@modelcontextprotocol/server-github": "^2025.1.23",
46
47
  "axios": "^1.6.7",
47
- "dotenv": "^16.4.1"
48
+ "dotenv": "^16.4.1",
49
+ "zod": "^3.24.2"
48
50
  },
49
51
  "devDependencies": {
50
52
  "@types/node": "^20.11.16",