@taazkareem/clickup-mcp-server 0.4.49 → 0.4.52
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 +8 -2
- package/README.md +71 -211
- package/build/config.js +4 -16
- package/build/index.js +566 -310
- package/build/services/clickup.js +637 -80
- package/package.json +10 -10
- package/smithery.yaml +23 -0
- package/build/handlers/prompts.js +0 -88
- package/build/handlers/tools.js +0 -38
- package/build/utils/resolvers.js +0 -48
package/build/index.js
CHANGED
|
@@ -1,136 +1,69 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
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
|
+
* allowing AI assistants to manage tasks, lists, and folders in ClickUp workspaces.
|
|
7
|
+
*
|
|
8
|
+
* Key capabilities include:
|
|
9
|
+
*
|
|
10
|
+
* Task Management:
|
|
11
|
+
* - Create, update, move and duplicate tasks with rich description support
|
|
12
|
+
* - Find tasks by name with smart disambiguation
|
|
13
|
+
* - Bulk task creation for efficient workflow setup
|
|
14
|
+
* - Comprehensive filtering and sorting options
|
|
15
|
+
*
|
|
16
|
+
* Workspace Organization:
|
|
17
|
+
* - Navigate and discover workspace structure with hierarchical views
|
|
18
|
+
* - Create and manage lists and folders with proper nesting
|
|
19
|
+
* - Smart name-based lookups that eliminate the need for IDs
|
|
20
|
+
* - Support for priorities, statuses, and due dates
|
|
21
|
+
*
|
|
22
|
+
* AI-Enhanced Capabilities:
|
|
23
|
+
* - Task summarization and status grouping for project overviews
|
|
24
|
+
* - Priority analysis and optimization for workload balancing
|
|
25
|
+
* - Detailed task description generation with structured content
|
|
26
|
+
* - Task relationship identification for dependency management
|
|
27
|
+
*
|
|
28
|
+
* Technical Features:
|
|
29
|
+
* - Full markdown support for rich text content
|
|
30
|
+
* - Secure credential handling through configuration
|
|
31
|
+
* - Comprehensive error reporting and validation
|
|
32
|
+
* - Name-based entity resolution with fuzzy matching
|
|
33
|
+
*
|
|
34
|
+
* This implementation follows the Model Context Protocol specification and
|
|
35
|
+
* is designed to be used with AI assistants that support MCP.
|
|
36
|
+
*/
|
|
2
37
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
38
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import { CallToolRequestSchema,
|
|
39
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
40
|
import { ClickUpService } from "./services/clickup.js";
|
|
6
41
|
import config from "./config.js";
|
|
7
|
-
import { handleWorkspaceHierarchy, handleCreateTask } from "./handlers/tools.js";
|
|
8
|
-
import { handleSummarizeTasks, handleAnalyzeTaskPriorities, handleGenerateDescription } from "./handlers/prompts.js";
|
|
9
|
-
import { getAllTasks } from "./utils/resolvers.js";
|
|
10
|
-
console.log('Server starting up...');
|
|
11
|
-
console.log('Config loaded:', {
|
|
12
|
-
clickupApiKey: config.clickupApiKey ? '***' : 'missing',
|
|
13
|
-
teamId: config.teamId || 'missing'
|
|
14
|
-
});
|
|
15
|
-
// Create and configure the server
|
|
16
|
-
const server = new Server({
|
|
17
|
-
name: "clickup",
|
|
18
|
-
version: "0.1.0",
|
|
19
|
-
description: "ClickUp MCP Server"
|
|
20
|
-
}, {
|
|
21
|
-
capabilities: {
|
|
22
|
-
resources: {
|
|
23
|
-
canList: true,
|
|
24
|
-
canRead: true,
|
|
25
|
-
schemes: ["clickup"]
|
|
26
|
-
},
|
|
27
|
-
tools: {
|
|
28
|
-
canExecute: true,
|
|
29
|
-
tools: {
|
|
30
|
-
workspace_hierarchy: {
|
|
31
|
-
description: "List complete hierarchy of the ClickUp workspace"
|
|
32
|
-
},
|
|
33
|
-
create_task: {
|
|
34
|
-
description: "Create a new task in ClickUp"
|
|
35
|
-
},
|
|
36
|
-
create_bulk_tasks: {
|
|
37
|
-
description: "Create multiple tasks in a ClickUp list"
|
|
38
|
-
},
|
|
39
|
-
create_list: {
|
|
40
|
-
description: "Create a new list in a ClickUp space"
|
|
41
|
-
},
|
|
42
|
-
create_folder: {
|
|
43
|
-
description: "Create a new folder in a ClickUp space"
|
|
44
|
-
},
|
|
45
|
-
create_list_in_folder: {
|
|
46
|
-
description: "Create a new list in a ClickUp folder"
|
|
47
|
-
},
|
|
48
|
-
move_task: {
|
|
49
|
-
description: "Move a task to a different list"
|
|
50
|
-
},
|
|
51
|
-
duplicate_task: {
|
|
52
|
-
description: "Duplicate a task to a list"
|
|
53
|
-
},
|
|
54
|
-
update_task: {
|
|
55
|
-
description: "Update an existing task in ClickUp"
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
prompts: {
|
|
60
|
-
canExecute: true,
|
|
61
|
-
prompts: {
|
|
62
|
-
summarize_tasks: {
|
|
63
|
-
description: "Summarize all ClickUp tasks"
|
|
64
|
-
},
|
|
65
|
-
analyze_task_priorities: {
|
|
66
|
-
description: "Analyze task priorities"
|
|
67
|
-
},
|
|
68
|
-
generate_description: {
|
|
69
|
-
description: "Generate a detailed task description"
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
42
|
// Initialize ClickUp service
|
|
76
|
-
|
|
77
|
-
const clickup = ClickUpService.initialize(config.clickupApiKey);
|
|
78
|
-
console.log('Creating MCP server...');
|
|
79
|
-
console.log('MCP server created');
|
|
43
|
+
const clickup = ClickUpService.initialize(config.clickupApiKey, config.clickupTeamId);
|
|
80
44
|
/**
|
|
81
|
-
*
|
|
45
|
+
* Create an MCP server with capabilities for tools and prompts.
|
|
46
|
+
* Resources have been removed as they are being replaced with direct tool calls.
|
|
82
47
|
*/
|
|
83
|
-
server
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
name: task.name,
|
|
92
|
-
description: task.description || `Task in ${task.list.name} (${task.space.name})`,
|
|
93
|
-
tags: []
|
|
94
|
-
}))
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
catch (error) {
|
|
98
|
-
console.error('Error in ListResources:', error);
|
|
99
|
-
throw error;
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
/**
|
|
103
|
-
* Handler for reading the contents of a specific ClickUp task.
|
|
104
|
-
*/
|
|
105
|
-
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
106
|
-
try {
|
|
107
|
-
const url = new URL(request.params.uri);
|
|
108
|
-
const taskId = url.pathname.replace(/^\/task\//, '');
|
|
109
|
-
const task = await clickup.getTask(taskId);
|
|
110
|
-
return {
|
|
111
|
-
contents: [{
|
|
112
|
-
uri: request.params.uri,
|
|
113
|
-
mimeType: "application/json",
|
|
114
|
-
text: JSON.stringify(task, null, 2),
|
|
115
|
-
tags: []
|
|
116
|
-
}]
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
catch (error) {
|
|
120
|
-
console.error('Error reading resource:', error);
|
|
121
|
-
throw error;
|
|
122
|
-
}
|
|
48
|
+
const server = new Server({
|
|
49
|
+
name: "clickup-mcp-server",
|
|
50
|
+
version: "0.4.50",
|
|
51
|
+
}, {
|
|
52
|
+
capabilities: {
|
|
53
|
+
tools: {},
|
|
54
|
+
prompts: {},
|
|
55
|
+
},
|
|
123
56
|
});
|
|
124
57
|
/**
|
|
125
|
-
* Handler
|
|
58
|
+
* Handler that lists available tools.
|
|
59
|
+
* Exposes tools for listing spaces, creating tasks, and updating tasks.
|
|
126
60
|
*/
|
|
127
61
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
128
|
-
console.log('Handling ListTools request');
|
|
129
62
|
return {
|
|
130
63
|
tools: [
|
|
131
64
|
{
|
|
132
|
-
name: "
|
|
133
|
-
description: "
|
|
65
|
+
name: "get_workspace_hierarchy",
|
|
66
|
+
description: "Retrieve the complete ClickUp workspace hierarchy, including all spaces, folders, and lists with their IDs, names, and hierarchical paths. Call this tool only when you need to discover the workspace structure and don't already have this information from recent context. Avoid using for repeated lookups of the same information.",
|
|
134
67
|
inputSchema: {
|
|
135
68
|
type: "object",
|
|
136
69
|
properties: {},
|
|
@@ -139,37 +72,41 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
139
72
|
},
|
|
140
73
|
{
|
|
141
74
|
name: "create_task",
|
|
142
|
-
description: "Create a
|
|
75
|
+
description: "Create a single task in a ClickUp list. Use this tool for individual task creation only. For multiple tasks, use create_bulk_tasks instead. The tool finds lists by name (case-insensitive) so explicit list IDs aren't required. When creating a task, you must provide either a listId or listName.",
|
|
143
76
|
inputSchema: {
|
|
144
77
|
type: "object",
|
|
145
78
|
properties: {
|
|
146
79
|
listId: {
|
|
147
80
|
type: "string",
|
|
148
|
-
description: "ID of the list to create the task in (optional if listName
|
|
81
|
+
description: "ID of the list to create the task in (optional if using listName instead)"
|
|
149
82
|
},
|
|
150
83
|
listName: {
|
|
151
84
|
type: "string",
|
|
152
|
-
description: "Name of the list to create the task in (optional if listId
|
|
85
|
+
description: "Name of the list to create the task in - will automatically find the list by name (optional if using listId instead)"
|
|
153
86
|
},
|
|
154
87
|
name: {
|
|
155
88
|
type: "string",
|
|
156
|
-
description: "Name of the task"
|
|
89
|
+
description: "Name of the task. Put a relevant emoji followed by a blank space before the name."
|
|
157
90
|
},
|
|
158
91
|
description: {
|
|
159
92
|
type: "string",
|
|
160
|
-
description: "
|
|
93
|
+
description: "Plain text description for the task"
|
|
94
|
+
},
|
|
95
|
+
markdown_description: {
|
|
96
|
+
type: "string",
|
|
97
|
+
description: "Markdown formatted description for the task. If provided, this takes precedence over description"
|
|
161
98
|
},
|
|
162
99
|
status: {
|
|
163
100
|
type: "string",
|
|
164
|
-
description: "
|
|
101
|
+
description: "OPTIONAL: Override the default ClickUp status. In most cases, you should omit this to use ClickUp defaults"
|
|
165
102
|
},
|
|
166
103
|
priority: {
|
|
167
104
|
type: "number",
|
|
168
|
-
description: "Priority of the task (1-4)"
|
|
105
|
+
description: "Priority of the task (1-4), where 1 is urgent/highest priority and 4 is lowest priority. Only set this when the user explicitly requests a priority level."
|
|
169
106
|
},
|
|
170
107
|
dueDate: {
|
|
171
108
|
type: "string",
|
|
172
|
-
description: "Due date of the task (
|
|
109
|
+
description: "Due date of the task (Unix timestamp in milliseconds). Convert dates to this format before submitting."
|
|
173
110
|
}
|
|
174
111
|
},
|
|
175
112
|
required: ["name"]
|
|
@@ -177,43 +114,47 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
177
114
|
},
|
|
178
115
|
{
|
|
179
116
|
name: "create_bulk_tasks",
|
|
180
|
-
description: "Create multiple tasks in a ClickUp list",
|
|
117
|
+
description: "Create multiple tasks in a ClickUp list simultaneously. Use this tool when you need to add several related tasks in one operation. The tool finds lists by name (case-insensitive), so explicit list IDs aren't required. More efficient than creating tasks one by one for batch operations.",
|
|
181
118
|
inputSchema: {
|
|
182
119
|
type: "object",
|
|
183
120
|
properties: {
|
|
184
121
|
listId: {
|
|
185
122
|
type: "string",
|
|
186
|
-
description: "ID of the list to create the tasks in (optional if listName
|
|
123
|
+
description: "ID of the list to create the tasks in (optional if using listName instead)"
|
|
187
124
|
},
|
|
188
125
|
listName: {
|
|
189
126
|
type: "string",
|
|
190
|
-
description: "Name of the list to create the tasks in (optional if listId
|
|
127
|
+
description: "Name of the list to create the tasks in - will automatically find the list by name (optional if using listId instead)"
|
|
191
128
|
},
|
|
192
129
|
tasks: {
|
|
193
130
|
type: "array",
|
|
194
|
-
description: "Array of tasks to create",
|
|
131
|
+
description: "Array of tasks to create (at least one task required)",
|
|
195
132
|
items: {
|
|
196
133
|
type: "object",
|
|
197
134
|
properties: {
|
|
198
135
|
name: {
|
|
199
136
|
type: "string",
|
|
200
|
-
description: "Name of the task"
|
|
137
|
+
description: "Name of the task. Consider adding a relevant emoji before the name."
|
|
201
138
|
},
|
|
202
139
|
description: {
|
|
203
140
|
type: "string",
|
|
204
|
-
description: "
|
|
141
|
+
description: "Plain text description for the task"
|
|
142
|
+
},
|
|
143
|
+
markdown_description: {
|
|
144
|
+
type: "string",
|
|
145
|
+
description: "Markdown formatted description for the task. If provided, this takes precedence over description"
|
|
205
146
|
},
|
|
206
147
|
status: {
|
|
207
148
|
type: "string",
|
|
208
|
-
description: "
|
|
149
|
+
description: "OPTIONAL: Override the default ClickUp status. In most cases, you should omit this to use ClickUp defaults"
|
|
209
150
|
},
|
|
210
151
|
priority: {
|
|
211
152
|
type: "number",
|
|
212
|
-
description: "Priority level (1-4)"
|
|
153
|
+
description: "Priority level (1-4), where 1 is urgent/highest priority and 4 is lowest priority. Only set when explicitly requested."
|
|
213
154
|
},
|
|
214
155
|
dueDate: {
|
|
215
156
|
type: "string",
|
|
216
|
-
description: "Due date (
|
|
157
|
+
description: "Due date (Unix timestamp in milliseconds). Convert dates to this format before submitting."
|
|
217
158
|
},
|
|
218
159
|
assignees: {
|
|
219
160
|
type: "array",
|
|
@@ -232,17 +173,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
232
173
|
},
|
|
233
174
|
{
|
|
234
175
|
name: "create_list",
|
|
235
|
-
description: "Create a new list in a ClickUp space",
|
|
176
|
+
description: "Create a new list directly in a ClickUp space. Use this tool when you need a top-level list not nested inside a folder. The tool can find spaces by name, so explicit space IDs aren't required. For creating lists inside folders, use create_list_in_folder instead.",
|
|
236
177
|
inputSchema: {
|
|
237
178
|
type: "object",
|
|
238
179
|
properties: {
|
|
239
180
|
spaceId: {
|
|
240
181
|
type: "string",
|
|
241
|
-
description: "ID of the space to create the list in (optional if spaceName
|
|
182
|
+
description: "ID of the space to create the list in (optional if using spaceName instead)"
|
|
242
183
|
},
|
|
243
184
|
spaceName: {
|
|
244
185
|
type: "string",
|
|
245
|
-
description: "Name of the space to create the list in (optional if spaceId
|
|
186
|
+
description: "Name of the space to create the list in - will automatically find the space by name (optional if using spaceId instead)"
|
|
246
187
|
},
|
|
247
188
|
name: {
|
|
248
189
|
type: "string",
|
|
@@ -254,11 +195,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
254
195
|
},
|
|
255
196
|
dueDate: {
|
|
256
197
|
type: "string",
|
|
257
|
-
description: "Due date for the list (
|
|
198
|
+
description: "Due date for the list (Unix timestamp in milliseconds). Convert dates to this format before submitting."
|
|
258
199
|
},
|
|
259
200
|
priority: {
|
|
260
201
|
type: "number",
|
|
261
|
-
description: "Priority of the list (1-4)"
|
|
202
|
+
description: "Priority of the list (1-4), where 1 is urgent/highest priority and 4 is lowest priority. Only set when explicitly requested."
|
|
262
203
|
},
|
|
263
204
|
assignee: {
|
|
264
205
|
type: "number",
|
|
@@ -274,17 +215,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
274
215
|
},
|
|
275
216
|
{
|
|
276
217
|
name: "create_folder",
|
|
277
|
-
description: "Create a new folder in a ClickUp space",
|
|
218
|
+
description: "Create a new folder in a ClickUp space for organizing related lists. Use this tool when you need to group multiple lists together. The tool can find spaces by name, so explicit space IDs aren't required. After creating a folder, you can add lists to it using create_list_in_folder.",
|
|
278
219
|
inputSchema: {
|
|
279
220
|
type: "object",
|
|
280
221
|
properties: {
|
|
281
222
|
spaceId: {
|
|
282
223
|
type: "string",
|
|
283
|
-
description: "ID of the space to create the folder in (optional if spaceName
|
|
224
|
+
description: "ID of the space to create the folder in (optional if using spaceName instead)"
|
|
284
225
|
},
|
|
285
226
|
spaceName: {
|
|
286
227
|
type: "string",
|
|
287
|
-
description: "Name of the space to create the folder in (optional if spaceId
|
|
228
|
+
description: "Name of the space to create the folder in - will automatically find the space by name (optional if using spaceId instead)"
|
|
288
229
|
},
|
|
289
230
|
name: {
|
|
290
231
|
type: "string",
|
|
@@ -292,7 +233,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
292
233
|
},
|
|
293
234
|
override_statuses: {
|
|
294
235
|
type: "boolean",
|
|
295
|
-
description: "Whether to override space statuses"
|
|
236
|
+
description: "Whether to override space statuses with folder-specific statuses"
|
|
296
237
|
}
|
|
297
238
|
},
|
|
298
239
|
required: ["name"]
|
|
@@ -300,25 +241,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
300
241
|
},
|
|
301
242
|
{
|
|
302
243
|
name: "create_list_in_folder",
|
|
303
|
-
description: "Create a new list
|
|
244
|
+
description: "Create a new list within a ClickUp folder. Use this tool when you need to add a list to an existing folder structure. The tool can find folders and spaces by name, so explicit IDs aren't required. For top-level lists not in folders, use create_list instead.",
|
|
304
245
|
inputSchema: {
|
|
305
246
|
type: "object",
|
|
306
247
|
properties: {
|
|
307
248
|
folderId: {
|
|
308
249
|
type: "string",
|
|
309
|
-
description: "ID of the folder to create the list in (optional if folderName
|
|
250
|
+
description: "ID of the folder to create the list in (optional if using folderName instead)"
|
|
310
251
|
},
|
|
311
252
|
folderName: {
|
|
312
253
|
type: "string",
|
|
313
|
-
description: "Name of the folder to create the list in"
|
|
254
|
+
description: "Name of the folder to create the list in - will automatically find the folder by name (optional if using folderId instead)"
|
|
314
255
|
},
|
|
315
256
|
spaceId: {
|
|
316
257
|
type: "string",
|
|
317
|
-
description: "ID of the space containing the folder (
|
|
258
|
+
description: "ID of the space containing the folder (optional if using spaceName instead)"
|
|
318
259
|
},
|
|
319
260
|
spaceName: {
|
|
320
261
|
type: "string",
|
|
321
|
-
description: "Name of the space containing the folder (
|
|
262
|
+
description: "Name of the space containing the folder - will automatically find the space by name (optional if using spaceId instead)"
|
|
322
263
|
},
|
|
323
264
|
name: {
|
|
324
265
|
type: "string",
|
|
@@ -330,7 +271,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
330
271
|
},
|
|
331
272
|
status: {
|
|
332
273
|
type: "string",
|
|
333
|
-
description: "Status of the list"
|
|
274
|
+
description: "Status of the list (uses folder default if not specified)"
|
|
334
275
|
}
|
|
335
276
|
},
|
|
336
277
|
required: ["name"]
|
|
@@ -338,77 +279,225 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
338
279
|
},
|
|
339
280
|
{
|
|
340
281
|
name: "move_task",
|
|
341
|
-
description: "Move
|
|
282
|
+
description: "Move an existing task from its current list to a different list. Use this tool when you need to relocate a task within your workspace hierarchy. The tool can find tasks and lists by name, so explicit IDs aren't required. Task statuses may be reset if the destination list uses different status options.",
|
|
342
283
|
inputSchema: {
|
|
343
284
|
type: "object",
|
|
344
285
|
properties: {
|
|
345
286
|
taskId: {
|
|
346
287
|
type: "string",
|
|
347
|
-
description: "ID of the task to move"
|
|
288
|
+
description: "ID of the task to move (optional if using taskName instead)"
|
|
289
|
+
},
|
|
290
|
+
taskName: {
|
|
291
|
+
type: "string",
|
|
292
|
+
description: "Name of the task to move - will automatically find the task by name (optional if using taskId instead)"
|
|
293
|
+
},
|
|
294
|
+
sourceListName: {
|
|
295
|
+
type: "string",
|
|
296
|
+
description: "Optional: Name of the source list to narrow down task search when multiple tasks have the same name"
|
|
348
297
|
},
|
|
349
298
|
listId: {
|
|
350
299
|
type: "string",
|
|
351
|
-
description: "ID of the destination list (optional if listName
|
|
300
|
+
description: "ID of the destination list (optional if using listName instead)"
|
|
352
301
|
},
|
|
353
302
|
listName: {
|
|
354
303
|
type: "string",
|
|
355
|
-
description: "Name of the destination list (optional if listId
|
|
304
|
+
description: "Name of the destination list - will automatically find the list by name (optional if using listId instead)"
|
|
356
305
|
}
|
|
357
306
|
},
|
|
358
|
-
required: [
|
|
307
|
+
required: []
|
|
359
308
|
}
|
|
360
309
|
},
|
|
361
310
|
{
|
|
362
311
|
name: "duplicate_task",
|
|
363
|
-
description: "
|
|
312
|
+
description: "Create a copy of an existing task in the same or different list. Use this tool when you need to replicate a task's content and properties. The tool can find tasks and lists by name, so explicit IDs aren't required. The duplicate will preserve name, description, priority, and other attributes from the original task.",
|
|
364
313
|
inputSchema: {
|
|
365
314
|
type: "object",
|
|
366
315
|
properties: {
|
|
367
316
|
taskId: {
|
|
368
317
|
type: "string",
|
|
369
|
-
description: "ID of the task to duplicate"
|
|
318
|
+
description: "ID of the task to duplicate (optional if using taskName instead)"
|
|
319
|
+
},
|
|
320
|
+
taskName: {
|
|
321
|
+
type: "string",
|
|
322
|
+
description: "Name of the task to duplicate - will automatically find the task by name (optional if using taskId instead)"
|
|
323
|
+
},
|
|
324
|
+
sourceListName: {
|
|
325
|
+
type: "string",
|
|
326
|
+
description: "Optional: Name of the source list to narrow down task search when multiple tasks have the same name"
|
|
370
327
|
},
|
|
371
328
|
listId: {
|
|
372
329
|
type: "string",
|
|
373
|
-
description: "ID of the
|
|
330
|
+
description: "ID of the list to create the duplicate in (optional if using listName instead)"
|
|
374
331
|
},
|
|
375
332
|
listName: {
|
|
376
333
|
type: "string",
|
|
377
|
-
description: "Name of the
|
|
334
|
+
description: "Name of the list to create the duplicate in - will automatically find the list by name (optional if using listId instead)"
|
|
378
335
|
}
|
|
379
336
|
},
|
|
380
|
-
required: [
|
|
337
|
+
required: []
|
|
381
338
|
}
|
|
382
339
|
},
|
|
383
340
|
{
|
|
384
341
|
name: "update_task",
|
|
385
|
-
description: "
|
|
342
|
+
description: "Modify the properties of an existing task. Use this tool when you need to change a task's name, description, status, priority, or due date. The tool can find tasks by name, so explicit task IDs aren't required. Only the fields you specify will be updated; other fields will remain unchanged.",
|
|
386
343
|
inputSchema: {
|
|
387
344
|
type: "object",
|
|
388
345
|
properties: {
|
|
389
346
|
taskId: {
|
|
390
347
|
type: "string",
|
|
391
|
-
description: "ID of the task to update"
|
|
348
|
+
description: "ID of the task to update (optional if using taskName instead)"
|
|
349
|
+
},
|
|
350
|
+
taskName: {
|
|
351
|
+
type: "string",
|
|
352
|
+
description: "Name of the task to update - will automatically find the task by name (optional if using taskId instead)"
|
|
353
|
+
},
|
|
354
|
+
listName: {
|
|
355
|
+
type: "string",
|
|
356
|
+
description: "Optional: Name of the list to narrow down task search when multiple tasks have the same name"
|
|
392
357
|
},
|
|
393
358
|
name: {
|
|
394
359
|
type: "string",
|
|
395
|
-
description: "New name
|
|
360
|
+
description: "New name for the task"
|
|
396
361
|
},
|
|
397
362
|
description: {
|
|
398
363
|
type: "string",
|
|
399
|
-
description: "New description
|
|
364
|
+
description: "New plain text description for the task"
|
|
400
365
|
},
|
|
401
366
|
status: {
|
|
402
367
|
type: "string",
|
|
403
|
-
description: "New status
|
|
368
|
+
description: "New status for the task (must be a valid status in the task's list)"
|
|
404
369
|
},
|
|
405
370
|
priority: {
|
|
406
371
|
type: "number",
|
|
407
|
-
description: "New priority
|
|
372
|
+
description: "New priority for the task (1-4), where 1 is urgent/highest priority and 4 is lowest priority"
|
|
408
373
|
},
|
|
409
374
|
dueDate: {
|
|
410
375
|
type: "string",
|
|
411
|
-
description: "New due date
|
|
376
|
+
description: "New due date for the task (Unix timestamp in milliseconds). Convert dates to this format before submitting."
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
required: []
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
name: "get_tasks",
|
|
384
|
+
description: "Retrieve tasks from a ClickUp list with optional filtering capabilities. Use this tool when you need to see existing tasks or analyze your current workload. The tool can find lists by name, eliminating the need for explicit list IDs. Results can be filtered by status, assignees, dates, and more.",
|
|
385
|
+
inputSchema: {
|
|
386
|
+
type: "object",
|
|
387
|
+
properties: {
|
|
388
|
+
listId: {
|
|
389
|
+
type: "string",
|
|
390
|
+
description: "ID of the list to get tasks from (optional if using listName instead)"
|
|
391
|
+
},
|
|
392
|
+
listName: {
|
|
393
|
+
type: "string",
|
|
394
|
+
description: "Name of the list to get tasks from - will automatically find the list by name (optional if using listId instead)"
|
|
395
|
+
},
|
|
396
|
+
archived: {
|
|
397
|
+
type: "boolean",
|
|
398
|
+
description: "Set to true to include archived tasks in the results"
|
|
399
|
+
},
|
|
400
|
+
page: {
|
|
401
|
+
type: "number",
|
|
402
|
+
description: "Page number for pagination when dealing with many tasks (starts at 0)"
|
|
403
|
+
},
|
|
404
|
+
order_by: {
|
|
405
|
+
type: "string",
|
|
406
|
+
description: "Field to order tasks by (e.g., 'due_date', 'created', 'updated')"
|
|
407
|
+
},
|
|
408
|
+
reverse: {
|
|
409
|
+
type: "boolean",
|
|
410
|
+
description: "Set to true to reverse the sort order (descending instead of ascending)"
|
|
411
|
+
},
|
|
412
|
+
subtasks: {
|
|
413
|
+
type: "boolean",
|
|
414
|
+
description: "Set to true to include subtasks in the results"
|
|
415
|
+
},
|
|
416
|
+
statuses: {
|
|
417
|
+
type: "array",
|
|
418
|
+
items: { type: "string" },
|
|
419
|
+
description: "Array of status names to filter tasks by (e.g., ['To Do', 'In Progress'])"
|
|
420
|
+
},
|
|
421
|
+
include_closed: {
|
|
422
|
+
type: "boolean",
|
|
423
|
+
description: "Set to true to include tasks with 'Closed' status"
|
|
424
|
+
},
|
|
425
|
+
assignees: {
|
|
426
|
+
type: "array",
|
|
427
|
+
items: { type: "string" },
|
|
428
|
+
description: "Array of user IDs to filter tasks by assignee"
|
|
429
|
+
},
|
|
430
|
+
due_date_gt: {
|
|
431
|
+
type: "number",
|
|
432
|
+
description: "Filter tasks due after this timestamp (Unix milliseconds)"
|
|
433
|
+
},
|
|
434
|
+
due_date_lt: {
|
|
435
|
+
type: "number",
|
|
436
|
+
description: "Filter tasks due before this timestamp (Unix milliseconds)"
|
|
437
|
+
},
|
|
438
|
+
date_created_gt: {
|
|
439
|
+
type: "number",
|
|
440
|
+
description: "Filter tasks created after this timestamp (Unix milliseconds)"
|
|
441
|
+
},
|
|
442
|
+
date_created_lt: {
|
|
443
|
+
type: "number",
|
|
444
|
+
description: "Filter tasks created before this timestamp (Unix milliseconds)"
|
|
445
|
+
},
|
|
446
|
+
date_updated_gt: {
|
|
447
|
+
type: "number",
|
|
448
|
+
description: "Filter tasks updated after this timestamp (Unix milliseconds)"
|
|
449
|
+
},
|
|
450
|
+
date_updated_lt: {
|
|
451
|
+
type: "number",
|
|
452
|
+
description: "Filter tasks updated before this timestamp (Unix milliseconds)"
|
|
453
|
+
},
|
|
454
|
+
custom_fields: {
|
|
455
|
+
type: "object",
|
|
456
|
+
description: "Object with custom field IDs as keys and desired values for filtering"
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
required: []
|
|
460
|
+
}
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
name: "get_task",
|
|
464
|
+
description: "Retrieve comprehensive details about a specific ClickUp task. Use this tool when you need in-depth information about a particular task, including its description, custom fields, attachments, and other metadata. The tool can find tasks by name, eliminating the need for explicit task IDs.",
|
|
465
|
+
inputSchema: {
|
|
466
|
+
type: "object",
|
|
467
|
+
properties: {
|
|
468
|
+
taskId: {
|
|
469
|
+
type: "string",
|
|
470
|
+
description: "ID of the task to retrieve (optional if using taskName instead)"
|
|
471
|
+
},
|
|
472
|
+
taskName: {
|
|
473
|
+
type: "string",
|
|
474
|
+
description: "Name of the task to retrieve - will automatically find the task by name (optional if using taskId instead)"
|
|
475
|
+
},
|
|
476
|
+
listName: {
|
|
477
|
+
type: "string",
|
|
478
|
+
description: "Optional: Name of the list to narrow down task search when multiple tasks have the same name"
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
required: []
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: "delete_task",
|
|
486
|
+
description: "Permanently remove a task from your ClickUp workspace. Use this tool with caution as deletion cannot be undone. The tool requires an explicit task ID for safety reasons, which you can obtain by first using get_task or get_tasks to find the appropriate task ID.",
|
|
487
|
+
inputSchema: {
|
|
488
|
+
type: "object",
|
|
489
|
+
properties: {
|
|
490
|
+
taskId: {
|
|
491
|
+
type: "string",
|
|
492
|
+
description: "ID of the task to delete - this is required for safety to prevent accidental deletions"
|
|
493
|
+
},
|
|
494
|
+
taskName: {
|
|
495
|
+
type: "string",
|
|
496
|
+
description: "Name of the task to delete - will automatically find the task by name (optional if using taskId instead)"
|
|
497
|
+
},
|
|
498
|
+
listName: {
|
|
499
|
+
type: "string",
|
|
500
|
+
description: "Optional: Name of the list to narrow down task search when multiple tasks have the same name"
|
|
412
501
|
}
|
|
413
502
|
},
|
|
414
503
|
required: ["taskId"]
|
|
@@ -418,23 +507,68 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
418
507
|
};
|
|
419
508
|
});
|
|
420
509
|
/**
|
|
421
|
-
* Handler for
|
|
510
|
+
* Handler for the CallToolRequestSchema.
|
|
511
|
+
* Handles the execution of tools like listing spaces, creating tasks, and updating tasks.
|
|
422
512
|
*/
|
|
423
513
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
424
514
|
try {
|
|
425
515
|
switch (request.params.name) {
|
|
426
|
-
case "
|
|
427
|
-
const
|
|
516
|
+
case "get_workspace_hierarchy": {
|
|
517
|
+
const hierarchy = await clickup.getWorkspaceHierarchy();
|
|
428
518
|
return {
|
|
429
519
|
content: [{
|
|
430
520
|
type: "text",
|
|
431
|
-
text:
|
|
521
|
+
text: JSON.stringify({
|
|
522
|
+
workspace: {
|
|
523
|
+
id: hierarchy.root.id,
|
|
524
|
+
name: hierarchy.root.name,
|
|
525
|
+
spaces: hierarchy.root.children.map((space) => ({
|
|
526
|
+
id: space.id,
|
|
527
|
+
name: space.name,
|
|
528
|
+
lists: space.children
|
|
529
|
+
.filter((node) => node.type === 'list')
|
|
530
|
+
.map((list) => ({
|
|
531
|
+
id: list.id,
|
|
532
|
+
name: list.name,
|
|
533
|
+
path: `${space.name} > ${list.name}`
|
|
534
|
+
})),
|
|
535
|
+
folders: space.children
|
|
536
|
+
.filter((node) => node.type === 'folder')
|
|
537
|
+
.map((folder) => ({
|
|
538
|
+
id: folder.id,
|
|
539
|
+
name: folder.name,
|
|
540
|
+
path: `${space.name} > ${folder.name}`,
|
|
541
|
+
lists: folder.children.map((list) => ({
|
|
542
|
+
id: list.id,
|
|
543
|
+
name: list.name,
|
|
544
|
+
path: `${space.name} > ${folder.name} > ${list.name}`
|
|
545
|
+
}))
|
|
546
|
+
}))
|
|
547
|
+
}))
|
|
548
|
+
}
|
|
549
|
+
}, null, 2)
|
|
432
550
|
}]
|
|
433
551
|
};
|
|
434
552
|
}
|
|
435
553
|
case "create_task": {
|
|
436
554
|
const args = request.params.arguments;
|
|
437
|
-
|
|
555
|
+
if (!args.listId && !args.listName) {
|
|
556
|
+
throw new Error("Either listId or listName is required");
|
|
557
|
+
}
|
|
558
|
+
if (!args.name) {
|
|
559
|
+
throw new Error("name is required");
|
|
560
|
+
}
|
|
561
|
+
let listId = args.listId;
|
|
562
|
+
if (!listId && args.listName) {
|
|
563
|
+
const hierarchy = await clickup.getWorkspaceHierarchy();
|
|
564
|
+
const result = clickup.findIDByNameInHierarchy(hierarchy, args.listName, 'list');
|
|
565
|
+
if (!result) {
|
|
566
|
+
throw new Error(`List with name "${args.listName}" not found`);
|
|
567
|
+
}
|
|
568
|
+
listId = result.id;
|
|
569
|
+
}
|
|
570
|
+
const { listId: _, listName: __, ...taskData } = args;
|
|
571
|
+
const task = await clickup.createTask(listId, taskData);
|
|
438
572
|
return {
|
|
439
573
|
content: [{
|
|
440
574
|
type: "text",
|
|
@@ -444,26 +578,26 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
444
578
|
}
|
|
445
579
|
case "create_bulk_tasks": {
|
|
446
580
|
const args = request.params.arguments;
|
|
581
|
+
if (!args.listId && !args.listName) {
|
|
582
|
+
throw new Error("Either listId or listName is required");
|
|
583
|
+
}
|
|
584
|
+
if (!args.tasks || args.tasks.length === 0) {
|
|
585
|
+
throw new Error("tasks array is required and must not be empty");
|
|
586
|
+
}
|
|
447
587
|
let listId = args.listId;
|
|
448
588
|
if (!listId && args.listName) {
|
|
449
|
-
const result = await clickup.
|
|
589
|
+
const result = await clickup.findListIDByName(args.listName);
|
|
450
590
|
if (!result) {
|
|
451
591
|
throw new Error(`List with name "${args.listName}" not found`);
|
|
452
592
|
}
|
|
453
|
-
listId = result.
|
|
454
|
-
}
|
|
455
|
-
if (!listId) {
|
|
456
|
-
throw new Error("Either listId or listName is required");
|
|
457
|
-
}
|
|
458
|
-
if (!args.tasks || !args.tasks.length) {
|
|
459
|
-
throw new Error("At least one task is required");
|
|
593
|
+
listId = result.id;
|
|
460
594
|
}
|
|
461
595
|
const { listId: _, listName: __, tasks } = args;
|
|
462
596
|
const createdTasks = await clickup.createBulkTasks(listId, { tasks });
|
|
463
597
|
return {
|
|
464
598
|
content: [{
|
|
465
599
|
type: "text",
|
|
466
|
-
text: `Created ${createdTasks.length} tasks
|
|
600
|
+
text: `Created ${createdTasks.length} tasks`
|
|
467
601
|
}]
|
|
468
602
|
};
|
|
469
603
|
}
|
|
@@ -472,41 +606,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
472
606
|
if (!args.name) {
|
|
473
607
|
throw new Error("name is required");
|
|
474
608
|
}
|
|
475
|
-
// If folder is specified, create list in folder
|
|
476
|
-
if (args.folderName || args.folderId) {
|
|
477
|
-
let folderId = args.folderId;
|
|
478
|
-
if (!folderId && args.folderName) {
|
|
479
|
-
const result = await clickup.findFolderByNameGlobally(config.teamId, args.folderName);
|
|
480
|
-
if (!result) {
|
|
481
|
-
throw new Error(`Folder with name "${args.folderName}" not found`);
|
|
482
|
-
}
|
|
483
|
-
folderId = result.folder.id;
|
|
484
|
-
}
|
|
485
|
-
if (!folderId) {
|
|
486
|
-
throw new Error("Either folderId or folderName must be provided");
|
|
487
|
-
}
|
|
488
|
-
const { spaceId: _, spaceName: __, folderName: ___, folderId: ____, ...listData } = args;
|
|
489
|
-
const list = await clickup.createListInFolder(folderId, listData);
|
|
490
|
-
return {
|
|
491
|
-
content: [{
|
|
492
|
-
type: "text",
|
|
493
|
-
text: `Created list ${list.id}: ${list.name} in folder`
|
|
494
|
-
}]
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
// Otherwise, create list in space
|
|
498
609
|
let spaceId = args.spaceId;
|
|
499
610
|
if (!spaceId && args.spaceName) {
|
|
500
|
-
const
|
|
501
|
-
if (!
|
|
611
|
+
const foundId = await clickup.findSpaceIDByName(args.spaceName);
|
|
612
|
+
if (!foundId) {
|
|
502
613
|
throw new Error(`Space with name "${args.spaceName}" not found`);
|
|
503
614
|
}
|
|
504
|
-
spaceId =
|
|
615
|
+
spaceId = foundId;
|
|
505
616
|
}
|
|
506
617
|
if (!spaceId) {
|
|
507
618
|
throw new Error("Either spaceId or spaceName must be provided");
|
|
508
619
|
}
|
|
509
|
-
const { spaceId: _, spaceName: __,
|
|
620
|
+
const { spaceId: _, spaceName: __, ...listData } = args;
|
|
510
621
|
const list = await clickup.createList(spaceId, listData);
|
|
511
622
|
return {
|
|
512
623
|
content: [{
|
|
@@ -522,11 +633,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
522
633
|
}
|
|
523
634
|
let spaceId = args.spaceId;
|
|
524
635
|
if (!spaceId && args.spaceName) {
|
|
525
|
-
const
|
|
526
|
-
if (!
|
|
636
|
+
const foundId = await clickup.findSpaceIDByName(args.spaceName);
|
|
637
|
+
if (!foundId) {
|
|
527
638
|
throw new Error(`Space with name "${args.spaceName}" not found`);
|
|
528
639
|
}
|
|
529
|
-
spaceId =
|
|
640
|
+
spaceId = foundId;
|
|
530
641
|
}
|
|
531
642
|
if (!spaceId) {
|
|
532
643
|
throw new Error("Either spaceId or spaceName must be provided");
|
|
@@ -547,195 +658,340 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
547
658
|
}
|
|
548
659
|
let folderId = args.folderId;
|
|
549
660
|
if (!folderId && args.folderName) {
|
|
550
|
-
const result = await clickup.
|
|
661
|
+
const result = await clickup.findFolderIDByName(args.folderName);
|
|
551
662
|
if (!result) {
|
|
552
663
|
throw new Error(`Folder with name "${args.folderName}" not found`);
|
|
553
664
|
}
|
|
554
|
-
folderId = result.
|
|
665
|
+
folderId = result.id;
|
|
555
666
|
}
|
|
556
667
|
if (!folderId) {
|
|
557
|
-
throw new Error("Either folderId or folderName
|
|
668
|
+
throw new Error("Either folderId or folderName must be provided");
|
|
558
669
|
}
|
|
559
|
-
const listData =
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
670
|
+
const { folderId: _, folderName: __, spaceId: ___, spaceName: ____, ...listData } = args;
|
|
671
|
+
const list = await clickup.createListInFolder(folderId, listData);
|
|
672
|
+
return {
|
|
673
|
+
content: [{
|
|
674
|
+
type: "text",
|
|
675
|
+
text: `Created list ${list.id}: ${list.name} in folder`
|
|
676
|
+
}]
|
|
563
677
|
};
|
|
564
|
-
try {
|
|
565
|
-
const list = await clickup.createListInFolder(folderId, listData);
|
|
566
|
-
return {
|
|
567
|
-
content: [{
|
|
568
|
-
type: "text",
|
|
569
|
-
text: `Created list ${list.id}: ${list.name} in folder`
|
|
570
|
-
}]
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
catch (error) {
|
|
574
|
-
throw new Error(`Failed to create list: ${error.message}`);
|
|
575
|
-
}
|
|
576
678
|
}
|
|
577
679
|
case "move_task": {
|
|
578
680
|
const args = request.params.arguments;
|
|
579
|
-
if (!args.taskId) {
|
|
580
|
-
throw new Error("taskId is required");
|
|
681
|
+
if (!args.taskId && !args.taskName) {
|
|
682
|
+
throw new Error("Either taskId or taskName is required");
|
|
683
|
+
}
|
|
684
|
+
if (!args.listId && !args.listName) {
|
|
685
|
+
throw new Error("Either listId or listName is required");
|
|
686
|
+
}
|
|
687
|
+
let taskId = args.taskId;
|
|
688
|
+
if (!taskId && args.taskName) {
|
|
689
|
+
const result = await clickup.findTaskByName(args.taskName, undefined, args.sourceListName);
|
|
690
|
+
if (!result) {
|
|
691
|
+
throw new Error(`Task with name "${args.taskName}" not found${args.sourceListName ? ` in list "${args.sourceListName}"` : ''}`);
|
|
692
|
+
}
|
|
693
|
+
taskId = result.id;
|
|
581
694
|
}
|
|
582
695
|
let listId = args.listId;
|
|
583
696
|
if (!listId && args.listName) {
|
|
584
|
-
const result = await clickup.
|
|
697
|
+
const result = await clickup.findListIDByName(args.listName);
|
|
585
698
|
if (!result) {
|
|
586
699
|
throw new Error(`List with name "${args.listName}" not found`);
|
|
587
700
|
}
|
|
588
|
-
listId = result.
|
|
701
|
+
listId = result.id;
|
|
589
702
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
const task = await clickup.moveTask(args.taskId, listId);
|
|
703
|
+
// Get the original task details for the response message
|
|
704
|
+
const originalTask = await clickup.getTask(taskId);
|
|
705
|
+
const newTask = await clickup.moveTask(taskId, listId);
|
|
594
706
|
return {
|
|
595
707
|
content: [{
|
|
596
708
|
type: "text",
|
|
597
|
-
text: `Moved task ${
|
|
709
|
+
text: `Moved task "${originalTask.name}" from "${originalTask.list.name}" to "${newTask.list.name}"`
|
|
598
710
|
}]
|
|
599
711
|
};
|
|
600
712
|
}
|
|
601
713
|
case "duplicate_task": {
|
|
602
714
|
const args = request.params.arguments;
|
|
603
|
-
|
|
604
|
-
|
|
715
|
+
// Require either taskId or taskName
|
|
716
|
+
if (!args.taskId && !args.taskName) {
|
|
717
|
+
throw new Error("Either taskId or taskName is required");
|
|
718
|
+
}
|
|
719
|
+
// Require either listId or listName
|
|
720
|
+
if (!args.listId && !args.listName) {
|
|
721
|
+
throw new Error("Either listId or listName is required");
|
|
722
|
+
}
|
|
723
|
+
// Get taskId from taskName if needed
|
|
724
|
+
let taskId = args.taskId;
|
|
725
|
+
if (!taskId && args.taskName) {
|
|
726
|
+
const result = await clickup.findTaskByName(args.taskName, undefined, args.sourceListName);
|
|
727
|
+
if (!result) {
|
|
728
|
+
throw new Error(`Task with name "${args.taskName}" not found${args.sourceListName ? ` in list "${args.sourceListName}"` : ''}`);
|
|
729
|
+
}
|
|
730
|
+
taskId = result.id;
|
|
605
731
|
}
|
|
732
|
+
// Get listId from listName if needed
|
|
606
733
|
let listId = args.listId;
|
|
607
734
|
if (!listId && args.listName) {
|
|
608
|
-
const result = await clickup.
|
|
735
|
+
const result = await clickup.findListIDByName(args.listName);
|
|
609
736
|
if (!result) {
|
|
610
737
|
throw new Error(`List with name "${args.listName}" not found`);
|
|
611
738
|
}
|
|
612
|
-
listId = result.
|
|
739
|
+
listId = result.id;
|
|
740
|
+
}
|
|
741
|
+
// Get the original task details for the response message
|
|
742
|
+
const originalTask = await clickup.getTask(taskId);
|
|
743
|
+
const newTask = await clickup.duplicateTask(taskId, listId);
|
|
744
|
+
return {
|
|
745
|
+
content: [{
|
|
746
|
+
type: "text",
|
|
747
|
+
text: `Duplicated task "${originalTask.name}" from "${originalTask.list.name}" to "${newTask.list.name}"`
|
|
748
|
+
}]
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
case "update_task": {
|
|
752
|
+
const args = request.params.arguments;
|
|
753
|
+
if (!args.taskId && !args.taskName) {
|
|
754
|
+
throw new Error("Either taskId or taskName is required");
|
|
755
|
+
}
|
|
756
|
+
let taskId = args.taskId;
|
|
757
|
+
if (!taskId && args.taskName) {
|
|
758
|
+
const result = await clickup.findTaskByName(args.taskName, undefined, args.listName);
|
|
759
|
+
if (!result) {
|
|
760
|
+
throw new Error(`Task with name "${args.taskName}" not found${args.listName ? ` in list "${args.listName}"` : ''}`);
|
|
761
|
+
}
|
|
762
|
+
taskId = result.id;
|
|
613
763
|
}
|
|
614
|
-
|
|
764
|
+
const { taskId: _, taskName: __, listName: ___, ...updateData } = args;
|
|
765
|
+
const task = await clickup.updateTask(taskId, updateData);
|
|
766
|
+
return {
|
|
767
|
+
content: [{
|
|
768
|
+
type: "text",
|
|
769
|
+
text: `Updated task ${task.id}: ${task.name}`
|
|
770
|
+
}]
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
case "get_tasks": {
|
|
774
|
+
const args = request.params.arguments;
|
|
775
|
+
// Enforce required listName field as specified in the schema
|
|
776
|
+
if (!args.listId && !args.listName) {
|
|
615
777
|
throw new Error("Either listId or listName is required");
|
|
616
778
|
}
|
|
617
|
-
|
|
779
|
+
let listId = args.listId;
|
|
780
|
+
if (!listId && args.listName) {
|
|
781
|
+
const result = await clickup.findListIDByName(args.listName);
|
|
782
|
+
if (!result) {
|
|
783
|
+
throw new Error(`List with name "${args.listName}" not found`);
|
|
784
|
+
}
|
|
785
|
+
listId = result.id;
|
|
786
|
+
}
|
|
787
|
+
// Remove listId and listName from filters
|
|
788
|
+
const { listId: _, listName: __, ...filters } = args;
|
|
789
|
+
const { tasks, statuses } = await clickup.getTasks(listId, filters);
|
|
618
790
|
return {
|
|
619
791
|
content: [{
|
|
620
792
|
type: "text",
|
|
621
|
-
text:
|
|
793
|
+
text: JSON.stringify({ tasks, available_statuses: statuses }, null, 2)
|
|
622
794
|
}]
|
|
623
795
|
};
|
|
624
796
|
}
|
|
625
|
-
case "
|
|
797
|
+
case "get_task": {
|
|
798
|
+
const args = request.params.arguments;
|
|
799
|
+
// Enforce required taskName field as specified in the schema
|
|
800
|
+
if (!args.taskId && !args.taskName) {
|
|
801
|
+
throw new Error("Either taskId or taskName is required");
|
|
802
|
+
}
|
|
803
|
+
let taskId = args.taskId;
|
|
804
|
+
if (!taskId && args.taskName) {
|
|
805
|
+
const result = await clickup.findTaskByName(args.taskName, undefined, args.listName);
|
|
806
|
+
if (!result) {
|
|
807
|
+
throw new Error(`Task with name "${args.taskName}" not found${args.listName ? ` in list "${args.listName}"` : ''}`);
|
|
808
|
+
}
|
|
809
|
+
taskId = result.id;
|
|
810
|
+
}
|
|
811
|
+
const task = await clickup.getTask(taskId);
|
|
812
|
+
return {
|
|
813
|
+
content: [{
|
|
814
|
+
type: "text",
|
|
815
|
+
text: JSON.stringify(task, null, 2)
|
|
816
|
+
}]
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
case "delete_task": {
|
|
626
820
|
const args = request.params.arguments;
|
|
821
|
+
// Validate the required taskId parameter
|
|
627
822
|
if (!args.taskId) {
|
|
628
|
-
throw new Error("taskId is required");
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
823
|
+
throw new Error("taskId is required for deletion operations");
|
|
824
|
+
}
|
|
825
|
+
// Store the task name before deletion for the response message
|
|
826
|
+
let taskName = args.taskName;
|
|
827
|
+
if (!taskName) {
|
|
828
|
+
try {
|
|
829
|
+
const task = await clickup.getTask(args.taskId);
|
|
830
|
+
taskName = task.name;
|
|
831
|
+
}
|
|
832
|
+
catch (error) {
|
|
833
|
+
// If we can't get the task details, just use the ID in the response
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
await clickup.deleteTask(args.taskId);
|
|
638
837
|
return {
|
|
639
838
|
content: [{
|
|
640
839
|
type: "text",
|
|
641
|
-
text: `
|
|
840
|
+
text: `Successfully deleted task ${taskName || args.taskId}`
|
|
642
841
|
}]
|
|
643
842
|
};
|
|
644
843
|
}
|
|
645
844
|
default:
|
|
646
|
-
throw new Error(
|
|
845
|
+
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
647
846
|
}
|
|
648
847
|
}
|
|
649
848
|
catch (error) {
|
|
650
|
-
console.error('Error
|
|
651
|
-
|
|
849
|
+
console.error('Error in tool call:', error);
|
|
850
|
+
return {
|
|
851
|
+
content: [{
|
|
852
|
+
type: "text",
|
|
853
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
854
|
+
}]
|
|
855
|
+
};
|
|
652
856
|
}
|
|
653
857
|
});
|
|
654
858
|
/**
|
|
655
|
-
*
|
|
859
|
+
* Add handlers for listing and getting prompts.
|
|
860
|
+
* Prompts include summarizing tasks, analyzing priorities, and generating task descriptions.
|
|
656
861
|
*/
|
|
657
862
|
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
658
863
|
return {
|
|
659
864
|
prompts: [
|
|
660
865
|
{
|
|
661
866
|
name: "summarize_tasks",
|
|
662
|
-
description: "
|
|
867
|
+
description: "Generate a comprehensive summary of tasks in a ClickUp list or workspace. The summary includes a high-level overview, groups tasks by status, highlights priority items, and identifies potential task relationships or dependencies. Useful for project status reports and team updates.",
|
|
663
868
|
},
|
|
664
869
|
{
|
|
665
|
-
name: "
|
|
666
|
-
description: "
|
|
870
|
+
name: "analyze_priorities",
|
|
871
|
+
description: "Evaluate task priority distribution across your workspace and identify optimization opportunities. The analysis examines current priority assignments, identifies misaligned priorities, suggests adjustments, and recommends task sequencing based on priorities. Helpful for workload management and project planning.",
|
|
667
872
|
},
|
|
668
873
|
{
|
|
669
874
|
name: "generate_description",
|
|
670
|
-
description: "
|
|
875
|
+
description: "Create a detailed, well-structured task description with clearly defined objectives, success criteria, required resources, dependencies, and risk assessments. This prompt helps ensure tasks are comprehensively documented with all necessary information for successful execution.",
|
|
671
876
|
}
|
|
672
877
|
]
|
|
673
878
|
};
|
|
674
879
|
});
|
|
675
|
-
/**
|
|
676
|
-
* Handler for getting a specific prompt.
|
|
677
|
-
*/
|
|
678
880
|
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
679
881
|
try {
|
|
680
882
|
switch (request.params.name) {
|
|
681
883
|
case "summarize_tasks": {
|
|
682
|
-
const
|
|
884
|
+
const spaces = await clickup.getSpaces(config.clickupTeamId);
|
|
885
|
+
const tasks = [];
|
|
886
|
+
// Gather all tasks
|
|
887
|
+
for (const space of spaces) {
|
|
888
|
+
const lists = await clickup.getLists(space.id);
|
|
889
|
+
for (const list of lists) {
|
|
890
|
+
const { tasks: listTasks } = await clickup.getTasks(list.id);
|
|
891
|
+
tasks.push(...listTasks.map((task) => ({
|
|
892
|
+
type: "resource",
|
|
893
|
+
resource: {
|
|
894
|
+
uri: `clickup://task/${task.id}`,
|
|
895
|
+
mimeType: "application/json",
|
|
896
|
+
text: JSON.stringify(task, null, 2)
|
|
897
|
+
}
|
|
898
|
+
})));
|
|
899
|
+
}
|
|
900
|
+
}
|
|
683
901
|
return {
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
902
|
+
messages: [
|
|
903
|
+
{
|
|
904
|
+
role: "user",
|
|
905
|
+
content: {
|
|
906
|
+
type: "text",
|
|
907
|
+
text: "Please provide a summary of the following ClickUp tasks:"
|
|
908
|
+
}
|
|
909
|
+
},
|
|
910
|
+
...tasks.map(task => ({
|
|
911
|
+
role: "user",
|
|
912
|
+
content: task
|
|
913
|
+
})),
|
|
914
|
+
{
|
|
915
|
+
role: "user",
|
|
916
|
+
content: {
|
|
917
|
+
type: "text",
|
|
918
|
+
text: "Please provide:\n1. A high-level overview of all tasks\n2. Group them by status\n3. Highlight any urgent or high-priority items\n4. Suggest any task dependencies or relationships"
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
]
|
|
688
922
|
};
|
|
689
923
|
}
|
|
690
|
-
case "
|
|
691
|
-
const
|
|
924
|
+
case "analyze_priorities": {
|
|
925
|
+
const spaces = await clickup.getSpaces(config.clickupTeamId);
|
|
926
|
+
const tasks = [];
|
|
927
|
+
for (const space of spaces) {
|
|
928
|
+
const lists = await clickup.getLists(space.id);
|
|
929
|
+
for (const list of lists) {
|
|
930
|
+
const { tasks: listTasks } = await clickup.getTasks(list.id);
|
|
931
|
+
tasks.push(...listTasks.map((task) => ({
|
|
932
|
+
type: "resource",
|
|
933
|
+
resource: {
|
|
934
|
+
uri: `clickup://task/${task.id}`,
|
|
935
|
+
mimeType: "application/json",
|
|
936
|
+
text: JSON.stringify(task, null, 2)
|
|
937
|
+
}
|
|
938
|
+
})));
|
|
939
|
+
}
|
|
940
|
+
}
|
|
692
941
|
return {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
942
|
+
messages: [
|
|
943
|
+
{
|
|
944
|
+
role: "user",
|
|
945
|
+
content: {
|
|
946
|
+
type: "text",
|
|
947
|
+
text: "Analyze the priorities of the following ClickUp tasks:"
|
|
948
|
+
}
|
|
949
|
+
},
|
|
950
|
+
...tasks.map(task => ({
|
|
951
|
+
role: "user",
|
|
952
|
+
content: task
|
|
953
|
+
})),
|
|
954
|
+
{
|
|
955
|
+
role: "user",
|
|
956
|
+
content: {
|
|
957
|
+
type: "text",
|
|
958
|
+
text: "Please provide:\n1. Analysis of current priority distribution\n2. Identify any misaligned priorities\n3. Suggest priority adjustments\n4. Recommend task sequencing based on priorities"
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
]
|
|
697
962
|
};
|
|
698
963
|
}
|
|
699
964
|
case "generate_description": {
|
|
700
|
-
if (!request.params.arguments?.taskId) {
|
|
701
|
-
throw new Error("taskId is required for generate_description prompt");
|
|
702
|
-
}
|
|
703
|
-
const output = await handleGenerateDescription(clickup, request.params.arguments.taskId);
|
|
704
965
|
return {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
966
|
+
messages: [
|
|
967
|
+
{
|
|
968
|
+
role: "user",
|
|
969
|
+
content: {
|
|
970
|
+
type: "text",
|
|
971
|
+
text: "Please help me generate a detailed description for a ClickUp task. The description should include:\n1. Clear objective\n2. Success criteria\n3. Required resources\n4. Dependencies\n5. Potential risks\n\nPlease ask me about the task details."
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
]
|
|
709
975
|
};
|
|
710
976
|
}
|
|
711
977
|
default:
|
|
712
|
-
throw new Error("
|
|
978
|
+
throw new Error("Unknown prompt");
|
|
713
979
|
}
|
|
714
980
|
}
|
|
715
981
|
catch (error) {
|
|
716
|
-
console.error('Error
|
|
982
|
+
console.error('Error handling prompt:', error);
|
|
717
983
|
throw error;
|
|
718
984
|
}
|
|
719
985
|
});
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
986
|
+
/**
|
|
987
|
+
* Start the server using stdio transport.
|
|
988
|
+
* This allows the server to communicate via standard input/output streams.
|
|
989
|
+
*/
|
|
990
|
+
async function main() {
|
|
991
|
+
const transport = new StdioServerTransport();
|
|
992
|
+
await server.connect(transport);
|
|
993
|
+
}
|
|
994
|
+
main().catch((error) => {
|
|
995
|
+
console.error("Server error:", error);
|
|
727
996
|
process.exit(1);
|
|
728
997
|
});
|
|
729
|
-
// Handle process signals
|
|
730
|
-
process.on('SIGINT', () => {
|
|
731
|
-
console.log('Received SIGINT. Shutting down...');
|
|
732
|
-
transport.close();
|
|
733
|
-
});
|
|
734
|
-
process.on('SIGTERM', () => {
|
|
735
|
-
console.log('Received SIGTERM. Shutting down...');
|
|
736
|
-
transport.close();
|
|
737
|
-
});
|
|
738
|
-
// Prevent unhandled promise rejections from crashing the server
|
|
739
|
-
process.on('unhandledRejection', (error) => {
|
|
740
|
-
console.error('Unhandled promise rejection:', error);
|
|
741
|
-
});
|