@taazkareem/clickup-mcp-server 0.1.7 → 0.2.1
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 +289 -81
- package/build/config.js +2 -5
- package/build/index.js +692 -121
- package/build/services/clickup.js +675 -25
- 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.teamId);
|
|
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,33 +75,270 @@ 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."
|
|
91
|
+
},
|
|
92
|
+
dueDate: {
|
|
93
|
+
type: "string",
|
|
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
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
required: ["listId", "tasks"]
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: "create_list",
|
|
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).",
|
|
162
|
+
inputSchema: {
|
|
163
|
+
type: "object",
|
|
164
|
+
properties: {
|
|
165
|
+
spaceId: {
|
|
166
|
+
type: "string",
|
|
167
|
+
description: "ID of the space to create the list in (optional if using spaceName instead)"
|
|
168
|
+
},
|
|
169
|
+
spaceName: {
|
|
170
|
+
type: "string",
|
|
171
|
+
description: "Name of the space to create the list in - will automatically find the space by name (optional if using spaceId instead)"
|
|
172
|
+
},
|
|
173
|
+
name: {
|
|
174
|
+
type: "string",
|
|
175
|
+
description: "Name of the list"
|
|
176
|
+
},
|
|
177
|
+
content: {
|
|
178
|
+
type: "string",
|
|
179
|
+
description: "Description or content of the list"
|
|
137
180
|
},
|
|
138
181
|
dueDate: {
|
|
139
182
|
type: "string",
|
|
140
|
-
description: "Due date
|
|
183
|
+
description: "Due date for the list (ISO string)"
|
|
184
|
+
},
|
|
185
|
+
priority: {
|
|
186
|
+
type: "number",
|
|
187
|
+
description: "Priority of the list (1-4)"
|
|
188
|
+
},
|
|
189
|
+
assignee: {
|
|
190
|
+
type: "number",
|
|
191
|
+
description: "User ID to assign the list to"
|
|
192
|
+
},
|
|
193
|
+
status: {
|
|
194
|
+
type: "string",
|
|
195
|
+
description: "Status of the list"
|
|
141
196
|
}
|
|
142
197
|
},
|
|
143
|
-
required: ["
|
|
198
|
+
required: ["name"]
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: "create_folder",
|
|
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).",
|
|
204
|
+
inputSchema: {
|
|
205
|
+
type: "object",
|
|
206
|
+
properties: {
|
|
207
|
+
spaceId: {
|
|
208
|
+
type: "string",
|
|
209
|
+
description: "ID of the space to create the folder in (optional if using spaceName instead)"
|
|
210
|
+
},
|
|
211
|
+
spaceName: {
|
|
212
|
+
type: "string",
|
|
213
|
+
description: "Name of the space to create the folder in - will automatically find the space by name (optional if using spaceId instead)"
|
|
214
|
+
},
|
|
215
|
+
name: {
|
|
216
|
+
type: "string",
|
|
217
|
+
description: "Name of the folder"
|
|
218
|
+
},
|
|
219
|
+
override_statuses: {
|
|
220
|
+
type: "boolean",
|
|
221
|
+
description: "Whether to override space statuses"
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
required: ["name"]
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
name: "create_list_in_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.",
|
|
230
|
+
inputSchema: {
|
|
231
|
+
type: "object",
|
|
232
|
+
properties: {
|
|
233
|
+
folderId: {
|
|
234
|
+
type: "string",
|
|
235
|
+
description: "ID of the folder to create the list in (optional if using folderName instead)"
|
|
236
|
+
},
|
|
237
|
+
folderName: {
|
|
238
|
+
type: "string",
|
|
239
|
+
description: "Name of the folder to create the list in - will automatically find the folder by name (optional if using folderId instead)"
|
|
240
|
+
},
|
|
241
|
+
spaceId: {
|
|
242
|
+
type: "string",
|
|
243
|
+
description: "ID of the space containing the folder (optional if using spaceName instead)"
|
|
244
|
+
},
|
|
245
|
+
spaceName: {
|
|
246
|
+
type: "string",
|
|
247
|
+
description: "Name of the space containing the folder - will automatically find the space by name (optional if using spaceId instead)"
|
|
248
|
+
},
|
|
249
|
+
name: {
|
|
250
|
+
type: "string",
|
|
251
|
+
description: "Name of the list"
|
|
252
|
+
},
|
|
253
|
+
content: {
|
|
254
|
+
type: "string",
|
|
255
|
+
description: "Description or content of the list"
|
|
256
|
+
},
|
|
257
|
+
status: {
|
|
258
|
+
type: "string",
|
|
259
|
+
description: "Status of the list"
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
required: ["name"]
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "move_task",
|
|
267
|
+
description: "Move a task to a different list. Supports direct name-based lookup for lists and tasks - no need to know IDs.",
|
|
268
|
+
inputSchema: {
|
|
269
|
+
type: "object",
|
|
270
|
+
properties: {
|
|
271
|
+
taskId: {
|
|
272
|
+
type: "string",
|
|
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"
|
|
282
|
+
},
|
|
283
|
+
listId: {
|
|
284
|
+
type: "string",
|
|
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)"
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
required: []
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
name: "duplicate_task",
|
|
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.",
|
|
298
|
+
inputSchema: {
|
|
299
|
+
type: "object",
|
|
300
|
+
properties: {
|
|
301
|
+
taskId: {
|
|
302
|
+
type: "string",
|
|
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"
|
|
312
|
+
},
|
|
313
|
+
listId: {
|
|
314
|
+
type: "string",
|
|
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)"
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
required: []
|
|
144
323
|
}
|
|
145
324
|
},
|
|
146
325
|
{
|
|
147
326
|
name: "update_task",
|
|
148
|
-
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.",
|
|
149
328
|
inputSchema: {
|
|
150
329
|
type: "object",
|
|
151
330
|
properties: {
|
|
152
331
|
taskId: {
|
|
153
332
|
type: "string",
|
|
154
|
-
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"
|
|
155
342
|
},
|
|
156
343
|
name: {
|
|
157
344
|
type: "string",
|
|
@@ -174,6 +361,122 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
174
361
|
description: "New due date of the task (ISO string)"
|
|
175
362
|
}
|
|
176
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
|
+
},
|
|
177
480
|
required: ["taskId"]
|
|
178
481
|
}
|
|
179
482
|
}
|
|
@@ -187,43 +490,61 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
187
490
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
188
491
|
try {
|
|
189
492
|
switch (request.params.name) {
|
|
190
|
-
case "
|
|
191
|
-
const
|
|
192
|
-
const allLists = await clickup.getAllLists(config.teamId);
|
|
193
|
-
let output = "Available Spaces and Lists:\n\n";
|
|
194
|
-
for (const space of spaces) {
|
|
195
|
-
output += `Space: ${space.name} (ID: ${space.id})\n`;
|
|
196
|
-
const spaceLists = allLists.filter(list => list.space.id === space.id);
|
|
197
|
-
for (const list of spaceLists) {
|
|
198
|
-
const { statuses } = await clickup.getTasks(list.id);
|
|
199
|
-
output += ` └─ List: ${list.name} (ID: ${list.id})\n`;
|
|
200
|
-
output += ` Available Statuses: ${statuses.join(', ')}\n`;
|
|
201
|
-
}
|
|
202
|
-
output += "\n";
|
|
203
|
-
}
|
|
204
|
-
// Add lists without spaces at the end
|
|
205
|
-
const listsWithoutSpace = allLists.filter(list => !list.space);
|
|
206
|
-
if (listsWithoutSpace.length > 0) {
|
|
207
|
-
output += "Lists without assigned spaces:\n";
|
|
208
|
-
for (const list of listsWithoutSpace) {
|
|
209
|
-
const { statuses } = await clickup.getTasks(list.id);
|
|
210
|
-
output += ` └─ List: ${list.name} (ID: ${list.id})\n`;
|
|
211
|
-
output += ` Available Statuses: ${statuses.join(', ')}\n`;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
493
|
+
case "get_workspace_hierarchy": {
|
|
494
|
+
const hierarchy = await clickup.getWorkspaceHierarchy();
|
|
214
495
|
return {
|
|
215
496
|
content: [{
|
|
216
497
|
type: "text",
|
|
217
|
-
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)
|
|
218
527
|
}]
|
|
219
528
|
};
|
|
220
529
|
}
|
|
221
530
|
case "create_task": {
|
|
222
531
|
const args = request.params.arguments;
|
|
223
|
-
if (!args.listId
|
|
224
|
-
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;
|
|
225
546
|
}
|
|
226
|
-
const { listId, ...taskData } = args;
|
|
547
|
+
const { listId: _, listName: __, ...taskData } = args;
|
|
227
548
|
const task = await clickup.createTask(listId, taskData);
|
|
228
549
|
return {
|
|
229
550
|
content: [{
|
|
@@ -232,12 +553,192 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
232
553
|
}]
|
|
233
554
|
};
|
|
234
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
|
+
}
|
|
581
|
+
case "create_list": {
|
|
582
|
+
const args = request.params.arguments;
|
|
583
|
+
if (!args.name) {
|
|
584
|
+
throw new Error("name is required");
|
|
585
|
+
}
|
|
586
|
+
let spaceId = args.spaceId;
|
|
587
|
+
if (!spaceId && args.spaceName) {
|
|
588
|
+
const foundId = await clickup.findSpaceIDByName(args.spaceName);
|
|
589
|
+
if (!foundId) {
|
|
590
|
+
throw new Error(`Space with name "${args.spaceName}" not found`);
|
|
591
|
+
}
|
|
592
|
+
spaceId = foundId;
|
|
593
|
+
}
|
|
594
|
+
if (!spaceId) {
|
|
595
|
+
throw new Error("Either spaceId or spaceName must be provided");
|
|
596
|
+
}
|
|
597
|
+
const { spaceId: _, spaceName: __, ...listData } = args;
|
|
598
|
+
const list = await clickup.createList(spaceId, listData);
|
|
599
|
+
return {
|
|
600
|
+
content: [{
|
|
601
|
+
type: "text",
|
|
602
|
+
text: `Created list ${list.id}: ${list.name}`
|
|
603
|
+
}]
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
case "create_folder": {
|
|
607
|
+
const args = request.params.arguments;
|
|
608
|
+
if (!args.name) {
|
|
609
|
+
throw new Error("name is required");
|
|
610
|
+
}
|
|
611
|
+
let spaceId = args.spaceId;
|
|
612
|
+
if (!spaceId && args.spaceName) {
|
|
613
|
+
const foundId = await clickup.findSpaceIDByName(args.spaceName);
|
|
614
|
+
if (!foundId) {
|
|
615
|
+
throw new Error(`Space with name "${args.spaceName}" not found`);
|
|
616
|
+
}
|
|
617
|
+
spaceId = foundId;
|
|
618
|
+
}
|
|
619
|
+
if (!spaceId) {
|
|
620
|
+
throw new Error("Either spaceId or spaceName must be provided");
|
|
621
|
+
}
|
|
622
|
+
const { spaceId: _, spaceName: __, ...folderData } = args;
|
|
623
|
+
const folder = await clickup.createFolder(spaceId, folderData);
|
|
624
|
+
return {
|
|
625
|
+
content: [{
|
|
626
|
+
type: "text",
|
|
627
|
+
text: `Created folder ${folder.id}: ${folder.name}`
|
|
628
|
+
}]
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
case "create_list_in_folder": {
|
|
632
|
+
const args = request.params.arguments;
|
|
633
|
+
if (!args.name) {
|
|
634
|
+
throw new Error("name is required");
|
|
635
|
+
}
|
|
636
|
+
let folderId = args.folderId;
|
|
637
|
+
if (!folderId && args.folderName) {
|
|
638
|
+
const result = await clickup.findFolderIDByName(args.folderName);
|
|
639
|
+
if (!result) {
|
|
640
|
+
throw new Error(`Folder with name "${args.folderName}" not found`);
|
|
641
|
+
}
|
|
642
|
+
folderId = result.id;
|
|
643
|
+
}
|
|
644
|
+
if (!folderId) {
|
|
645
|
+
throw new Error("Either folderId or folderName must be provided");
|
|
646
|
+
}
|
|
647
|
+
const { folderId: _, folderName: __, spaceId: ___, spaceName: ____, ...listData } = args;
|
|
648
|
+
const list = await clickup.createListInFolder(folderId, listData);
|
|
649
|
+
return {
|
|
650
|
+
content: [{
|
|
651
|
+
type: "text",
|
|
652
|
+
text: `Created list ${list.id}: ${list.name} in folder`
|
|
653
|
+
}]
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
case "move_task": {
|
|
657
|
+
const args = request.params.arguments;
|
|
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;
|
|
679
|
+
}
|
|
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);
|
|
683
|
+
return {
|
|
684
|
+
content: [{
|
|
685
|
+
type: "text",
|
|
686
|
+
text: `Moved task "${originalTask.name}" from "${originalTask.list.name}" to "${newTask.list.name}"`
|
|
687
|
+
}]
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
case "duplicate_task": {
|
|
691
|
+
const args = request.params.arguments;
|
|
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;
|
|
717
|
+
}
|
|
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);
|
|
721
|
+
return {
|
|
722
|
+
content: [{
|
|
723
|
+
type: "text",
|
|
724
|
+
text: `Duplicated task "${originalTask.name}" from "${originalTask.list.name}" to "${newTask.list.name}"`
|
|
725
|
+
}]
|
|
726
|
+
};
|
|
727
|
+
}
|
|
235
728
|
case "update_task": {
|
|
236
729
|
const args = request.params.arguments;
|
|
237
|
-
if (!args.taskId) {
|
|
238
|
-
throw new Error("taskId is required");
|
|
730
|
+
if (!args.taskId && !args.taskName) {
|
|
731
|
+
throw new Error("Either taskId or taskName is required");
|
|
732
|
+
}
|
|
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;
|
|
239
740
|
}
|
|
240
|
-
const { taskId, ...updateData } = args;
|
|
741
|
+
const { taskId: _, taskName: __, listName: ___, ...updateData } = args;
|
|
241
742
|
const task = await clickup.updateTask(taskId, updateData);
|
|
242
743
|
return {
|
|
243
744
|
content: [{
|
|
@@ -246,13 +747,83 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
246
747
|
}]
|
|
247
748
|
};
|
|
248
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
|
+
}
|
|
249
815
|
default:
|
|
250
|
-
throw new Error(
|
|
816
|
+
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
251
817
|
}
|
|
252
818
|
}
|
|
253
819
|
catch (error) {
|
|
254
|
-
console.error('Error
|
|
255
|
-
|
|
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
|
+
};
|
|
256
827
|
}
|
|
257
828
|
});
|
|
258
829
|
/**
|