@taazkareem/clickup-mcp-server 0.4.48 → 0.4.51

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/build/index.js CHANGED
@@ -1,136 +1,54 @@
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
+ * 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.
21
+ */
2
22
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
23
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
24
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
25
  import { ClickUpService } from "./services/clickup.js";
6
26
  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
27
+ // Initialize ClickUp service
28
+ const clickup = ClickUpService.initialize(config.clickupApiKey, config.clickupTeamId);
29
+ /**
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.
32
+ */
16
33
  const server = new Server({
17
- name: "clickup",
34
+ name: "clickup-mcp-server",
18
35
  version: "0.1.0",
19
- description: "ClickUp MCP Server"
20
36
  }, {
21
37
  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
- // Initialize ClickUp service
76
- console.log('Initializing ClickUp service...');
77
- const clickup = ClickUpService.initialize(config.clickupApiKey);
78
- console.log('Creating MCP server...');
79
- console.log('MCP server created');
80
- /**
81
- * Handler for listing available ClickUp tasks as resources.
82
- */
83
- server.setRequestHandler(ListResourcesRequestSchema, async () => {
84
- console.log('Handling ListResources request');
85
- try {
86
- const { tasks, spaces } = await getAllTasks(clickup, config.teamId);
87
- return {
88
- resources: tasks.map(task => ({
89
- uri: `clickup://task/${task.id}`,
90
- mimeType: "application/json",
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
- }
38
+ tools: {},
39
+ prompts: {},
40
+ },
101
41
  });
102
42
  /**
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
- }
123
- });
124
- /**
125
- * Handler for listing available tools.
43
+ * Handler that lists available tools.
44
+ * Exposes tools for listing spaces, creating tasks, and updating tasks.
126
45
  */
127
46
  server.setRequestHandler(ListToolsRequestSchema, async () => {
128
- console.log('Handling ListTools request');
129
47
  return {
130
48
  tools: [
131
49
  {
132
- name: "workspace_hierarchy",
133
- description: "List complete hierarchy of the ClickUp workspace",
50
+ name: "get_workspace_hierarchy",
51
+ description: "Get the complete hierarchy of spaces, folders, and lists in the workspace. Important: If looking up information, first check chat history for space, folder, and list names or ID matches. If not found, use this tool to get necessary information.",
134
52
  inputSchema: {
135
53
  type: "object",
136
54
  properties: {},
@@ -139,37 +57,41 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
139
57
  },
140
58
  {
141
59
  name: "create_task",
142
- 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.",
143
61
  inputSchema: {
144
62
  type: "object",
145
63
  properties: {
146
64
  listId: {
147
65
  type: "string",
148
- description: "ID of the list to create the task in (optional if listName is provided)"
66
+ description: "ID of the list to create the task in (optional if using listName instead)"
149
67
  },
150
68
  listName: {
151
69
  type: "string",
152
- description: "Name of the list to create the task in (optional if listId is provided)"
70
+ description: "Name of the list to create the task in - will automatically find the list by name (optional if using listId instead)"
153
71
  },
154
72
  name: {
155
73
  type: "string",
156
- description: "Name of the task"
74
+ description: "Name of the task. Put a relevant emoji followed by a blank space before the name."
157
75
  },
158
76
  description: {
159
77
  type: "string",
160
- description: "Description of the task"
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"
161
83
  },
162
84
  status: {
163
85
  type: "string",
164
- description: "Status of the task"
86
+ description: "OPTIONAL: Override the default ClickUp status. In most cases, you should omit this to use ClickUp defaults"
165
87
  },
166
88
  priority: {
167
89
  type: "number",
168
- description: "Priority of the task (1-4)"
90
+ description: "Priority of the task (1-4), 1 is urgent/highest priority, 4 is lowest priority. Only set this if priority is explicitly mentioned in the user's request."
169
91
  },
170
92
  dueDate: {
171
93
  type: "string",
172
- description: "Due date of the task (ISO string)"
94
+ description: "Due date of the task (Unix timestamp in milliseconds)"
173
95
  }
174
96
  },
175
97
  required: ["name"]
@@ -177,17 +99,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
177
99
  },
178
100
  {
179
101
  name: "create_bulk_tasks",
180
- description: "Create multiple tasks in a ClickUp list",
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.",
181
103
  inputSchema: {
182
104
  type: "object",
183
105
  properties: {
184
106
  listId: {
185
107
  type: "string",
186
- description: "ID of the list to create the tasks in (optional if listName is provided)"
108
+ description: "ID of the list to create the tasks in (optional if using listName instead)"
187
109
  },
188
110
  listName: {
189
111
  type: "string",
190
- description: "Name of the list to create the tasks in (optional if listId is provided)"
112
+ description: "Name of the list to create the tasks in - will automatically find the list by name (optional if using listId instead)"
191
113
  },
192
114
  tasks: {
193
115
  type: "array",
@@ -201,19 +123,23 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
201
123
  },
202
124
  description: {
203
125
  type: "string",
204
- description: "Description of the task"
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"
205
131
  },
206
132
  status: {
207
133
  type: "string",
208
- description: "Status of the task"
134
+ description: "OPTIONAL: Override the default ClickUp status. In most cases, you should omit this to use ClickUp defaults"
209
135
  },
210
136
  priority: {
211
137
  type: "number",
212
- description: "Priority level (1-4)"
138
+ description: "Priority level (1-4), 1 is urgent/highest priority, 4 is lowest priority. Only set this if priority is explicitly mentioned in the user's request."
213
139
  },
214
140
  dueDate: {
215
141
  type: "string",
216
- description: "Due date (ISO string)"
142
+ description: "Due date (Unix timestamp in milliseconds)"
217
143
  },
218
144
  assignees: {
219
145
  type: "array",
@@ -227,22 +153,22 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
227
153
  }
228
154
  }
229
155
  },
230
- required: ["tasks"]
156
+ required: ["listId", "tasks"]
231
157
  }
232
158
  },
233
159
  {
234
160
  name: "create_list",
235
- 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).",
236
162
  inputSchema: {
237
163
  type: "object",
238
164
  properties: {
239
165
  spaceId: {
240
166
  type: "string",
241
- description: "ID of the space to create the list in (optional if spaceName is provided)"
167
+ description: "ID of the space to create the list in (optional if using spaceName instead)"
242
168
  },
243
169
  spaceName: {
244
170
  type: "string",
245
- description: "Name of the space to create the list in (optional if spaceId is provided)"
171
+ description: "Name of the space to create the list in - will automatically find the space by name (optional if using spaceId instead)"
246
172
  },
247
173
  name: {
248
174
  type: "string",
@@ -258,7 +184,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
258
184
  },
259
185
  priority: {
260
186
  type: "number",
261
- description: "Priority of the list (1-4)"
187
+ description: "Priority of the list (1-4). Only set this if priority is explicitly mentioned in the user's request."
262
188
  },
263
189
  assignee: {
264
190
  type: "number",
@@ -274,17 +200,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
274
200
  },
275
201
  {
276
202
  name: "create_folder",
277
- 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).",
278
204
  inputSchema: {
279
205
  type: "object",
280
206
  properties: {
281
207
  spaceId: {
282
208
  type: "string",
283
- description: "ID of the space to create the folder in (optional if spaceName is provided)"
209
+ description: "ID of the space to create the folder in (optional if using spaceName instead)"
284
210
  },
285
211
  spaceName: {
286
212
  type: "string",
287
- description: "Name of the space to create the folder in (optional if spaceId is provided)"
213
+ description: "Name of the space to create the folder in - will automatically find the space by name (optional if using spaceId instead)"
288
214
  },
289
215
  name: {
290
216
  type: "string",
@@ -300,25 +226,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
300
226
  },
301
227
  {
302
228
  name: "create_list_in_folder",
303
- 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.",
304
230
  inputSchema: {
305
231
  type: "object",
306
232
  properties: {
307
233
  folderId: {
308
234
  type: "string",
309
- description: "ID of the folder to create the list in (optional if folderName and spaceId/spaceName are provided)"
235
+ description: "ID of the folder to create the list in (optional if using folderName instead)"
310
236
  },
311
237
  folderName: {
312
238
  type: "string",
313
- 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)"
314
240
  },
315
241
  spaceId: {
316
242
  type: "string",
317
- description: "ID of the space containing the folder (required if using folderName)"
243
+ description: "ID of the space containing the folder (optional if using spaceName instead)"
318
244
  },
319
245
  spaceName: {
320
246
  type: "string",
321
- description: "Name of the space containing the folder (alternative to spaceId)"
247
+ description: "Name of the space containing the folder - will automatically find the space by name (optional if using spaceId instead)"
322
248
  },
323
249
  name: {
324
250
  type: "string",
@@ -338,57 +264,81 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
338
264
  },
339
265
  {
340
266
  name: "move_task",
341
- 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.",
342
268
  inputSchema: {
343
269
  type: "object",
344
270
  properties: {
345
271
  taskId: {
346
272
  type: "string",
347
- 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"
348
282
  },
349
283
  listId: {
350
284
  type: "string",
351
- description: "ID of the destination list (optional if listName is provided)"
285
+ description: "ID of the destination list (optional if using listName instead)"
352
286
  },
353
287
  listName: {
354
288
  type: "string",
355
- description: "Name of the destination list (optional if listId is provided)"
289
+ description: "Name of the destination list - will automatically find the list by name (optional if using listId instead)"
356
290
  }
357
291
  },
358
- required: ["taskId"]
292
+ required: []
359
293
  }
360
294
  },
361
295
  {
362
296
  name: "duplicate_task",
363
- 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.",
364
298
  inputSchema: {
365
299
  type: "object",
366
300
  properties: {
367
301
  taskId: {
368
302
  type: "string",
369
- 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"
370
312
  },
371
313
  listId: {
372
314
  type: "string",
373
- description: "ID of the destination list (optional if listName is provided)"
315
+ description: "ID of the list to create the duplicate in (optional if using listName instead)"
374
316
  },
375
317
  listName: {
376
318
  type: "string",
377
- description: "Name of the destination list (optional if listId is provided)"
319
+ description: "Name of the list to create the duplicate in - will automatically find the list by name (optional if using listId instead)"
378
320
  }
379
321
  },
380
- required: ["taskId"]
322
+ required: []
381
323
  }
382
324
  },
383
325
  {
384
326
  name: "update_task",
385
- 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.",
386
328
  inputSchema: {
387
329
  type: "object",
388
330
  properties: {
389
331
  taskId: {
390
332
  type: "string",
391
- 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"
392
342
  },
393
343
  name: {
394
344
  type: "string",
@@ -404,13 +354,129 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
404
354
  },
405
355
  priority: {
406
356
  type: "number",
407
- description: "New priority of the task (1-4)"
357
+ description: "New priority of the task (1-4). Only set this if priority is explicitly mentioned in the user's request."
408
358
  },
409
359
  dueDate: {
410
360
  type: "string",
411
361
  description: "New due date of the task (ISO string)"
412
362
  }
413
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
+ },
414
480
  required: ["taskId"]
415
481
  }
416
482
  }
@@ -418,23 +484,68 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
418
484
  };
419
485
  });
420
486
  /**
421
- * Handler for executing tools.
487
+ * Handler for the CallToolRequestSchema.
488
+ * Handles the execution of tools like listing spaces, creating tasks, and updating tasks.
422
489
  */
423
490
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
424
491
  try {
425
492
  switch (request.params.name) {
426
- case "workspace_hierarchy": {
427
- const output = await handleWorkspaceHierarchy(clickup, config.teamId);
493
+ case "get_workspace_hierarchy": {
494
+ const hierarchy = await clickup.getWorkspaceHierarchy();
428
495
  return {
429
496
  content: [{
430
497
  type: "text",
431
- text: output
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)
432
527
  }]
433
528
  };
434
529
  }
435
530
  case "create_task": {
436
531
  const args = request.params.arguments;
437
- const task = await handleCreateTask(clickup, config.teamId, args);
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;
546
+ }
547
+ const { listId: _, listName: __, ...taskData } = args;
548
+ const task = await clickup.createTask(listId, taskData);
438
549
  return {
439
550
  content: [{
440
551
  type: "text",
@@ -444,26 +555,26 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
444
555
  }
445
556
  case "create_bulk_tasks": {
446
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
+ }
447
564
  let listId = args.listId;
448
565
  if (!listId && args.listName) {
449
- const result = await clickup.findListByNameGlobally(config.teamId, args.listName);
566
+ const result = await clickup.findListIDByName(args.listName);
450
567
  if (!result) {
451
568
  throw new Error(`List with name "${args.listName}" not found`);
452
569
  }
453
- listId = result.list.id;
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");
570
+ listId = result.id;
460
571
  }
461
572
  const { listId: _, listName: __, tasks } = args;
462
573
  const createdTasks = await clickup.createBulkTasks(listId, { tasks });
463
574
  return {
464
575
  content: [{
465
576
  type: "text",
466
- text: `Created ${createdTasks.length} tasks:\n${createdTasks.map(task => `- ${task.id}: ${task.name}`).join('\n')}`
577
+ text: `Created ${createdTasks.length} tasks`
467
578
  }]
468
579
  };
469
580
  }
@@ -472,41 +583,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
472
583
  if (!args.name) {
473
584
  throw new Error("name is required");
474
585
  }
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
586
  let spaceId = args.spaceId;
499
587
  if (!spaceId && args.spaceName) {
500
- const space = await clickup.findSpaceByName(config.teamId, args.spaceName);
501
- if (!space) {
588
+ const foundId = await clickup.findSpaceIDByName(args.spaceName);
589
+ if (!foundId) {
502
590
  throw new Error(`Space with name "${args.spaceName}" not found`);
503
591
  }
504
- spaceId = space.id;
592
+ spaceId = foundId;
505
593
  }
506
594
  if (!spaceId) {
507
595
  throw new Error("Either spaceId or spaceName must be provided");
508
596
  }
509
- const { spaceId: _, spaceName: __, folderName: ___, folderId: ____, ...listData } = args;
597
+ const { spaceId: _, spaceName: __, ...listData } = args;
510
598
  const list = await clickup.createList(spaceId, listData);
511
599
  return {
512
600
  content: [{
@@ -522,11 +610,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
522
610
  }
523
611
  let spaceId = args.spaceId;
524
612
  if (!spaceId && args.spaceName) {
525
- const space = await clickup.findSpaceByName(config.teamId, args.spaceName);
526
- if (!space) {
613
+ const foundId = await clickup.findSpaceIDByName(args.spaceName);
614
+ if (!foundId) {
527
615
  throw new Error(`Space with name "${args.spaceName}" not found`);
528
616
  }
529
- spaceId = space.id;
617
+ spaceId = foundId;
530
618
  }
531
619
  if (!spaceId) {
532
620
  throw new Error("Either spaceId or spaceName must be provided");
@@ -547,94 +635,111 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
547
635
  }
548
636
  let folderId = args.folderId;
549
637
  if (!folderId && args.folderName) {
550
- const result = await clickup.findFolderByNameGlobally(config.teamId, args.folderName);
638
+ const result = await clickup.findFolderIDByName(args.folderName);
551
639
  if (!result) {
552
640
  throw new Error(`Folder with name "${args.folderName}" not found`);
553
641
  }
554
- folderId = result.folder.id;
642
+ folderId = result.id;
555
643
  }
556
644
  if (!folderId) {
557
- throw new Error("Either folderId or folderName is required");
645
+ throw new Error("Either folderId or folderName must be provided");
558
646
  }
559
- const listData = {
560
- name: args.name,
561
- content: args.content,
562
- status: args.status
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
+ }]
563
654
  };
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
655
  }
577
656
  case "move_task": {
578
657
  const args = request.params.arguments;
579
- if (!args.taskId) {
580
- throw new Error("taskId is required");
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;
581
671
  }
582
672
  let listId = args.listId;
583
673
  if (!listId && args.listName) {
584
- const result = await clickup.findListByNameGlobally(config.teamId, args.listName);
674
+ const result = await clickup.findListIDByName(args.listName);
585
675
  if (!result) {
586
676
  throw new Error(`List with name "${args.listName}" not found`);
587
677
  }
588
- listId = result.list.id;
589
- }
590
- if (!listId) {
591
- throw new Error("Either listId or listName is required");
678
+ listId = result.id;
592
679
  }
593
- const task = await clickup.moveTask(args.taskId, listId);
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);
594
683
  return {
595
684
  content: [{
596
685
  type: "text",
597
- text: `Moved task ${task.id} to list ${listId}`
686
+ text: `Moved task "${originalTask.name}" from "${originalTask.list.name}" to "${newTask.list.name}"`
598
687
  }]
599
688
  };
600
689
  }
601
690
  case "duplicate_task": {
602
691
  const args = request.params.arguments;
603
- if (!args.taskId) {
604
- throw new Error("taskId is required");
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;
605
708
  }
709
+ // Get listId from listName if needed
606
710
  let listId = args.listId;
607
711
  if (!listId && args.listName) {
608
- const result = await clickup.findListByNameGlobally(config.teamId, args.listName);
712
+ const result = await clickup.findListIDByName(args.listName);
609
713
  if (!result) {
610
714
  throw new Error(`List with name "${args.listName}" not found`);
611
715
  }
612
- listId = result.list.id;
716
+ listId = result.id;
613
717
  }
614
- if (!listId) {
615
- throw new Error("Either listId or listName is required");
616
- }
617
- const task = await clickup.duplicateTask(args.taskId, listId);
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);
618
721
  return {
619
722
  content: [{
620
723
  type: "text",
621
- text: `Duplicated task ${args.taskId} to new task ${task.id} in list ${listId}`
724
+ text: `Duplicated task "${originalTask.name}" from "${originalTask.list.name}" to "${newTask.list.name}"`
622
725
  }]
623
726
  };
624
727
  }
625
728
  case "update_task": {
626
729
  const args = request.params.arguments;
627
- if (!args.taskId) {
628
- throw new Error("taskId is required");
629
- }
630
- const dueDate = args.due_date ? new Date(args.due_date).getTime() : undefined;
631
- const task = await clickup.updateTask(args.taskId, {
632
- name: args.name,
633
- description: args.description,
634
- status: args.status,
635
- priority: args.priority,
636
- due_date: dueDate
637
- });
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;
740
+ }
741
+ const { taskId: _, taskName: __, listName: ___, ...updateData } = args;
742
+ const task = await clickup.updateTask(taskId, updateData);
638
743
  return {
639
744
  content: [{
640
745
  type: "text",
@@ -642,100 +747,222 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
642
747
  }]
643
748
  };
644
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
+ }
645
815
  default:
646
- throw new Error("Unknown tool");
816
+ throw new Error(`Unknown tool: ${request.params.name}`);
647
817
  }
648
818
  }
649
819
  catch (error) {
650
- console.error('Error executing tool:', error);
651
- throw error;
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
+ };
652
827
  }
653
828
  });
654
829
  /**
655
- * Handler for listing available prompts.
830
+ * Add handlers for listing and getting prompts.
831
+ * Prompts include summarizing tasks, analyzing priorities, and generating task descriptions.
656
832
  */
657
833
  server.setRequestHandler(ListPromptsRequestSchema, async () => {
658
834
  return {
659
835
  prompts: [
660
836
  {
661
837
  name: "summarize_tasks",
662
- description: "Summarize all ClickUp tasks"
838
+ description: "Summarize all tasks in a list",
663
839
  },
664
840
  {
665
- name: "analyze_task_priorities",
666
- description: "Analyze task priorities"
841
+ name: "analyze_priorities",
842
+ description: "Analyze task priorities and suggest optimizations",
667
843
  },
668
844
  {
669
845
  name: "generate_description",
670
- description: "Generate a detailed task description with objectives, success criteria, resources, and dependencies"
846
+ description: "Generate a detailed description for a task",
671
847
  }
672
848
  ]
673
849
  };
674
850
  });
675
- /**
676
- * Handler for getting a specific prompt.
677
- */
678
851
  server.setRequestHandler(GetPromptRequestSchema, async (request) => {
679
852
  try {
680
853
  switch (request.params.name) {
681
854
  case "summarize_tasks": {
682
- const output = await handleSummarizeTasks(clickup, config.teamId);
855
+ const spaces = await clickup.getSpaces(config.clickupTeamId);
856
+ const tasks = [];
857
+ // Gather all tasks
858
+ for (const space of spaces) {
859
+ const lists = await clickup.getLists(space.id);
860
+ for (const list of lists) {
861
+ const { tasks: listTasks } = await clickup.getTasks(list.id);
862
+ tasks.push(...listTasks.map((task) => ({
863
+ type: "resource",
864
+ resource: {
865
+ uri: `clickup://task/${task.id}`,
866
+ mimeType: "application/json",
867
+ text: JSON.stringify(task, null, 2)
868
+ }
869
+ })));
870
+ }
871
+ }
683
872
  return {
684
- content: [{
685
- type: "text",
686
- text: output
687
- }]
873
+ messages: [
874
+ {
875
+ role: "user",
876
+ content: {
877
+ type: "text",
878
+ text: "Please provide a summary of the following ClickUp tasks:"
879
+ }
880
+ },
881
+ ...tasks.map(task => ({
882
+ role: "user",
883
+ content: task
884
+ })),
885
+ {
886
+ role: "user",
887
+ content: {
888
+ type: "text",
889
+ 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"
890
+ }
891
+ }
892
+ ]
688
893
  };
689
894
  }
690
- case "analyze_task_priorities": {
691
- const output = await handleAnalyzeTaskPriorities(clickup, config.teamId);
895
+ case "analyze_priorities": {
896
+ const spaces = await clickup.getSpaces(config.clickupTeamId);
897
+ const tasks = [];
898
+ for (const space of spaces) {
899
+ const lists = await clickup.getLists(space.id);
900
+ for (const list of lists) {
901
+ const { tasks: listTasks } = await clickup.getTasks(list.id);
902
+ tasks.push(...listTasks.map((task) => ({
903
+ type: "resource",
904
+ resource: {
905
+ uri: `clickup://task/${task.id}`,
906
+ mimeType: "application/json",
907
+ text: JSON.stringify(task, null, 2)
908
+ }
909
+ })));
910
+ }
911
+ }
692
912
  return {
693
- content: [{
694
- type: "text",
695
- text: output
696
- }]
913
+ messages: [
914
+ {
915
+ role: "user",
916
+ content: {
917
+ type: "text",
918
+ text: "Analyze the priorities of the following ClickUp tasks:"
919
+ }
920
+ },
921
+ ...tasks.map(task => ({
922
+ role: "user",
923
+ content: task
924
+ })),
925
+ {
926
+ role: "user",
927
+ content: {
928
+ type: "text",
929
+ 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"
930
+ }
931
+ }
932
+ ]
697
933
  };
698
934
  }
699
935
  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
936
  return {
705
- content: [{
706
- type: "text",
707
- text: output
708
- }]
937
+ messages: [
938
+ {
939
+ role: "user",
940
+ content: {
941
+ type: "text",
942
+ 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."
943
+ }
944
+ }
945
+ ]
709
946
  };
710
947
  }
711
948
  default:
712
- throw new Error("Prompt not found");
949
+ throw new Error("Unknown prompt");
713
950
  }
714
951
  }
715
952
  catch (error) {
716
- console.error('Error getting prompt:', error);
953
+ console.error('Error handling prompt:', error);
717
954
  throw error;
718
955
  }
719
956
  });
720
- // Start the server
721
- console.log('Setting up transport...');
722
- const transport = new StdioServerTransport();
723
- // Connect the server to the transport
724
- console.log('Connecting server to transport...');
725
- server.connect(transport).catch(error => {
726
- console.error('Error connecting server to transport:', error);
957
+ /**
958
+ * Start the server using stdio transport.
959
+ * This allows the server to communicate via standard input/output streams.
960
+ */
961
+ async function main() {
962
+ const transport = new StdioServerTransport();
963
+ await server.connect(transport);
964
+ }
965
+ main().catch((error) => {
966
+ console.error("Server error:", error);
727
967
  process.exit(1);
728
968
  });
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
- });