@taazkareem/clickup-mcp-server 0.4.60 → 0.4.63
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 +10 -2
- package/build/config.js +10 -0
- package/build/index.js +9 -1356
- package/build/server.js +120 -0
- package/build/services/clickup/base.js +253 -0
- package/build/services/clickup/bulk.js +116 -0
- package/build/services/clickup/folder.js +133 -0
- package/build/services/clickup/index.js +43 -0
- package/build/services/clickup/initialization.js +28 -0
- package/build/services/clickup/list.js +188 -0
- package/build/services/clickup/task.js +492 -0
- package/build/services/clickup/types.js +4 -0
- package/build/services/clickup/workspace.js +314 -0
- package/build/services/shared.js +15 -0
- package/build/tools/folder.js +356 -0
- package/build/tools/index.js +11 -0
- package/build/tools/list.js +452 -0
- package/build/tools/task.js +1519 -0
- package/build/tools/utils.js +150 -0
- package/build/tools/workspace.js +132 -0
- package/package.json +3 -1
- package/build/services/clickup.js +0 -765
- package/build/types/clickup.js +0 -1
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClickUp Workspace Service Module
|
|
3
|
+
*
|
|
4
|
+
* Handles workspace hierarchy and space-related operations
|
|
5
|
+
*/
|
|
6
|
+
import { BaseClickUpService, ClickUpServiceError, ErrorCode } from './base.js';
|
|
7
|
+
/**
|
|
8
|
+
* Service for workspace-related operations
|
|
9
|
+
*/
|
|
10
|
+
export class WorkspaceService extends BaseClickUpService {
|
|
11
|
+
/**
|
|
12
|
+
* Creates an instance of WorkspaceService
|
|
13
|
+
* @param apiKey - ClickUp API key
|
|
14
|
+
* @param teamId - ClickUp team ID
|
|
15
|
+
* @param baseUrl - Optional custom API URL
|
|
16
|
+
*/
|
|
17
|
+
constructor(apiKey, teamId, baseUrl) {
|
|
18
|
+
super(apiKey, teamId, baseUrl);
|
|
19
|
+
// Store the workspace hierarchy in memory
|
|
20
|
+
this.workspaceHierarchy = null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Helper method to handle errors consistently
|
|
24
|
+
* @param error - Error caught from a try/catch
|
|
25
|
+
* @param message - Optional message to add to the error
|
|
26
|
+
* @returns - A standardized ClickUpServiceError
|
|
27
|
+
*/
|
|
28
|
+
handleError(error, message) {
|
|
29
|
+
// Log the error for debugging
|
|
30
|
+
console.error('WorkspaceService error:', error);
|
|
31
|
+
// If the error is already a ClickUpServiceError, return it
|
|
32
|
+
if (error instanceof ClickUpServiceError) {
|
|
33
|
+
return error;
|
|
34
|
+
}
|
|
35
|
+
// Otherwise, create a new ClickUpServiceError
|
|
36
|
+
const errorMessage = message || 'An error occurred in WorkspaceService';
|
|
37
|
+
return new ClickUpServiceError(errorMessage, ErrorCode.WORKSPACE_ERROR, error);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get all spaces for the team
|
|
41
|
+
* @returns - Promise resolving to array of spaces
|
|
42
|
+
*/
|
|
43
|
+
async getSpaces() {
|
|
44
|
+
try {
|
|
45
|
+
const response = await this.makeRequest(async () => {
|
|
46
|
+
const result = await this.client.get(`/team/${this.teamId}/space`);
|
|
47
|
+
return result.data;
|
|
48
|
+
});
|
|
49
|
+
return response.spaces || [];
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
throw this.handleError(error, 'Failed to get spaces');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get a specific space by ID
|
|
57
|
+
* @param spaceId - The ID of the space to retrieve
|
|
58
|
+
* @returns - Promise resolving to the space object
|
|
59
|
+
*/
|
|
60
|
+
async getSpace(spaceId) {
|
|
61
|
+
try {
|
|
62
|
+
// Validate spaceId
|
|
63
|
+
if (!spaceId) {
|
|
64
|
+
throw new ClickUpServiceError('Space ID is required', ErrorCode.INVALID_PARAMETER);
|
|
65
|
+
}
|
|
66
|
+
return await this.makeRequest(async () => {
|
|
67
|
+
const result = await this.client.get(`/space/${spaceId}`);
|
|
68
|
+
return result.data;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
throw this.handleError(error, `Failed to get space with ID ${spaceId}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Find a space by name
|
|
77
|
+
* @param spaceName - The name of the space to find
|
|
78
|
+
* @returns - Promise resolving to the space or null if not found
|
|
79
|
+
*/
|
|
80
|
+
async findSpaceByName(spaceName) {
|
|
81
|
+
try {
|
|
82
|
+
// Validate spaceName
|
|
83
|
+
if (!spaceName) {
|
|
84
|
+
throw new ClickUpServiceError('Space name is required', ErrorCode.INVALID_PARAMETER);
|
|
85
|
+
}
|
|
86
|
+
// Get all spaces and find the one with the matching name
|
|
87
|
+
const spaces = await this.getSpaces();
|
|
88
|
+
const space = spaces.find(s => s.name === spaceName);
|
|
89
|
+
return space || null;
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
throw this.handleError(error, `Failed to find space with name ${spaceName}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get the complete workspace hierarchy including spaces, folders, and lists
|
|
97
|
+
* @param forceRefresh - Whether to force a refresh of the hierarchy
|
|
98
|
+
* @returns - Promise resolving to the workspace tree
|
|
99
|
+
*/
|
|
100
|
+
async getWorkspaceHierarchy(forceRefresh = false) {
|
|
101
|
+
try {
|
|
102
|
+
// If we have a cached result and not forcing refresh, return it
|
|
103
|
+
if (this.workspaceHierarchy && !forceRefresh) {
|
|
104
|
+
return this.workspaceHierarchy;
|
|
105
|
+
}
|
|
106
|
+
// Start building the workspace tree
|
|
107
|
+
const workspaceTree = {
|
|
108
|
+
root: {
|
|
109
|
+
id: this.teamId,
|
|
110
|
+
name: 'Workspace',
|
|
111
|
+
children: []
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
// Get all spaces
|
|
115
|
+
const spaces = await this.getSpaces();
|
|
116
|
+
// Process each space
|
|
117
|
+
for (const space of spaces) {
|
|
118
|
+
const spaceNode = {
|
|
119
|
+
id: space.id,
|
|
120
|
+
name: space.name,
|
|
121
|
+
type: 'space',
|
|
122
|
+
children: []
|
|
123
|
+
};
|
|
124
|
+
// Get folders for the space
|
|
125
|
+
const folders = await this.getFoldersInSpace(space.id);
|
|
126
|
+
for (const folder of folders) {
|
|
127
|
+
const folderNode = {
|
|
128
|
+
id: folder.id,
|
|
129
|
+
name: folder.name,
|
|
130
|
+
type: 'folder',
|
|
131
|
+
parentId: space.id,
|
|
132
|
+
children: []
|
|
133
|
+
};
|
|
134
|
+
// Get lists in the folder
|
|
135
|
+
const listsInFolder = await this.getListsInFolder(folder.id);
|
|
136
|
+
for (const list of listsInFolder) {
|
|
137
|
+
folderNode.children?.push({
|
|
138
|
+
id: list.id,
|
|
139
|
+
name: list.name,
|
|
140
|
+
type: 'list',
|
|
141
|
+
parentId: folder.id
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
spaceNode.children?.push(folderNode);
|
|
145
|
+
}
|
|
146
|
+
// Get lists directly in the space (not in any folder)
|
|
147
|
+
const listsInSpace = await this.getListsInSpace(space.id);
|
|
148
|
+
for (const list of listsInSpace) {
|
|
149
|
+
spaceNode.children?.push({
|
|
150
|
+
id: list.id,
|
|
151
|
+
name: list.name,
|
|
152
|
+
type: 'list',
|
|
153
|
+
parentId: space.id
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
workspaceTree.root.children.push(spaceNode);
|
|
157
|
+
}
|
|
158
|
+
// Store the hierarchy for later use
|
|
159
|
+
this.workspaceHierarchy = workspaceTree;
|
|
160
|
+
return workspaceTree;
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
throw this.handleError(error, 'Failed to get workspace hierarchy');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Invalidate the workspace hierarchy cache
|
|
168
|
+
*/
|
|
169
|
+
invalidateWorkspaceCache() {
|
|
170
|
+
this.workspaceHierarchy = null;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Find a node in the workspace tree by name and type
|
|
174
|
+
* @param node - The node to start searching from
|
|
175
|
+
* @param name - The name to search for
|
|
176
|
+
* @param type - The type of node to search for
|
|
177
|
+
* @returns - The node and its path if found, null otherwise
|
|
178
|
+
*/
|
|
179
|
+
findNodeInTree(node, name, type) {
|
|
180
|
+
// If this is the node we're looking for, return it
|
|
181
|
+
if ('type' in node && node.type === type && node.name === name) {
|
|
182
|
+
return { node, path: node.name };
|
|
183
|
+
}
|
|
184
|
+
// Otherwise, search its children recursively
|
|
185
|
+
for (const child of (node.children || [])) {
|
|
186
|
+
const result = this.findNodeInTree(child, name, type);
|
|
187
|
+
if (result) {
|
|
188
|
+
// Prepend this node's name to the path
|
|
189
|
+
const currentNodeName = 'name' in node ? node.name : 'Workspace';
|
|
190
|
+
result.path = `${currentNodeName} > ${result.path}`;
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Not found in this subtree
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Find an ID by name and type in the workspace hierarchy
|
|
199
|
+
* @param hierarchy - The workspace hierarchy
|
|
200
|
+
* @param name - The name to search for
|
|
201
|
+
* @param type - The type of node to search for
|
|
202
|
+
* @returns - The ID and path if found, null otherwise
|
|
203
|
+
*/
|
|
204
|
+
findIDByNameInHierarchy(hierarchy, name, type) {
|
|
205
|
+
const result = this.findNodeInTree(hierarchy.root, name, type);
|
|
206
|
+
if (result) {
|
|
207
|
+
return { id: result.node.id, path: result.path };
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Find a space ID by name
|
|
213
|
+
* @param spaceName - The name of the space to find
|
|
214
|
+
* @returns - Promise resolving to the space ID or null if not found
|
|
215
|
+
*/
|
|
216
|
+
async findSpaceIDByName(spaceName) {
|
|
217
|
+
const space = await this.findSpaceByName(spaceName);
|
|
218
|
+
return space ? space.id : null;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get lists from the API (using the appropriate endpoint based on context)
|
|
222
|
+
* @param spaceId - The ID of the space
|
|
223
|
+
* @returns - Promise resolving to array of lists
|
|
224
|
+
*/
|
|
225
|
+
async getLists(spaceId) {
|
|
226
|
+
try {
|
|
227
|
+
const response = await this.makeRequest(async () => {
|
|
228
|
+
const result = await this.client.get(`/space/${spaceId}/list`);
|
|
229
|
+
return result.data;
|
|
230
|
+
});
|
|
231
|
+
return response.lists || [];
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
throw this.handleError(error, `Failed to get lists for space ${spaceId}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get folders from the API
|
|
239
|
+
* @param spaceId - The ID of the space
|
|
240
|
+
* @returns - Promise resolving to array of folders
|
|
241
|
+
*/
|
|
242
|
+
async getFolders(spaceId) {
|
|
243
|
+
try {
|
|
244
|
+
const response = await this.makeRequest(async () => {
|
|
245
|
+
const result = await this.client.get(`/space/${spaceId}/folder`);
|
|
246
|
+
return result.data;
|
|
247
|
+
});
|
|
248
|
+
return response.folders || [];
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
throw this.handleError(error, `Failed to get folders for space ${spaceId}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Get a specific folder by ID
|
|
256
|
+
* @param folderId - The ID of the folder to retrieve
|
|
257
|
+
* @returns - Promise resolving to the folder
|
|
258
|
+
*/
|
|
259
|
+
async getFolder(folderId) {
|
|
260
|
+
try {
|
|
261
|
+
return await this.makeRequest(async () => {
|
|
262
|
+
const result = await this.client.get(`/folder/${folderId}`);
|
|
263
|
+
return result.data;
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
throw this.handleError(error, `Failed to get folder with ID ${folderId}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Get lists in a space (not in any folder)
|
|
272
|
+
* @param spaceId - The ID of the space
|
|
273
|
+
* @returns - Promise resolving to array of lists
|
|
274
|
+
*/
|
|
275
|
+
async getListsInSpace(spaceId) {
|
|
276
|
+
try {
|
|
277
|
+
const lists = await this.getLists(spaceId);
|
|
278
|
+
return lists.filter((list) => !list.folder); // Filter lists not in a folder
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
throw this.handleError(error, `Failed to get lists in space ${spaceId}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Get folders in a space
|
|
286
|
+
* @param spaceId - The ID of the space
|
|
287
|
+
* @returns - Promise resolving to array of folders
|
|
288
|
+
*/
|
|
289
|
+
async getFoldersInSpace(spaceId) {
|
|
290
|
+
try {
|
|
291
|
+
return await this.getFolders(spaceId);
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
throw this.handleError(error, `Failed to get folders in space ${spaceId}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Get lists in a folder
|
|
299
|
+
* @param folderId - The ID of the folder
|
|
300
|
+
* @returns - Promise resolving to array of lists
|
|
301
|
+
*/
|
|
302
|
+
async getListsInFolder(folderId) {
|
|
303
|
+
try {
|
|
304
|
+
const response = await this.makeRequest(async () => {
|
|
305
|
+
const result = await this.client.get(`/folder/${folderId}/list`);
|
|
306
|
+
return result.data;
|
|
307
|
+
});
|
|
308
|
+
return response.lists || [];
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
throw this.handleError(error, `Failed to get lists in folder ${folderId}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Services Module
|
|
3
|
+
*
|
|
4
|
+
* This module maintains singleton instances of services that should be shared
|
|
5
|
+
* across the application to ensure consistent state.
|
|
6
|
+
*/
|
|
7
|
+
import { createClickUpServices } from './clickup/index.js';
|
|
8
|
+
import config from '../config.js';
|
|
9
|
+
// Create a single instance of ClickUp services to be shared
|
|
10
|
+
export const clickUpServices = createClickUpServices({
|
|
11
|
+
apiKey: config.clickupApiKey,
|
|
12
|
+
teamId: config.clickupTeamId
|
|
13
|
+
});
|
|
14
|
+
// Export individual services for convenience
|
|
15
|
+
export const { list: listService, task: taskService, folder: folderService, workspace: workspaceService } = clickUpServices;
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClickUp MCP Folder Tools
|
|
3
|
+
*
|
|
4
|
+
* This module defines folder-related tools for creating, retrieving,
|
|
5
|
+
* updating, and deleting folders in the ClickUp workspace hierarchy.
|
|
6
|
+
*/
|
|
7
|
+
import { createClickUpServices } from '../services/clickup/index.js';
|
|
8
|
+
import config from '../config.js';
|
|
9
|
+
// Initialize ClickUp services using the factory function
|
|
10
|
+
const services = createClickUpServices({
|
|
11
|
+
apiKey: config.clickupApiKey,
|
|
12
|
+
teamId: config.clickupTeamId
|
|
13
|
+
});
|
|
14
|
+
// Extract the services we need for folder operations
|
|
15
|
+
const { folder: folderService, workspace: workspaceService } = services;
|
|
16
|
+
/**
|
|
17
|
+
* Tool definition for creating a folder
|
|
18
|
+
*/
|
|
19
|
+
export const createFolderTool = {
|
|
20
|
+
name: "create_folder",
|
|
21
|
+
description: "Create a new folder in a ClickUp space for organizing related lists. You MUST provide:\n1. A folder name\n2. Either spaceId (preferred) or spaceName\n\nAfter creating a folder, you can add lists to it using create_list_in_folder.",
|
|
22
|
+
inputSchema: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
name: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Name of the folder"
|
|
28
|
+
},
|
|
29
|
+
spaceId: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "ID of the space to create the folder in (preferred). Use this instead of spaceName if you have it."
|
|
32
|
+
},
|
|
33
|
+
spaceName: {
|
|
34
|
+
type: "string",
|
|
35
|
+
description: "Name of the space to create the folder in. Only use if you don't have spaceId."
|
|
36
|
+
},
|
|
37
|
+
override_statuses: {
|
|
38
|
+
type: "boolean",
|
|
39
|
+
description: "Whether to override space statuses with folder-specific statuses"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
required: ["name"]
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Tool definition for getting folder details
|
|
47
|
+
*/
|
|
48
|
+
export const getFolderTool = {
|
|
49
|
+
name: "get_folder",
|
|
50
|
+
description: "Retrieve details about a specific folder including name, status, and metadata. Valid parameter combinations:\n1. Use folderId alone (preferred)\n2. Use folderName + (spaceId or spaceName)\n\nHelps you understand folder structure before creating or updating lists.",
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
folderId: {
|
|
55
|
+
type: "string",
|
|
56
|
+
description: "ID of folder to retrieve (preferred). Use this instead of folderName if you have it."
|
|
57
|
+
},
|
|
58
|
+
folderName: {
|
|
59
|
+
type: "string",
|
|
60
|
+
description: "Name of folder to retrieve. When using this, you MUST also provide spaceId or spaceName."
|
|
61
|
+
},
|
|
62
|
+
spaceId: {
|
|
63
|
+
type: "string",
|
|
64
|
+
description: "ID of space containing the folder (required with folderName). Use this instead of spaceName if you have it."
|
|
65
|
+
},
|
|
66
|
+
spaceName: {
|
|
67
|
+
type: "string",
|
|
68
|
+
description: "Name of space containing the folder (required with folderName). Only use if you don't have spaceId."
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
required: []
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Tool definition for updating a folder
|
|
76
|
+
*/
|
|
77
|
+
export const updateFolderTool = {
|
|
78
|
+
name: "update_folder",
|
|
79
|
+
description: "Modify an existing folder's properties. Valid parameter combinations:\n1. Use folderId alone (preferred)\n2. Use folderName + (spaceId or spaceName)\n\nAt least one update field (name or override_statuses) must be provided.",
|
|
80
|
+
inputSchema: {
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {
|
|
83
|
+
folderId: {
|
|
84
|
+
type: "string",
|
|
85
|
+
description: "ID of folder to update (preferred). Use this instead of folderName if you have it."
|
|
86
|
+
},
|
|
87
|
+
folderName: {
|
|
88
|
+
type: "string",
|
|
89
|
+
description: "Name of folder to update. When using this, you MUST also provide spaceId or spaceName."
|
|
90
|
+
},
|
|
91
|
+
spaceId: {
|
|
92
|
+
type: "string",
|
|
93
|
+
description: "ID of space containing the folder (required with folderName). Use this instead of spaceName if you have it."
|
|
94
|
+
},
|
|
95
|
+
spaceName: {
|
|
96
|
+
type: "string",
|
|
97
|
+
description: "Name of space containing the folder (required with folderName). Only use if you don't have spaceId."
|
|
98
|
+
},
|
|
99
|
+
name: {
|
|
100
|
+
type: "string",
|
|
101
|
+
description: "New name for the folder"
|
|
102
|
+
},
|
|
103
|
+
override_statuses: {
|
|
104
|
+
type: "boolean",
|
|
105
|
+
description: "Whether to override space statuses with folder-specific statuses"
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
required: []
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Tool definition for deleting a folder
|
|
113
|
+
*/
|
|
114
|
+
export const deleteFolderTool = {
|
|
115
|
+
name: "delete_folder",
|
|
116
|
+
description: "⚠️ PERMANENTLY DELETE a folder and all its contents. This action cannot be undone. Valid parameter combinations:\n1. Use folderId alone (preferred and safest)\n2. Use folderName + (spaceId or spaceName)\n\nWARNING: This will also delete all lists and tasks within the folder.",
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: "object",
|
|
119
|
+
properties: {
|
|
120
|
+
folderId: {
|
|
121
|
+
type: "string",
|
|
122
|
+
description: "ID of folder to delete (preferred). Use this instead of folderName for safety."
|
|
123
|
+
},
|
|
124
|
+
folderName: {
|
|
125
|
+
type: "string",
|
|
126
|
+
description: "Name of folder to delete. When using this, you MUST also provide spaceId or spaceName."
|
|
127
|
+
},
|
|
128
|
+
spaceId: {
|
|
129
|
+
type: "string",
|
|
130
|
+
description: "ID of space containing the folder (required with folderName). Use this instead of spaceName if you have it."
|
|
131
|
+
},
|
|
132
|
+
spaceName: {
|
|
133
|
+
type: "string",
|
|
134
|
+
description: "Name of space containing the folder (required with folderName). Only use if you don't have spaceId."
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
required: []
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Handler for the create_folder tool
|
|
142
|
+
* Creates a new folder in a space
|
|
143
|
+
*/
|
|
144
|
+
export async function handleCreateFolder(parameters) {
|
|
145
|
+
const { name, spaceId, spaceName, override_statuses } = parameters;
|
|
146
|
+
// Validate required fields
|
|
147
|
+
if (!name) {
|
|
148
|
+
throw new Error("Folder name is required");
|
|
149
|
+
}
|
|
150
|
+
let targetSpaceId = spaceId;
|
|
151
|
+
// If no spaceId but spaceName is provided, look up the space ID
|
|
152
|
+
if (!targetSpaceId && spaceName) {
|
|
153
|
+
const spaceIdResult = await workspaceService.findSpaceByName(spaceName);
|
|
154
|
+
if (!spaceIdResult) {
|
|
155
|
+
throw new Error(`Space "${spaceName}" not found`);
|
|
156
|
+
}
|
|
157
|
+
targetSpaceId = spaceIdResult.id;
|
|
158
|
+
}
|
|
159
|
+
if (!targetSpaceId) {
|
|
160
|
+
throw new Error("Either spaceId or spaceName must be provided");
|
|
161
|
+
}
|
|
162
|
+
// Prepare folder data
|
|
163
|
+
const folderData = {
|
|
164
|
+
name
|
|
165
|
+
};
|
|
166
|
+
// Add optional fields if provided
|
|
167
|
+
if (override_statuses !== undefined)
|
|
168
|
+
folderData.override_statuses = override_statuses;
|
|
169
|
+
try {
|
|
170
|
+
// Create the folder
|
|
171
|
+
const newFolder = await folderService.createFolder(targetSpaceId, folderData);
|
|
172
|
+
return {
|
|
173
|
+
content: [{
|
|
174
|
+
type: "text",
|
|
175
|
+
text: JSON.stringify({
|
|
176
|
+
id: newFolder.id,
|
|
177
|
+
name: newFolder.name,
|
|
178
|
+
space: {
|
|
179
|
+
id: newFolder.space.id,
|
|
180
|
+
name: newFolder.space.name
|
|
181
|
+
},
|
|
182
|
+
message: `Folder "${newFolder.name}" created successfully`
|
|
183
|
+
}, null, 2)
|
|
184
|
+
}]
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
throw new Error(`Failed to create folder: ${error.message}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Handler for the get_folder tool
|
|
193
|
+
* Retrieves details about a specific folder
|
|
194
|
+
*/
|
|
195
|
+
export async function handleGetFolder(parameters) {
|
|
196
|
+
const { folderId, folderName, spaceId, spaceName } = parameters;
|
|
197
|
+
let targetFolderId = folderId;
|
|
198
|
+
// If no folderId provided but folderName is, look up the folder ID
|
|
199
|
+
if (!targetFolderId && folderName) {
|
|
200
|
+
let targetSpaceId = spaceId;
|
|
201
|
+
// If no spaceId provided but spaceName is, look up the space ID first
|
|
202
|
+
if (!targetSpaceId && spaceName) {
|
|
203
|
+
const spaceIdResult = await workspaceService.findSpaceByName(spaceName);
|
|
204
|
+
if (!spaceIdResult) {
|
|
205
|
+
throw new Error(`Space "${spaceName}" not found`);
|
|
206
|
+
}
|
|
207
|
+
targetSpaceId = spaceIdResult.id;
|
|
208
|
+
}
|
|
209
|
+
if (!targetSpaceId) {
|
|
210
|
+
throw new Error("Either spaceId or spaceName must be provided when using folderName");
|
|
211
|
+
}
|
|
212
|
+
const folderResult = await folderService.findFolderByName(targetSpaceId, folderName);
|
|
213
|
+
if (!folderResult) {
|
|
214
|
+
throw new Error(`Folder "${folderName}" not found in space`);
|
|
215
|
+
}
|
|
216
|
+
targetFolderId = folderResult.id;
|
|
217
|
+
}
|
|
218
|
+
if (!targetFolderId) {
|
|
219
|
+
throw new Error("Either folderId or folderName must be provided");
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
// Get the folder
|
|
223
|
+
const folder = await folderService.getFolder(targetFolderId);
|
|
224
|
+
return {
|
|
225
|
+
content: [{
|
|
226
|
+
type: "text",
|
|
227
|
+
text: JSON.stringify({
|
|
228
|
+
id: folder.id,
|
|
229
|
+
name: folder.name,
|
|
230
|
+
lists: folder.lists.map((list) => ({
|
|
231
|
+
id: list.id,
|
|
232
|
+
name: list.name
|
|
233
|
+
})),
|
|
234
|
+
space: {
|
|
235
|
+
id: folder.space.id,
|
|
236
|
+
name: folder.space.name
|
|
237
|
+
}
|
|
238
|
+
}, null, 2)
|
|
239
|
+
}]
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
throw new Error(`Failed to retrieve folder: ${error.message}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Handler for the update_folder tool
|
|
248
|
+
* Updates an existing folder's properties
|
|
249
|
+
*/
|
|
250
|
+
export async function handleUpdateFolder(parameters) {
|
|
251
|
+
const { folderId, folderName, name, override_statuses, spaceId, spaceName } = parameters;
|
|
252
|
+
let targetFolderId = folderId;
|
|
253
|
+
// If no folderId provided but folderName is, look up the folder ID
|
|
254
|
+
if (!targetFolderId && folderName) {
|
|
255
|
+
let targetSpaceId = spaceId;
|
|
256
|
+
// If no spaceId provided but spaceName is, look up the space ID first
|
|
257
|
+
if (!targetSpaceId && spaceName) {
|
|
258
|
+
const spaceIdResult = await workspaceService.findSpaceByName(spaceName);
|
|
259
|
+
if (!spaceIdResult) {
|
|
260
|
+
throw new Error(`Space "${spaceName}" not found`);
|
|
261
|
+
}
|
|
262
|
+
targetSpaceId = spaceIdResult.id;
|
|
263
|
+
}
|
|
264
|
+
if (!targetSpaceId) {
|
|
265
|
+
throw new Error("Either spaceId or spaceName must be provided when using folderName");
|
|
266
|
+
}
|
|
267
|
+
const folderResult = await folderService.findFolderByName(targetSpaceId, folderName);
|
|
268
|
+
if (!folderResult) {
|
|
269
|
+
throw new Error(`Folder "${folderName}" not found in space`);
|
|
270
|
+
}
|
|
271
|
+
targetFolderId = folderResult.id;
|
|
272
|
+
}
|
|
273
|
+
if (!targetFolderId) {
|
|
274
|
+
throw new Error("Either folderId or folderName must be provided");
|
|
275
|
+
}
|
|
276
|
+
// Ensure at least one update field is provided
|
|
277
|
+
if (!name && override_statuses === undefined) {
|
|
278
|
+
throw new Error("At least one of name or override_statuses must be provided for update");
|
|
279
|
+
}
|
|
280
|
+
// Prepare update data
|
|
281
|
+
const updateData = {};
|
|
282
|
+
if (name)
|
|
283
|
+
updateData.name = name;
|
|
284
|
+
if (override_statuses !== undefined)
|
|
285
|
+
updateData.override_statuses = override_statuses;
|
|
286
|
+
try {
|
|
287
|
+
// Update the folder
|
|
288
|
+
const updatedFolder = await folderService.updateFolder(targetFolderId, updateData);
|
|
289
|
+
return {
|
|
290
|
+
content: [{
|
|
291
|
+
type: "text",
|
|
292
|
+
text: JSON.stringify({
|
|
293
|
+
id: updatedFolder.id,
|
|
294
|
+
name: updatedFolder.name,
|
|
295
|
+
space: {
|
|
296
|
+
id: updatedFolder.space.id,
|
|
297
|
+
name: updatedFolder.space.name
|
|
298
|
+
},
|
|
299
|
+
message: `Folder "${updatedFolder.name}" updated successfully`
|
|
300
|
+
}, null, 2)
|
|
301
|
+
}]
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
throw new Error(`Failed to update folder: ${error.message}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Handler for the delete_folder tool
|
|
310
|
+
* Permanently removes a folder from the workspace
|
|
311
|
+
*/
|
|
312
|
+
export async function handleDeleteFolder(parameters) {
|
|
313
|
+
const { folderId, folderName, spaceId, spaceName } = parameters;
|
|
314
|
+
let targetFolderId = folderId;
|
|
315
|
+
// If no folderId provided but folderName is, look up the folder ID
|
|
316
|
+
if (!targetFolderId && folderName) {
|
|
317
|
+
let targetSpaceId = spaceId;
|
|
318
|
+
// If no spaceId provided but spaceName is, look up the space ID first
|
|
319
|
+
if (!targetSpaceId && spaceName) {
|
|
320
|
+
const spaceIdResult = await workspaceService.findSpaceByName(spaceName);
|
|
321
|
+
if (!spaceIdResult) {
|
|
322
|
+
throw new Error(`Space "${spaceName}" not found`);
|
|
323
|
+
}
|
|
324
|
+
targetSpaceId = spaceIdResult.id;
|
|
325
|
+
}
|
|
326
|
+
if (!targetSpaceId) {
|
|
327
|
+
throw new Error("Either spaceId or spaceName must be provided when using folderName");
|
|
328
|
+
}
|
|
329
|
+
const folderResult = await folderService.findFolderByName(targetSpaceId, folderName);
|
|
330
|
+
if (!folderResult) {
|
|
331
|
+
throw new Error(`Folder "${folderName}" not found in space`);
|
|
332
|
+
}
|
|
333
|
+
targetFolderId = folderResult.id;
|
|
334
|
+
}
|
|
335
|
+
if (!targetFolderId) {
|
|
336
|
+
throw new Error("Either folderId or folderName must be provided");
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
// Get folder details before deletion for confirmation message
|
|
340
|
+
const folder = await folderService.getFolder(targetFolderId);
|
|
341
|
+
const folderName = folder.name;
|
|
342
|
+
// Delete the folder
|
|
343
|
+
await folderService.deleteFolder(targetFolderId);
|
|
344
|
+
return {
|
|
345
|
+
content: [{
|
|
346
|
+
type: "text",
|
|
347
|
+
text: JSON.stringify({
|
|
348
|
+
message: `Folder "${folderName}" deleted successfully`
|
|
349
|
+
}, null, 2)
|
|
350
|
+
}]
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
throw new Error(`Failed to delete folder: ${error.message}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools Index
|
|
3
|
+
*
|
|
4
|
+
* This file exports all tool definitions and handlers for the ClickUp MCP server.
|
|
5
|
+
* Each tool category (workspace, task, list, folder) is organized into its own module
|
|
6
|
+
* for better maintainability.
|
|
7
|
+
*/
|
|
8
|
+
export * from './workspace.js';
|
|
9
|
+
export * from './task.js';
|
|
10
|
+
export * from './list.js';
|
|
11
|
+
export * from './folder.js';
|