@taazkareem/clickup-mcp-server 0.2.0 → 0.2.2
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 +21 -0
- package/README.md +91 -188
- package/build/config.js +4 -9
- package/build/index.js +488 -171
- package/build/services/clickup.js +643 -49
- package/package.json +4 -2
package/build/index.js
CHANGED
|
@@ -1,98 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
3
|
+
* ClickUp MCP Server - A Model Context Protocol server for ClickUp integration
|
|
4
|
+
*
|
|
5
|
+
* This server enables AI applications to interact with ClickUp through a standardized protocol.
|
|
6
|
+
* Key capabilities include:
|
|
7
|
+
*
|
|
8
|
+
* Tools:
|
|
9
|
+
* - Task Management: Create, update, move and duplicate tasks
|
|
10
|
+
* - Bulk task creation and management
|
|
11
|
+
* - Workspace Organization: Create lists, folders and manage hierarchy
|
|
12
|
+
* - Smart lookups by name or ID with case-insensitive matching
|
|
13
|
+
*
|
|
14
|
+
* Prompts:
|
|
15
|
+
* - Task summarization and status grouping
|
|
16
|
+
* - Priority analysis and optimization
|
|
17
|
+
* - Detailed task description generation
|
|
18
|
+
* - Task relationship insights
|
|
19
|
+
*
|
|
20
|
+
* Features markdown support, secure credential handling, and comprehensive error reporting.
|
|
12
21
|
*/
|
|
13
22
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
14
23
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
|
-
import { CallToolRequestSchema,
|
|
24
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
16
25
|
import { ClickUpService } from "./services/clickup.js";
|
|
17
26
|
import config from "./config.js";
|
|
18
|
-
/**
|
|
19
|
-
* Simple in-memory storage for notes.
|
|
20
|
-
* In a real implementation, this would likely be backed by a database.
|
|
21
|
-
*/
|
|
22
|
-
const notes = {
|
|
23
|
-
"1": { title: "First Note", content: "This is note 1" },
|
|
24
|
-
"2": { title: "Second Note", content: "This is note 2" }
|
|
25
|
-
};
|
|
26
27
|
// Initialize ClickUp service
|
|
27
|
-
const clickup = ClickUpService.initialize(config.clickupApiKey);
|
|
28
|
+
const clickup = ClickUpService.initialize(config.clickupApiKey, config.clickupTeamId);
|
|
28
29
|
/**
|
|
29
|
-
* Create an MCP server with capabilities for
|
|
30
|
-
*
|
|
30
|
+
* Create an MCP server with capabilities for tools and prompts.
|
|
31
|
+
* Resources have been removed as they are being replaced with direct tool calls.
|
|
31
32
|
*/
|
|
32
33
|
const server = new Server({
|
|
33
34
|
name: "clickup-mcp-server",
|
|
34
35
|
version: "0.1.0",
|
|
35
36
|
}, {
|
|
36
37
|
capabilities: {
|
|
37
|
-
resources: {},
|
|
38
38
|
tools: {},
|
|
39
39
|
prompts: {},
|
|
40
40
|
},
|
|
41
41
|
});
|
|
42
|
-
/**
|
|
43
|
-
* Handler for listing available ClickUp tasks as resources.
|
|
44
|
-
* Each task is exposed as a resource with:
|
|
45
|
-
* - A clickup:// URI scheme
|
|
46
|
-
* - JSON MIME type
|
|
47
|
-
* - Human readable name and description (including the task name and description)
|
|
48
|
-
*/
|
|
49
|
-
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
50
|
-
try {
|
|
51
|
-
const spaces = await clickup.getSpaces(config.teamId);
|
|
52
|
-
const resources = [];
|
|
53
|
-
for (const space of spaces) {
|
|
54
|
-
const lists = await clickup.getLists(space.id);
|
|
55
|
-
for (const list of lists) {
|
|
56
|
-
const { tasks } = await clickup.getTasks(list.id);
|
|
57
|
-
resources.push(...tasks.map((task) => ({
|
|
58
|
-
uri: `clickup://task/${task.id}`,
|
|
59
|
-
mimeType: "application/json",
|
|
60
|
-
name: task.name,
|
|
61
|
-
description: task.description || `Task in ${list.name} (${space.name})`,
|
|
62
|
-
tags: []
|
|
63
|
-
})));
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return { resources };
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
console.error('Error listing resources:', error);
|
|
70
|
-
throw error;
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
/**
|
|
74
|
-
* Handler for reading the contents of a specific ClickUp task.
|
|
75
|
-
* Takes a clickup:// URI and returns the task content as JSON.
|
|
76
|
-
*/
|
|
77
|
-
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
78
|
-
try {
|
|
79
|
-
const url = new URL(request.params.uri);
|
|
80
|
-
const taskId = url.pathname.replace(/^\/task\//, '');
|
|
81
|
-
const task = await clickup.getTask(taskId);
|
|
82
|
-
return {
|
|
83
|
-
contents: [{
|
|
84
|
-
uri: request.params.uri,
|
|
85
|
-
mimeType: "application/json",
|
|
86
|
-
text: JSON.stringify(task, null, 2),
|
|
87
|
-
tags: []
|
|
88
|
-
}]
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
catch (error) {
|
|
92
|
-
console.error('Error reading resource:', error);
|
|
93
|
-
throw error;
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
42
|
/**
|
|
97
43
|
* Handler that lists available tools.
|
|
98
44
|
* Exposes tools for listing spaces, creating tasks, and updating tasks.
|
|
@@ -101,8 +47,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
101
47
|
return {
|
|
102
48
|
tools: [
|
|
103
49
|
{
|
|
104
|
-
name: "
|
|
105
|
-
description: "
|
|
50
|
+
name: "get_workspace_hierarchy",
|
|
51
|
+
description: "Get the complete hierarchy of spaces, folders, and lists in the workspace. -First check chat history for space, folder, and list names or IDs. If not found, use this tool to get necessary information.",
|
|
106
52
|
inputSchema: {
|
|
107
53
|
type: "object",
|
|
108
54
|
properties: {},
|
|
@@ -111,13 +57,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
111
57
|
},
|
|
112
58
|
{
|
|
113
59
|
name: "create_task",
|
|
114
|
-
description: "Create a new task in ClickUp",
|
|
60
|
+
description: "Create a new task in ClickUp. Supports direct name-based lookup for lists - no need to know the list ID. -Status will use ClickUp defaults if not specified. If the specified list doesn't exist, you can create it using create_list or create_list_in_folder.",
|
|
115
61
|
inputSchema: {
|
|
116
62
|
type: "object",
|
|
117
63
|
properties: {
|
|
118
64
|
listId: {
|
|
119
65
|
type: "string",
|
|
120
|
-
description: "ID of the list to create the task in"
|
|
66
|
+
description: "ID of the list to create the task in (optional if using listName instead)"
|
|
67
|
+
},
|
|
68
|
+
listName: {
|
|
69
|
+
type: "string",
|
|
70
|
+
description: "Name of the list to create the task in - will automatically find the list by name (optional if using listId instead)"
|
|
121
71
|
},
|
|
122
72
|
name: {
|
|
123
73
|
type: "string",
|
|
@@ -125,37 +75,100 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
125
75
|
},
|
|
126
76
|
description: {
|
|
127
77
|
type: "string",
|
|
128
|
-
description: "
|
|
78
|
+
description: "Plain text description for the task"
|
|
79
|
+
},
|
|
80
|
+
markdown_description: {
|
|
81
|
+
type: "string",
|
|
82
|
+
description: "Markdown formatted description for the task. If provided, this takes precedence over description"
|
|
129
83
|
},
|
|
130
84
|
status: {
|
|
131
85
|
type: "string",
|
|
132
|
-
description: "
|
|
86
|
+
description: "OPTIONAL: Override the default ClickUp status. In most cases, you should omit this to use ClickUp defaults"
|
|
133
87
|
},
|
|
134
88
|
priority: {
|
|
135
89
|
type: "number",
|
|
136
|
-
description: "Priority of the task (1-4)"
|
|
90
|
+
description: "Priority of the task (1-4), 1 is highest priority, 4 is lowest priority. -If not specified, the task will not set a priority."
|
|
137
91
|
},
|
|
138
92
|
dueDate: {
|
|
139
93
|
type: "string",
|
|
140
|
-
description: "Due date of the task (
|
|
94
|
+
description: "Due date of the task (Unix timestamp in milliseconds)"
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
required: ["name"]
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "create_bulk_tasks",
|
|
102
|
+
description: "Create multiple tasks in a ClickUp list. Supports direct name-based lookup for lists - no need to know the list ID. -Tasks will use ClickUp default status if not specified. If the specified list doesn't exist, you can create it using create_list or create_list_in_folder.",
|
|
103
|
+
inputSchema: {
|
|
104
|
+
type: "object",
|
|
105
|
+
properties: {
|
|
106
|
+
listId: {
|
|
107
|
+
type: "string",
|
|
108
|
+
description: "ID of the list to create the tasks in (optional if using listName instead)"
|
|
109
|
+
},
|
|
110
|
+
listName: {
|
|
111
|
+
type: "string",
|
|
112
|
+
description: "Name of the list to create the tasks in - will automatically find the list by name (optional if using listId instead)"
|
|
113
|
+
},
|
|
114
|
+
tasks: {
|
|
115
|
+
type: "array",
|
|
116
|
+
description: "Array of tasks to create",
|
|
117
|
+
items: {
|
|
118
|
+
type: "object",
|
|
119
|
+
properties: {
|
|
120
|
+
name: {
|
|
121
|
+
type: "string",
|
|
122
|
+
description: "Name of the task"
|
|
123
|
+
},
|
|
124
|
+
description: {
|
|
125
|
+
type: "string",
|
|
126
|
+
description: "Plain text description for the task"
|
|
127
|
+
},
|
|
128
|
+
markdown_description: {
|
|
129
|
+
type: "string",
|
|
130
|
+
description: "Markdown formatted description for the task. If provided, this takes precedence over description"
|
|
131
|
+
},
|
|
132
|
+
status: {
|
|
133
|
+
type: "string",
|
|
134
|
+
description: "OPTIONAL: Override the default ClickUp status. In most cases, you should omit this to use ClickUp defaults"
|
|
135
|
+
},
|
|
136
|
+
priority: {
|
|
137
|
+
type: "number",
|
|
138
|
+
description: "Priority level (1-4), 1 is highest priority, 4 is lowest priority"
|
|
139
|
+
},
|
|
140
|
+
dueDate: {
|
|
141
|
+
type: "string",
|
|
142
|
+
description: "Due date (Unix timestamp in milliseconds)"
|
|
143
|
+
},
|
|
144
|
+
assignees: {
|
|
145
|
+
type: "array",
|
|
146
|
+
items: {
|
|
147
|
+
type: "number"
|
|
148
|
+
},
|
|
149
|
+
description: "Array of user IDs to assign to the task"
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
required: ["name"]
|
|
153
|
+
}
|
|
141
154
|
}
|
|
142
155
|
},
|
|
143
|
-
required: ["listId", "
|
|
156
|
+
required: ["listId", "tasks"]
|
|
144
157
|
}
|
|
145
158
|
},
|
|
146
159
|
{
|
|
147
160
|
name: "create_list",
|
|
148
|
-
description: "Create a new list in a ClickUp space",
|
|
161
|
+
description: "Create a new list in a ClickUp space. Supports direct name-based lookup for spaces - no need to know the space ID. If the specified space doesn't exist, you can create it through the ClickUp web interface (space creation via API not supported).",
|
|
149
162
|
inputSchema: {
|
|
150
163
|
type: "object",
|
|
151
164
|
properties: {
|
|
152
165
|
spaceId: {
|
|
153
166
|
type: "string",
|
|
154
|
-
description: "ID of the space to create the list in (optional if spaceName
|
|
167
|
+
description: "ID of the space to create the list in (optional if using spaceName instead)"
|
|
155
168
|
},
|
|
156
169
|
spaceName: {
|
|
157
170
|
type: "string",
|
|
158
|
-
description: "Name of the space to create the list in (optional if spaceId
|
|
171
|
+
description: "Name of the space to create the list in - will automatically find the space by name (optional if using spaceId instead)"
|
|
159
172
|
},
|
|
160
173
|
name: {
|
|
161
174
|
type: "string",
|
|
@@ -187,17 +200,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
187
200
|
},
|
|
188
201
|
{
|
|
189
202
|
name: "create_folder",
|
|
190
|
-
description: "Create a new folder in a ClickUp space",
|
|
203
|
+
description: "Create a new folder in a ClickUp space. Supports direct name-based lookup for spaces - no need to know the space ID. If the specified space doesn't exist, you can create it through the ClickUp web interface (space creation via API not supported).",
|
|
191
204
|
inputSchema: {
|
|
192
205
|
type: "object",
|
|
193
206
|
properties: {
|
|
194
207
|
spaceId: {
|
|
195
208
|
type: "string",
|
|
196
|
-
description: "ID of the space to create the folder in (optional if spaceName
|
|
209
|
+
description: "ID of the space to create the folder in (optional if using spaceName instead)"
|
|
197
210
|
},
|
|
198
211
|
spaceName: {
|
|
199
212
|
type: "string",
|
|
200
|
-
description: "Name of the space to create the folder in (optional if spaceId
|
|
213
|
+
description: "Name of the space to create the folder in - will automatically find the space by name (optional if using spaceId instead)"
|
|
201
214
|
},
|
|
202
215
|
name: {
|
|
203
216
|
type: "string",
|
|
@@ -213,25 +226,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
213
226
|
},
|
|
214
227
|
{
|
|
215
228
|
name: "create_list_in_folder",
|
|
216
|
-
description: "Create a new list in a ClickUp folder",
|
|
229
|
+
description: "Create a new list in a ClickUp folder. Supports direct name-based lookup for folders and spaces - no need to know IDs. If the specified folder doesn't exist, you can create it using create_folder. If the space doesn't exist, it must be created through the ClickUp web interface.",
|
|
217
230
|
inputSchema: {
|
|
218
231
|
type: "object",
|
|
219
232
|
properties: {
|
|
220
233
|
folderId: {
|
|
221
234
|
type: "string",
|
|
222
|
-
description: "ID of the folder to create the list in (optional if folderName
|
|
235
|
+
description: "ID of the folder to create the list in (optional if using folderName instead)"
|
|
223
236
|
},
|
|
224
237
|
folderName: {
|
|
225
238
|
type: "string",
|
|
226
|
-
description: "Name of the folder to create the list in"
|
|
239
|
+
description: "Name of the folder to create the list in - will automatically find the folder by name (optional if using folderId instead)"
|
|
227
240
|
},
|
|
228
241
|
spaceId: {
|
|
229
242
|
type: "string",
|
|
230
|
-
description: "ID of the space containing the folder (
|
|
243
|
+
description: "ID of the space containing the folder (optional if using spaceName instead)"
|
|
231
244
|
},
|
|
232
245
|
spaceName: {
|
|
233
246
|
type: "string",
|
|
234
|
-
description: "Name of the space containing the folder (
|
|
247
|
+
description: "Name of the space containing the folder - will automatically find the space by name (optional if using spaceId instead)"
|
|
235
248
|
},
|
|
236
249
|
name: {
|
|
237
250
|
type: "string",
|
|
@@ -251,49 +264,81 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
251
264
|
},
|
|
252
265
|
{
|
|
253
266
|
name: "move_task",
|
|
254
|
-
description: "Move a task to a different list",
|
|
267
|
+
description: "Move a task to a different list. Supports direct name-based lookup for lists and tasks - no need to know IDs.",
|
|
255
268
|
inputSchema: {
|
|
256
269
|
type: "object",
|
|
257
270
|
properties: {
|
|
258
271
|
taskId: {
|
|
259
272
|
type: "string",
|
|
260
|
-
description: "ID of the task to move"
|
|
273
|
+
description: "ID of the task to move (optional if using taskName instead)"
|
|
274
|
+
},
|
|
275
|
+
taskName: {
|
|
276
|
+
type: "string",
|
|
277
|
+
description: "Name of the task to move - will automatically find the task by name (optional if using taskId instead)"
|
|
278
|
+
},
|
|
279
|
+
sourceListName: {
|
|
280
|
+
type: "string",
|
|
281
|
+
description: "Optional: Name of the list to narrow down task search"
|
|
261
282
|
},
|
|
262
283
|
listId: {
|
|
263
284
|
type: "string",
|
|
264
|
-
description: "ID of the destination list"
|
|
285
|
+
description: "ID of the destination list (optional if using listName instead)"
|
|
286
|
+
},
|
|
287
|
+
listName: {
|
|
288
|
+
type: "string",
|
|
289
|
+
description: "Name of the destination list - will automatically find the list by name (optional if using listId instead)"
|
|
265
290
|
}
|
|
266
291
|
},
|
|
267
|
-
required: [
|
|
292
|
+
required: []
|
|
268
293
|
}
|
|
269
294
|
},
|
|
270
295
|
{
|
|
271
296
|
name: "duplicate_task",
|
|
272
|
-
description: "Duplicate a task to a list",
|
|
297
|
+
description: "Duplicate a task to a list. Supports direct name-based lookup for lists - no need to know the list ID. If the destination list doesn't exist, you can create it using create_list or create_list_in_folder.",
|
|
273
298
|
inputSchema: {
|
|
274
299
|
type: "object",
|
|
275
300
|
properties: {
|
|
276
301
|
taskId: {
|
|
277
302
|
type: "string",
|
|
278
|
-
description: "ID of the task to duplicate"
|
|
303
|
+
description: "ID of the task to duplicate (optional if using taskName instead)"
|
|
304
|
+
},
|
|
305
|
+
taskName: {
|
|
306
|
+
type: "string",
|
|
307
|
+
description: "Name of the task to duplicate - will automatically find the task by name (optional if using taskId instead)"
|
|
308
|
+
},
|
|
309
|
+
sourceListName: {
|
|
310
|
+
type: "string",
|
|
311
|
+
description: "Optional: Name of the list to narrow down task search"
|
|
279
312
|
},
|
|
280
313
|
listId: {
|
|
281
314
|
type: "string",
|
|
282
|
-
description: "ID of the list to create the duplicate in"
|
|
315
|
+
description: "ID of the list to create the duplicate in (optional if using listName instead)"
|
|
316
|
+
},
|
|
317
|
+
listName: {
|
|
318
|
+
type: "string",
|
|
319
|
+
description: "Name of the list to create the duplicate in - will automatically find the list by name (optional if using listId instead)"
|
|
283
320
|
}
|
|
284
321
|
},
|
|
285
|
-
required: [
|
|
322
|
+
required: []
|
|
286
323
|
}
|
|
287
324
|
},
|
|
288
325
|
{
|
|
289
326
|
name: "update_task",
|
|
290
|
-
description: "Update an existing task in ClickUp",
|
|
327
|
+
description: "Update an existing task in ClickUp. Supports direct name-based lookup for tasks - no need to know the task ID.",
|
|
291
328
|
inputSchema: {
|
|
292
329
|
type: "object",
|
|
293
330
|
properties: {
|
|
294
331
|
taskId: {
|
|
295
332
|
type: "string",
|
|
296
|
-
description: "ID of the task to update"
|
|
333
|
+
description: "ID of the task to update (optional if using taskName instead)"
|
|
334
|
+
},
|
|
335
|
+
taskName: {
|
|
336
|
+
type: "string",
|
|
337
|
+
description: "Name of the task to update - will automatically find the task by name (optional if using taskId instead)"
|
|
338
|
+
},
|
|
339
|
+
listName: {
|
|
340
|
+
type: "string",
|
|
341
|
+
description: "Optional: Name of the list to narrow down task search"
|
|
297
342
|
},
|
|
298
343
|
name: {
|
|
299
344
|
type: "string",
|
|
@@ -316,6 +361,122 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
316
361
|
description: "New due date of the task (ISO string)"
|
|
317
362
|
}
|
|
318
363
|
},
|
|
364
|
+
required: []
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
name: "get_tasks",
|
|
369
|
+
description: "Get tasks from a ClickUp list with optional filters. Supports direct name-based lookup for lists - no need to know the list ID. If the list doesn't exist, you can create it using create_list or create_list_in_folder.",
|
|
370
|
+
inputSchema: {
|
|
371
|
+
type: "object",
|
|
372
|
+
properties: {
|
|
373
|
+
listId: {
|
|
374
|
+
type: "string",
|
|
375
|
+
description: "ID of the list to get tasks from (optional if using listName instead)"
|
|
376
|
+
},
|
|
377
|
+
listName: {
|
|
378
|
+
type: "string",
|
|
379
|
+
description: "Name of the list to get tasks from - will automatically find the list by name (optional if using listId instead)"
|
|
380
|
+
},
|
|
381
|
+
archived: {
|
|
382
|
+
type: "boolean",
|
|
383
|
+
description: "Include archived tasks"
|
|
384
|
+
},
|
|
385
|
+
page: {
|
|
386
|
+
type: "number",
|
|
387
|
+
description: "Page number for pagination"
|
|
388
|
+
},
|
|
389
|
+
order_by: {
|
|
390
|
+
type: "string",
|
|
391
|
+
description: "Field to order tasks by"
|
|
392
|
+
},
|
|
393
|
+
reverse: {
|
|
394
|
+
type: "boolean",
|
|
395
|
+
description: "Reverse the order of tasks"
|
|
396
|
+
},
|
|
397
|
+
subtasks: {
|
|
398
|
+
type: "boolean",
|
|
399
|
+
description: "Include subtasks"
|
|
400
|
+
},
|
|
401
|
+
statuses: {
|
|
402
|
+
type: "array",
|
|
403
|
+
items: { type: "string" },
|
|
404
|
+
description: "Filter tasks by status"
|
|
405
|
+
},
|
|
406
|
+
include_closed: {
|
|
407
|
+
type: "boolean",
|
|
408
|
+
description: "Include closed tasks"
|
|
409
|
+
},
|
|
410
|
+
assignees: {
|
|
411
|
+
type: "array",
|
|
412
|
+
items: { type: "string" },
|
|
413
|
+
description: "Filter tasks by assignee IDs"
|
|
414
|
+
},
|
|
415
|
+
due_date_gt: {
|
|
416
|
+
type: "number",
|
|
417
|
+
description: "Filter tasks due after this timestamp"
|
|
418
|
+
},
|
|
419
|
+
due_date_lt: {
|
|
420
|
+
type: "number",
|
|
421
|
+
description: "Filter tasks due before this timestamp"
|
|
422
|
+
},
|
|
423
|
+
date_created_gt: {
|
|
424
|
+
type: "number",
|
|
425
|
+
description: "Filter tasks created after this timestamp"
|
|
426
|
+
},
|
|
427
|
+
date_created_lt: {
|
|
428
|
+
type: "number",
|
|
429
|
+
description: "Filter tasks created before this timestamp"
|
|
430
|
+
},
|
|
431
|
+
date_updated_gt: {
|
|
432
|
+
type: "number",
|
|
433
|
+
description: "Filter tasks updated after this timestamp"
|
|
434
|
+
},
|
|
435
|
+
date_updated_lt: {
|
|
436
|
+
type: "number",
|
|
437
|
+
description: "Filter tasks updated before this timestamp"
|
|
438
|
+
},
|
|
439
|
+
custom_fields: {
|
|
440
|
+
type: "object",
|
|
441
|
+
description: "Filter tasks by custom field values"
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
required: []
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
name: "get_task",
|
|
449
|
+
description: "Get detailed information about a specific ClickUp task, including attachments. Supports direct name-based lookup for tasks.",
|
|
450
|
+
inputSchema: {
|
|
451
|
+
type: "object",
|
|
452
|
+
properties: {
|
|
453
|
+
taskId: {
|
|
454
|
+
type: "string",
|
|
455
|
+
description: "ID of the task to retrieve (optional if using taskName instead)"
|
|
456
|
+
},
|
|
457
|
+
taskName: {
|
|
458
|
+
type: "string",
|
|
459
|
+
description: "Name of the task to retrieve - will automatically find the task by name (optional if using taskId instead)"
|
|
460
|
+
},
|
|
461
|
+
listName: {
|
|
462
|
+
type: "string",
|
|
463
|
+
description: "Optional: Name of the list to narrow down task search"
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
required: []
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
name: "delete_task",
|
|
471
|
+
description: "Delete a task from your workspace. This action cannot be undone.",
|
|
472
|
+
inputSchema: {
|
|
473
|
+
type: "object",
|
|
474
|
+
properties: {
|
|
475
|
+
taskId: {
|
|
476
|
+
type: "string",
|
|
477
|
+
description: "ID of the task to delete"
|
|
478
|
+
}
|
|
479
|
+
},
|
|
319
480
|
required: ["taskId"]
|
|
320
481
|
}
|
|
321
482
|
}
|
|
@@ -329,43 +490,61 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
329
490
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
330
491
|
try {
|
|
331
492
|
switch (request.params.name) {
|
|
332
|
-
case "
|
|
333
|
-
const
|
|
334
|
-
const allLists = await clickup.getAllLists(config.teamId);
|
|
335
|
-
let output = "Available Spaces and Lists:\n\n";
|
|
336
|
-
for (const space of spaces) {
|
|
337
|
-
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`;
|
|
343
|
-
}
|
|
344
|
-
output += "\n";
|
|
345
|
-
}
|
|
346
|
-
// Add lists without spaces at the end
|
|
347
|
-
const listsWithoutSpace = allLists.filter(list => !list.space);
|
|
348
|
-
if (listsWithoutSpace.length > 0) {
|
|
349
|
-
output += "Lists without assigned spaces:\n";
|
|
350
|
-
for (const list of listsWithoutSpace) {
|
|
351
|
-
const { statuses } = await clickup.getTasks(list.id);
|
|
352
|
-
output += ` └─ List: ${list.name} (ID: ${list.id})\n`;
|
|
353
|
-
output += ` Available Statuses: ${statuses.join(', ')}\n`;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
493
|
+
case "get_workspace_hierarchy": {
|
|
494
|
+
const hierarchy = await clickup.getWorkspaceHierarchy();
|
|
356
495
|
return {
|
|
357
496
|
content: [{
|
|
358
497
|
type: "text",
|
|
359
|
-
text:
|
|
498
|
+
text: JSON.stringify({
|
|
499
|
+
workspace: {
|
|
500
|
+
id: hierarchy.root.id,
|
|
501
|
+
name: hierarchy.root.name,
|
|
502
|
+
spaces: hierarchy.root.children.map((space) => ({
|
|
503
|
+
id: space.id,
|
|
504
|
+
name: space.name,
|
|
505
|
+
lists: space.children
|
|
506
|
+
.filter((node) => node.type === 'list')
|
|
507
|
+
.map((list) => ({
|
|
508
|
+
id: list.id,
|
|
509
|
+
name: list.name,
|
|
510
|
+
path: `${space.name} > ${list.name}`
|
|
511
|
+
})),
|
|
512
|
+
folders: space.children
|
|
513
|
+
.filter((node) => node.type === 'folder')
|
|
514
|
+
.map((folder) => ({
|
|
515
|
+
id: folder.id,
|
|
516
|
+
name: folder.name,
|
|
517
|
+
path: `${space.name} > ${folder.name}`,
|
|
518
|
+
lists: folder.children.map((list) => ({
|
|
519
|
+
id: list.id,
|
|
520
|
+
name: list.name,
|
|
521
|
+
path: `${space.name} > ${folder.name} > ${list.name}`
|
|
522
|
+
}))
|
|
523
|
+
}))
|
|
524
|
+
}))
|
|
525
|
+
}
|
|
526
|
+
}, null, 2)
|
|
360
527
|
}]
|
|
361
528
|
};
|
|
362
529
|
}
|
|
363
530
|
case "create_task": {
|
|
364
531
|
const args = request.params.arguments;
|
|
365
|
-
if (!args.listId
|
|
366
|
-
throw new Error("listId
|
|
532
|
+
if (!args.listId && !args.listName) {
|
|
533
|
+
throw new Error("Either listId or listName is required");
|
|
534
|
+
}
|
|
535
|
+
if (!args.name) {
|
|
536
|
+
throw new Error("name is required");
|
|
537
|
+
}
|
|
538
|
+
let listId = args.listId;
|
|
539
|
+
if (!listId && args.listName) {
|
|
540
|
+
const hierarchy = await clickup.getWorkspaceHierarchy();
|
|
541
|
+
const result = clickup.findIDByNameInHierarchy(hierarchy, args.listName, 'list');
|
|
542
|
+
if (!result) {
|
|
543
|
+
throw new Error(`List with name "${args.listName}" not found`);
|
|
544
|
+
}
|
|
545
|
+
listId = result.id;
|
|
367
546
|
}
|
|
368
|
-
const { listId, ...taskData } = args;
|
|
547
|
+
const { listId: _, listName: __, ...taskData } = args;
|
|
369
548
|
const task = await clickup.createTask(listId, taskData);
|
|
370
549
|
return {
|
|
371
550
|
content: [{
|
|
@@ -374,6 +553,31 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
374
553
|
}]
|
|
375
554
|
};
|
|
376
555
|
}
|
|
556
|
+
case "create_bulk_tasks": {
|
|
557
|
+
const args = request.params.arguments;
|
|
558
|
+
if (!args.listId && !args.listName) {
|
|
559
|
+
throw new Error("Either listId or listName is required");
|
|
560
|
+
}
|
|
561
|
+
if (!args.tasks || args.tasks.length === 0) {
|
|
562
|
+
throw new Error("tasks array is required and must not be empty");
|
|
563
|
+
}
|
|
564
|
+
let listId = args.listId;
|
|
565
|
+
if (!listId && args.listName) {
|
|
566
|
+
const result = await clickup.findListIDByName(args.listName);
|
|
567
|
+
if (!result) {
|
|
568
|
+
throw new Error(`List with name "${args.listName}" not found`);
|
|
569
|
+
}
|
|
570
|
+
listId = result.id;
|
|
571
|
+
}
|
|
572
|
+
const { listId: _, listName: __, tasks } = args;
|
|
573
|
+
const createdTasks = await clickup.createBulkTasks(listId, { tasks });
|
|
574
|
+
return {
|
|
575
|
+
content: [{
|
|
576
|
+
type: "text",
|
|
577
|
+
text: `Created ${createdTasks.length} tasks`
|
|
578
|
+
}]
|
|
579
|
+
};
|
|
580
|
+
}
|
|
377
581
|
case "create_list": {
|
|
378
582
|
const args = request.params.arguments;
|
|
379
583
|
if (!args.name) {
|
|
@@ -381,11 +585,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
381
585
|
}
|
|
382
586
|
let spaceId = args.spaceId;
|
|
383
587
|
if (!spaceId && args.spaceName) {
|
|
384
|
-
const
|
|
385
|
-
if (!
|
|
588
|
+
const foundId = await clickup.findSpaceIDByName(args.spaceName);
|
|
589
|
+
if (!foundId) {
|
|
386
590
|
throw new Error(`Space with name "${args.spaceName}" not found`);
|
|
387
591
|
}
|
|
388
|
-
spaceId =
|
|
592
|
+
spaceId = foundId;
|
|
389
593
|
}
|
|
390
594
|
if (!spaceId) {
|
|
391
595
|
throw new Error("Either spaceId or spaceName must be provided");
|
|
@@ -406,11 +610,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
406
610
|
}
|
|
407
611
|
let spaceId = args.spaceId;
|
|
408
612
|
if (!spaceId && args.spaceName) {
|
|
409
|
-
const
|
|
410
|
-
if (!
|
|
613
|
+
const foundId = await clickup.findSpaceIDByName(args.spaceName);
|
|
614
|
+
if (!foundId) {
|
|
411
615
|
throw new Error(`Space with name "${args.spaceName}" not found`);
|
|
412
616
|
}
|
|
413
|
-
spaceId =
|
|
617
|
+
spaceId = foundId;
|
|
414
618
|
}
|
|
415
619
|
if (!spaceId) {
|
|
416
620
|
throw new Error("Either spaceId or spaceName must be provided");
|
|
@@ -431,25 +635,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
431
635
|
}
|
|
432
636
|
let folderId = args.folderId;
|
|
433
637
|
if (!folderId && args.folderName) {
|
|
434
|
-
|
|
435
|
-
if (!
|
|
436
|
-
const space = await clickup.findSpaceByName(config.teamId, args.spaceName);
|
|
437
|
-
if (!space) {
|
|
438
|
-
throw new Error(`Space with name "${args.spaceName}" not found`);
|
|
439
|
-
}
|
|
440
|
-
spaceId = space.id;
|
|
441
|
-
}
|
|
442
|
-
if (!spaceId) {
|
|
443
|
-
throw new Error("Either spaceId or spaceName must be provided when using folderName");
|
|
444
|
-
}
|
|
445
|
-
const folder = await clickup.findFolderByName(spaceId, args.folderName);
|
|
446
|
-
if (!folder) {
|
|
638
|
+
const result = await clickup.findFolderIDByName(args.folderName);
|
|
639
|
+
if (!result) {
|
|
447
640
|
throw new Error(`Folder with name "${args.folderName}" not found`);
|
|
448
641
|
}
|
|
449
|
-
folderId =
|
|
642
|
+
folderId = result.id;
|
|
450
643
|
}
|
|
451
644
|
if (!folderId) {
|
|
452
|
-
throw new Error("Either folderId or folderName
|
|
645
|
+
throw new Error("Either folderId or folderName must be provided");
|
|
453
646
|
}
|
|
454
647
|
const { folderId: _, folderName: __, spaceId: ___, spaceName: ____, ...listData } = args;
|
|
455
648
|
const list = await clickup.createListInFolder(folderId, listData);
|
|
@@ -462,36 +655,90 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
462
655
|
}
|
|
463
656
|
case "move_task": {
|
|
464
657
|
const args = request.params.arguments;
|
|
465
|
-
if (!args.taskId
|
|
466
|
-
throw new Error("taskId
|
|
658
|
+
if (!args.taskId && !args.taskName) {
|
|
659
|
+
throw new Error("Either taskId or taskName is required");
|
|
660
|
+
}
|
|
661
|
+
if (!args.listId && !args.listName) {
|
|
662
|
+
throw new Error("Either listId or listName is required");
|
|
663
|
+
}
|
|
664
|
+
let taskId = args.taskId;
|
|
665
|
+
if (!taskId && args.taskName) {
|
|
666
|
+
const result = await clickup.findTaskByName(args.taskName, undefined, args.sourceListName);
|
|
667
|
+
if (!result) {
|
|
668
|
+
throw new Error(`Task with name "${args.taskName}" not found${args.sourceListName ? ` in list "${args.sourceListName}"` : ''}`);
|
|
669
|
+
}
|
|
670
|
+
taskId = result.id;
|
|
671
|
+
}
|
|
672
|
+
let listId = args.listId;
|
|
673
|
+
if (!listId && args.listName) {
|
|
674
|
+
const result = await clickup.findListIDByName(args.listName);
|
|
675
|
+
if (!result) {
|
|
676
|
+
throw new Error(`List with name "${args.listName}" not found`);
|
|
677
|
+
}
|
|
678
|
+
listId = result.id;
|
|
467
679
|
}
|
|
468
|
-
|
|
680
|
+
// Get the original task details for the response message
|
|
681
|
+
const originalTask = await clickup.getTask(taskId);
|
|
682
|
+
const newTask = await clickup.moveTask(taskId, listId);
|
|
469
683
|
return {
|
|
470
684
|
content: [{
|
|
471
685
|
type: "text",
|
|
472
|
-
text: `Moved task ${
|
|
686
|
+
text: `Moved task "${originalTask.name}" from "${originalTask.list.name}" to "${newTask.list.name}"`
|
|
473
687
|
}]
|
|
474
688
|
};
|
|
475
689
|
}
|
|
476
690
|
case "duplicate_task": {
|
|
477
691
|
const args = request.params.arguments;
|
|
478
|
-
|
|
479
|
-
|
|
692
|
+
// Require either taskId or taskName
|
|
693
|
+
if (!args.taskId && !args.taskName) {
|
|
694
|
+
throw new Error("Either taskId or taskName is required");
|
|
695
|
+
}
|
|
696
|
+
// Require either listId or listName
|
|
697
|
+
if (!args.listId && !args.listName) {
|
|
698
|
+
throw new Error("Either listId or listName is required");
|
|
699
|
+
}
|
|
700
|
+
// Get taskId from taskName if needed
|
|
701
|
+
let taskId = args.taskId;
|
|
702
|
+
if (!taskId && args.taskName) {
|
|
703
|
+
const result = await clickup.findTaskByName(args.taskName, undefined, args.sourceListName);
|
|
704
|
+
if (!result) {
|
|
705
|
+
throw new Error(`Task with name "${args.taskName}" not found${args.sourceListName ? ` in list "${args.sourceListName}"` : ''}`);
|
|
706
|
+
}
|
|
707
|
+
taskId = result.id;
|
|
708
|
+
}
|
|
709
|
+
// Get listId from listName if needed
|
|
710
|
+
let listId = args.listId;
|
|
711
|
+
if (!listId && args.listName) {
|
|
712
|
+
const result = await clickup.findListIDByName(args.listName);
|
|
713
|
+
if (!result) {
|
|
714
|
+
throw new Error(`List with name "${args.listName}" not found`);
|
|
715
|
+
}
|
|
716
|
+
listId = result.id;
|
|
480
717
|
}
|
|
481
|
-
|
|
718
|
+
// Get the original task details for the response message
|
|
719
|
+
const originalTask = await clickup.getTask(taskId);
|
|
720
|
+
const newTask = await clickup.duplicateTask(taskId, listId);
|
|
482
721
|
return {
|
|
483
722
|
content: [{
|
|
484
723
|
type: "text",
|
|
485
|
-
text: `Duplicated task ${
|
|
724
|
+
text: `Duplicated task "${originalTask.name}" from "${originalTask.list.name}" to "${newTask.list.name}"`
|
|
486
725
|
}]
|
|
487
726
|
};
|
|
488
727
|
}
|
|
489
728
|
case "update_task": {
|
|
490
729
|
const args = request.params.arguments;
|
|
491
|
-
if (!args.taskId) {
|
|
492
|
-
throw new Error("taskId is required");
|
|
730
|
+
if (!args.taskId && !args.taskName) {
|
|
731
|
+
throw new Error("Either taskId or taskName is required");
|
|
493
732
|
}
|
|
494
|
-
|
|
733
|
+
let taskId = args.taskId;
|
|
734
|
+
if (!taskId && args.taskName) {
|
|
735
|
+
const result = await clickup.findTaskByName(args.taskName, undefined, args.listName);
|
|
736
|
+
if (!result) {
|
|
737
|
+
throw new Error(`Task with name "${args.taskName}" not found${args.listName ? ` in list "${args.listName}"` : ''}`);
|
|
738
|
+
}
|
|
739
|
+
taskId = result.id;
|
|
740
|
+
}
|
|
741
|
+
const { taskId: _, taskName: __, listName: ___, ...updateData } = args;
|
|
495
742
|
const task = await clickup.updateTask(taskId, updateData);
|
|
496
743
|
return {
|
|
497
744
|
content: [{
|
|
@@ -500,13 +747,83 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
500
747
|
}]
|
|
501
748
|
};
|
|
502
749
|
}
|
|
750
|
+
case "get_tasks": {
|
|
751
|
+
const args = request.params.arguments;
|
|
752
|
+
if (!args.listId && !args.listName) {
|
|
753
|
+
throw new Error("Either listId or listName is required");
|
|
754
|
+
}
|
|
755
|
+
let listId = args.listId;
|
|
756
|
+
if (!listId && args.listName) {
|
|
757
|
+
const result = await clickup.findListIDByName(args.listName);
|
|
758
|
+
if (!result) {
|
|
759
|
+
throw new Error(`List with name "${args.listName}" not found`);
|
|
760
|
+
}
|
|
761
|
+
listId = result.id;
|
|
762
|
+
}
|
|
763
|
+
// Remove listId and listName from filters
|
|
764
|
+
const { listId: _, listName: __, ...filters } = args;
|
|
765
|
+
const { tasks, statuses } = await clickup.getTasks(listId, filters);
|
|
766
|
+
return {
|
|
767
|
+
content: [{
|
|
768
|
+
type: "text",
|
|
769
|
+
text: JSON.stringify({ tasks, available_statuses: statuses }, null, 2)
|
|
770
|
+
}]
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
case "get_task": {
|
|
774
|
+
const args = request.params.arguments;
|
|
775
|
+
if (!args.taskId && !args.taskName) {
|
|
776
|
+
throw new Error("Either taskId or taskName is required");
|
|
777
|
+
}
|
|
778
|
+
let taskId = args.taskId;
|
|
779
|
+
if (!taskId && args.taskName) {
|
|
780
|
+
const result = await clickup.findTaskByName(args.taskName, undefined, args.listName);
|
|
781
|
+
if (!result) {
|
|
782
|
+
throw new Error(`Task with name "${args.taskName}" not found${args.listName ? ` in list "${args.listName}"` : ''}`);
|
|
783
|
+
}
|
|
784
|
+
taskId = result.id;
|
|
785
|
+
}
|
|
786
|
+
const task = await clickup.getTask(taskId);
|
|
787
|
+
return {
|
|
788
|
+
content: [{
|
|
789
|
+
type: "text",
|
|
790
|
+
text: JSON.stringify(task, null, 2)
|
|
791
|
+
}]
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
case "delete_task": {
|
|
795
|
+
const args = request.params.arguments;
|
|
796
|
+
if (!args.taskId && !args.taskName) {
|
|
797
|
+
throw new Error("Either taskId or taskName is required");
|
|
798
|
+
}
|
|
799
|
+
let taskId = args.taskId;
|
|
800
|
+
if (!taskId && args.taskName) {
|
|
801
|
+
const result = await clickup.findTaskByName(args.taskName, undefined, args.listName);
|
|
802
|
+
if (!result) {
|
|
803
|
+
throw new Error(`Task with name "${args.taskName}" not found${args.listName ? ` in list "${args.listName}"` : ''}`);
|
|
804
|
+
}
|
|
805
|
+
taskId = result.id;
|
|
806
|
+
}
|
|
807
|
+
await clickup.deleteTask(taskId);
|
|
808
|
+
return {
|
|
809
|
+
content: [{
|
|
810
|
+
type: "text",
|
|
811
|
+
text: `Successfully deleted task ${args.taskName || taskId}`
|
|
812
|
+
}]
|
|
813
|
+
};
|
|
814
|
+
}
|
|
503
815
|
default:
|
|
504
|
-
throw new Error(
|
|
816
|
+
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
505
817
|
}
|
|
506
818
|
}
|
|
507
819
|
catch (error) {
|
|
508
|
-
console.error('Error
|
|
509
|
-
|
|
820
|
+
console.error('Error in tool call:', error);
|
|
821
|
+
return {
|
|
822
|
+
content: [{
|
|
823
|
+
type: "text",
|
|
824
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
825
|
+
}]
|
|
826
|
+
};
|
|
510
827
|
}
|
|
511
828
|
});
|
|
512
829
|
/**
|
|
@@ -535,7 +852,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
535
852
|
try {
|
|
536
853
|
switch (request.params.name) {
|
|
537
854
|
case "summarize_tasks": {
|
|
538
|
-
const spaces = await clickup.getSpaces(config.
|
|
855
|
+
const spaces = await clickup.getSpaces(config.clickupTeamId);
|
|
539
856
|
const tasks = [];
|
|
540
857
|
// Gather all tasks
|
|
541
858
|
for (const space of spaces) {
|
|
@@ -576,7 +893,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
576
893
|
};
|
|
577
894
|
}
|
|
578
895
|
case "analyze_priorities": {
|
|
579
|
-
const spaces = await clickup.getSpaces(config.
|
|
896
|
+
const spaces = await clickup.getSpaces(config.clickupTeamId);
|
|
580
897
|
const tasks = [];
|
|
581
898
|
for (const space of spaces) {
|
|
582
899
|
const lists = await clickup.getLists(space.id);
|