@taazkareem/clickup-mcp-server 0.2.0 → 0.2.2

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