@taazkareem/clickup-mcp-server 0.4.16 → 0.4.18
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 +3 -0
- package/build/config.js +14 -4
- package/build/handlers/prompts.js +53 -0
- package/build/handlers/tools.js +303 -30
- package/build/index.js +182 -346
- package/build/services/clickup.js +171 -5
- package/build/types/clickup.js +7 -0
- package/build/utils/logger.js +47 -0
- package/build/utils/resolvers.js +50 -0
- package/package.json +1 -1
|
@@ -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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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;
|
package/build/types/clickup.js
CHANGED
|
@@ -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 {};
|
|
@@ -0,0 +1,47 @@
|
|
|
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 });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
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
|
+
}
|
package/build/utils/resolvers.js
CHANGED
|
@@ -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