@taazkareem/clickup-mcp-server 0.1.7 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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.teamId);
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,33 +75,270 @@ 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."
91
+ },
92
+ dueDate: {
93
+ type: "string",
94
+ description: "Due date of the task (Unix timestamp in milliseconds)"
95
+ }
96
+ },
97
+ required: ["name"]
98
+ }
99
+ },
100
+ {
101
+ name: "create_bulk_tasks",
102
+ description: "Create multiple tasks in a ClickUp list. Supports direct name-based lookup for lists - no need to know the list ID. -Tasks will use ClickUp default status if not specified. If the specified list doesn't exist, you can create it using create_list or create_list_in_folder.",
103
+ inputSchema: {
104
+ type: "object",
105
+ properties: {
106
+ listId: {
107
+ type: "string",
108
+ description: "ID of the list to create the tasks in (optional if using listName instead)"
109
+ },
110
+ listName: {
111
+ type: "string",
112
+ description: "Name of the list to create the tasks in - will automatically find the list by name (optional if using listId instead)"
113
+ },
114
+ tasks: {
115
+ type: "array",
116
+ description: "Array of tasks to create",
117
+ items: {
118
+ type: "object",
119
+ properties: {
120
+ name: {
121
+ type: "string",
122
+ description: "Name of the task"
123
+ },
124
+ description: {
125
+ type: "string",
126
+ description: "Plain text description for the task"
127
+ },
128
+ markdown_description: {
129
+ type: "string",
130
+ description: "Markdown formatted description for the task. If provided, this takes precedence over description"
131
+ },
132
+ status: {
133
+ type: "string",
134
+ description: "OPTIONAL: Override the default ClickUp status. In most cases, you should omit this to use ClickUp defaults"
135
+ },
136
+ priority: {
137
+ type: "number",
138
+ description: "Priority level (1-4), 1 is highest priority, 4 is lowest priority"
139
+ },
140
+ dueDate: {
141
+ type: "string",
142
+ description: "Due date (Unix timestamp in milliseconds)"
143
+ },
144
+ assignees: {
145
+ type: "array",
146
+ items: {
147
+ type: "number"
148
+ },
149
+ description: "Array of user IDs to assign to the task"
150
+ }
151
+ },
152
+ required: ["name"]
153
+ }
154
+ }
155
+ },
156
+ required: ["listId", "tasks"]
157
+ }
158
+ },
159
+ {
160
+ name: "create_list",
161
+ description: "Create a new list in a ClickUp space. Supports direct name-based lookup for spaces - no need to know the space ID. If the specified space doesn't exist, you can create it through the ClickUp web interface (space creation via API not supported).",
162
+ inputSchema: {
163
+ type: "object",
164
+ properties: {
165
+ spaceId: {
166
+ type: "string",
167
+ description: "ID of the space to create the list in (optional if using spaceName instead)"
168
+ },
169
+ spaceName: {
170
+ type: "string",
171
+ description: "Name of the space to create the list in - will automatically find the space by name (optional if using spaceId instead)"
172
+ },
173
+ name: {
174
+ type: "string",
175
+ description: "Name of the list"
176
+ },
177
+ content: {
178
+ type: "string",
179
+ description: "Description or content of the list"
137
180
  },
138
181
  dueDate: {
139
182
  type: "string",
140
- description: "Due date of the task (ISO string)"
183
+ description: "Due date for the list (ISO string)"
184
+ },
185
+ priority: {
186
+ type: "number",
187
+ description: "Priority of the list (1-4)"
188
+ },
189
+ assignee: {
190
+ type: "number",
191
+ description: "User ID to assign the list to"
192
+ },
193
+ status: {
194
+ type: "string",
195
+ description: "Status of the list"
141
196
  }
142
197
  },
143
- required: ["listId", "name"]
198
+ required: ["name"]
199
+ }
200
+ },
201
+ {
202
+ name: "create_folder",
203
+ description: "Create a new folder in a ClickUp space. Supports direct name-based lookup for spaces - no need to know the space ID. If the specified space doesn't exist, you can create it through the ClickUp web interface (space creation via API not supported).",
204
+ inputSchema: {
205
+ type: "object",
206
+ properties: {
207
+ spaceId: {
208
+ type: "string",
209
+ description: "ID of the space to create the folder in (optional if using spaceName instead)"
210
+ },
211
+ spaceName: {
212
+ type: "string",
213
+ description: "Name of the space to create the folder in - will automatically find the space by name (optional if using spaceId instead)"
214
+ },
215
+ name: {
216
+ type: "string",
217
+ description: "Name of the folder"
218
+ },
219
+ override_statuses: {
220
+ type: "boolean",
221
+ description: "Whether to override space statuses"
222
+ }
223
+ },
224
+ required: ["name"]
225
+ }
226
+ },
227
+ {
228
+ name: "create_list_in_folder",
229
+ description: "Create a new list in a ClickUp folder. Supports direct name-based lookup for folders and spaces - no need to know IDs. If the specified folder doesn't exist, you can create it using create_folder. If the space doesn't exist, it must be created through the ClickUp web interface.",
230
+ inputSchema: {
231
+ type: "object",
232
+ properties: {
233
+ folderId: {
234
+ type: "string",
235
+ description: "ID of the folder to create the list in (optional if using folderName instead)"
236
+ },
237
+ folderName: {
238
+ type: "string",
239
+ description: "Name of the folder to create the list in - will automatically find the folder by name (optional if using folderId instead)"
240
+ },
241
+ spaceId: {
242
+ type: "string",
243
+ description: "ID of the space containing the folder (optional if using spaceName instead)"
244
+ },
245
+ spaceName: {
246
+ type: "string",
247
+ description: "Name of the space containing the folder - will automatically find the space by name (optional if using spaceId instead)"
248
+ },
249
+ name: {
250
+ type: "string",
251
+ description: "Name of the list"
252
+ },
253
+ content: {
254
+ type: "string",
255
+ description: "Description or content of the list"
256
+ },
257
+ status: {
258
+ type: "string",
259
+ description: "Status of the list"
260
+ }
261
+ },
262
+ required: ["name"]
263
+ }
264
+ },
265
+ {
266
+ name: "move_task",
267
+ description: "Move a task to a different list. Supports direct name-based lookup for lists and tasks - no need to know IDs.",
268
+ inputSchema: {
269
+ type: "object",
270
+ properties: {
271
+ taskId: {
272
+ type: "string",
273
+ description: "ID of the task to move (optional if using taskName instead)"
274
+ },
275
+ taskName: {
276
+ type: "string",
277
+ description: "Name of the task to move - will automatically find the task by name (optional if using taskId instead)"
278
+ },
279
+ sourceListName: {
280
+ type: "string",
281
+ description: "Optional: Name of the list to narrow down task search"
282
+ },
283
+ listId: {
284
+ type: "string",
285
+ description: "ID of the destination list (optional if using listName instead)"
286
+ },
287
+ listName: {
288
+ type: "string",
289
+ description: "Name of the destination list - will automatically find the list by name (optional if using listId instead)"
290
+ }
291
+ },
292
+ required: []
293
+ }
294
+ },
295
+ {
296
+ name: "duplicate_task",
297
+ description: "Duplicate a task to a list. Supports direct name-based lookup for lists - no need to know the list ID. If the destination list doesn't exist, you can create it using create_list or create_list_in_folder.",
298
+ inputSchema: {
299
+ type: "object",
300
+ properties: {
301
+ taskId: {
302
+ type: "string",
303
+ description: "ID of the task to duplicate (optional if using taskName instead)"
304
+ },
305
+ taskName: {
306
+ type: "string",
307
+ description: "Name of the task to duplicate - will automatically find the task by name (optional if using taskId instead)"
308
+ },
309
+ sourceListName: {
310
+ type: "string",
311
+ description: "Optional: Name of the list to narrow down task search"
312
+ },
313
+ listId: {
314
+ type: "string",
315
+ description: "ID of the list to create the duplicate in (optional if using listName instead)"
316
+ },
317
+ listName: {
318
+ type: "string",
319
+ description: "Name of the list to create the duplicate in - will automatically find the list by name (optional if using listId instead)"
320
+ }
321
+ },
322
+ required: []
144
323
  }
145
324
  },
146
325
  {
147
326
  name: "update_task",
148
- description: "Update an existing task in ClickUp",
327
+ description: "Update an existing task in ClickUp. Supports direct name-based lookup for tasks - no need to know the task ID.",
149
328
  inputSchema: {
150
329
  type: "object",
151
330
  properties: {
152
331
  taskId: {
153
332
  type: "string",
154
- description: "ID of the task to update"
333
+ description: "ID of the task to update (optional if using taskName instead)"
334
+ },
335
+ taskName: {
336
+ type: "string",
337
+ description: "Name of the task to update - will automatically find the task by name (optional if using taskId instead)"
338
+ },
339
+ listName: {
340
+ type: "string",
341
+ description: "Optional: Name of the list to narrow down task search"
155
342
  },
156
343
  name: {
157
344
  type: "string",
@@ -174,6 +361,122 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
174
361
  description: "New due date of the task (ISO string)"
175
362
  }
176
363
  },
364
+ required: []
365
+ }
366
+ },
367
+ {
368
+ name: "get_tasks",
369
+ description: "Get tasks from a ClickUp list with optional filters. Supports direct name-based lookup for lists - no need to know the list ID. If the list doesn't exist, you can create it using create_list or create_list_in_folder.",
370
+ inputSchema: {
371
+ type: "object",
372
+ properties: {
373
+ listId: {
374
+ type: "string",
375
+ description: "ID of the list to get tasks from (optional if using listName instead)"
376
+ },
377
+ listName: {
378
+ type: "string",
379
+ description: "Name of the list to get tasks from - will automatically find the list by name (optional if using listId instead)"
380
+ },
381
+ archived: {
382
+ type: "boolean",
383
+ description: "Include archived tasks"
384
+ },
385
+ page: {
386
+ type: "number",
387
+ description: "Page number for pagination"
388
+ },
389
+ order_by: {
390
+ type: "string",
391
+ description: "Field to order tasks by"
392
+ },
393
+ reverse: {
394
+ type: "boolean",
395
+ description: "Reverse the order of tasks"
396
+ },
397
+ subtasks: {
398
+ type: "boolean",
399
+ description: "Include subtasks"
400
+ },
401
+ statuses: {
402
+ type: "array",
403
+ items: { type: "string" },
404
+ description: "Filter tasks by status"
405
+ },
406
+ include_closed: {
407
+ type: "boolean",
408
+ description: "Include closed tasks"
409
+ },
410
+ assignees: {
411
+ type: "array",
412
+ items: { type: "string" },
413
+ description: "Filter tasks by assignee IDs"
414
+ },
415
+ due_date_gt: {
416
+ type: "number",
417
+ description: "Filter tasks due after this timestamp"
418
+ },
419
+ due_date_lt: {
420
+ type: "number",
421
+ description: "Filter tasks due before this timestamp"
422
+ },
423
+ date_created_gt: {
424
+ type: "number",
425
+ description: "Filter tasks created after this timestamp"
426
+ },
427
+ date_created_lt: {
428
+ type: "number",
429
+ description: "Filter tasks created before this timestamp"
430
+ },
431
+ date_updated_gt: {
432
+ type: "number",
433
+ description: "Filter tasks updated after this timestamp"
434
+ },
435
+ date_updated_lt: {
436
+ type: "number",
437
+ description: "Filter tasks updated before this timestamp"
438
+ },
439
+ custom_fields: {
440
+ type: "object",
441
+ description: "Filter tasks by custom field values"
442
+ }
443
+ },
444
+ required: []
445
+ }
446
+ },
447
+ {
448
+ name: "get_task",
449
+ description: "Get detailed information about a specific ClickUp task, including attachments. Supports direct name-based lookup for tasks.",
450
+ inputSchema: {
451
+ type: "object",
452
+ properties: {
453
+ taskId: {
454
+ type: "string",
455
+ description: "ID of the task to retrieve (optional if using taskName instead)"
456
+ },
457
+ taskName: {
458
+ type: "string",
459
+ description: "Name of the task to retrieve - will automatically find the task by name (optional if using taskId instead)"
460
+ },
461
+ listName: {
462
+ type: "string",
463
+ description: "Optional: Name of the list to narrow down task search"
464
+ }
465
+ },
466
+ required: []
467
+ }
468
+ },
469
+ {
470
+ name: "delete_task",
471
+ description: "Delete a task from your workspace. This action cannot be undone.",
472
+ inputSchema: {
473
+ type: "object",
474
+ properties: {
475
+ taskId: {
476
+ type: "string",
477
+ description: "ID of the task to delete"
478
+ }
479
+ },
177
480
  required: ["taskId"]
178
481
  }
179
482
  }
@@ -187,43 +490,61 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
187
490
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
188
491
  try {
189
492
  switch (request.params.name) {
190
- case "list_spaces": {
191
- const spaces = await clickup.getSpaces(config.teamId);
192
- const allLists = await clickup.getAllLists(config.teamId);
193
- let output = "Available Spaces and Lists:\n\n";
194
- for (const space of spaces) {
195
- output += `Space: ${space.name} (ID: ${space.id})\n`;
196
- const spaceLists = allLists.filter(list => list.space.id === space.id);
197
- for (const list of spaceLists) {
198
- const { statuses } = await clickup.getTasks(list.id);
199
- output += ` └─ List: ${list.name} (ID: ${list.id})\n`;
200
- output += ` Available Statuses: ${statuses.join(', ')}\n`;
201
- }
202
- output += "\n";
203
- }
204
- // Add lists without spaces at the end
205
- const listsWithoutSpace = allLists.filter(list => !list.space);
206
- if (listsWithoutSpace.length > 0) {
207
- output += "Lists without assigned spaces:\n";
208
- for (const list of listsWithoutSpace) {
209
- const { statuses } = await clickup.getTasks(list.id);
210
- output += ` └─ List: ${list.name} (ID: ${list.id})\n`;
211
- output += ` Available Statuses: ${statuses.join(', ')}\n`;
212
- }
213
- }
493
+ case "get_workspace_hierarchy": {
494
+ const hierarchy = await clickup.getWorkspaceHierarchy();
214
495
  return {
215
496
  content: [{
216
497
  type: "text",
217
- text: 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)
218
527
  }]
219
528
  };
220
529
  }
221
530
  case "create_task": {
222
531
  const args = request.params.arguments;
223
- if (!args.listId || !args.name) {
224
- 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;
225
546
  }
226
- const { listId, ...taskData } = args;
547
+ const { listId: _, listName: __, ...taskData } = args;
227
548
  const task = await clickup.createTask(listId, taskData);
228
549
  return {
229
550
  content: [{
@@ -232,12 +553,192 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
232
553
  }]
233
554
  };
234
555
  }
556
+ case "create_bulk_tasks": {
557
+ const args = request.params.arguments;
558
+ if (!args.listId && !args.listName) {
559
+ throw new Error("Either listId or listName is required");
560
+ }
561
+ if (!args.tasks || args.tasks.length === 0) {
562
+ throw new Error("tasks array is required and must not be empty");
563
+ }
564
+ let listId = args.listId;
565
+ if (!listId && args.listName) {
566
+ const result = await clickup.findListIDByName(args.listName);
567
+ if (!result) {
568
+ throw new Error(`List with name "${args.listName}" not found`);
569
+ }
570
+ listId = result.id;
571
+ }
572
+ const { listId: _, listName: __, tasks } = args;
573
+ const createdTasks = await clickup.createBulkTasks(listId, { tasks });
574
+ return {
575
+ content: [{
576
+ type: "text",
577
+ text: `Created ${createdTasks.length} tasks`
578
+ }]
579
+ };
580
+ }
581
+ case "create_list": {
582
+ const args = request.params.arguments;
583
+ if (!args.name) {
584
+ throw new Error("name is required");
585
+ }
586
+ let spaceId = args.spaceId;
587
+ if (!spaceId && args.spaceName) {
588
+ const foundId = await clickup.findSpaceIDByName(args.spaceName);
589
+ if (!foundId) {
590
+ throw new Error(`Space with name "${args.spaceName}" not found`);
591
+ }
592
+ spaceId = foundId;
593
+ }
594
+ if (!spaceId) {
595
+ throw new Error("Either spaceId or spaceName must be provided");
596
+ }
597
+ const { spaceId: _, spaceName: __, ...listData } = args;
598
+ const list = await clickup.createList(spaceId, listData);
599
+ return {
600
+ content: [{
601
+ type: "text",
602
+ text: `Created list ${list.id}: ${list.name}`
603
+ }]
604
+ };
605
+ }
606
+ case "create_folder": {
607
+ const args = request.params.arguments;
608
+ if (!args.name) {
609
+ throw new Error("name is required");
610
+ }
611
+ let spaceId = args.spaceId;
612
+ if (!spaceId && args.spaceName) {
613
+ const foundId = await clickup.findSpaceIDByName(args.spaceName);
614
+ if (!foundId) {
615
+ throw new Error(`Space with name "${args.spaceName}" not found`);
616
+ }
617
+ spaceId = foundId;
618
+ }
619
+ if (!spaceId) {
620
+ throw new Error("Either spaceId or spaceName must be provided");
621
+ }
622
+ const { spaceId: _, spaceName: __, ...folderData } = args;
623
+ const folder = await clickup.createFolder(spaceId, folderData);
624
+ return {
625
+ content: [{
626
+ type: "text",
627
+ text: `Created folder ${folder.id}: ${folder.name}`
628
+ }]
629
+ };
630
+ }
631
+ case "create_list_in_folder": {
632
+ const args = request.params.arguments;
633
+ if (!args.name) {
634
+ throw new Error("name is required");
635
+ }
636
+ let folderId = args.folderId;
637
+ if (!folderId && args.folderName) {
638
+ const result = await clickup.findFolderIDByName(args.folderName);
639
+ if (!result) {
640
+ throw new Error(`Folder with name "${args.folderName}" not found`);
641
+ }
642
+ folderId = result.id;
643
+ }
644
+ if (!folderId) {
645
+ throw new Error("Either folderId or folderName must be provided");
646
+ }
647
+ const { folderId: _, folderName: __, spaceId: ___, spaceName: ____, ...listData } = args;
648
+ const list = await clickup.createListInFolder(folderId, listData);
649
+ return {
650
+ content: [{
651
+ type: "text",
652
+ text: `Created list ${list.id}: ${list.name} in folder`
653
+ }]
654
+ };
655
+ }
656
+ case "move_task": {
657
+ const args = request.params.arguments;
658
+ if (!args.taskId && !args.taskName) {
659
+ throw new Error("Either taskId or taskName is required");
660
+ }
661
+ if (!args.listId && !args.listName) {
662
+ throw new Error("Either listId or listName is required");
663
+ }
664
+ let taskId = args.taskId;
665
+ if (!taskId && args.taskName) {
666
+ const result = await clickup.findTaskByName(args.taskName, undefined, args.sourceListName);
667
+ if (!result) {
668
+ throw new Error(`Task with name "${args.taskName}" not found${args.sourceListName ? ` in list "${args.sourceListName}"` : ''}`);
669
+ }
670
+ taskId = result.id;
671
+ }
672
+ let listId = args.listId;
673
+ if (!listId && args.listName) {
674
+ const result = await clickup.findListIDByName(args.listName);
675
+ if (!result) {
676
+ throw new Error(`List with name "${args.listName}" not found`);
677
+ }
678
+ listId = result.id;
679
+ }
680
+ // Get the original task details for the response message
681
+ const originalTask = await clickup.getTask(taskId);
682
+ const newTask = await clickup.moveTask(taskId, listId);
683
+ return {
684
+ content: [{
685
+ type: "text",
686
+ text: `Moved task "${originalTask.name}" from "${originalTask.list.name}" to "${newTask.list.name}"`
687
+ }]
688
+ };
689
+ }
690
+ case "duplicate_task": {
691
+ const args = request.params.arguments;
692
+ // Require either taskId or taskName
693
+ if (!args.taskId && !args.taskName) {
694
+ throw new Error("Either taskId or taskName is required");
695
+ }
696
+ // Require either listId or listName
697
+ if (!args.listId && !args.listName) {
698
+ throw new Error("Either listId or listName is required");
699
+ }
700
+ // Get taskId from taskName if needed
701
+ let taskId = args.taskId;
702
+ if (!taskId && args.taskName) {
703
+ const result = await clickup.findTaskByName(args.taskName, undefined, args.sourceListName);
704
+ if (!result) {
705
+ throw new Error(`Task with name "${args.taskName}" not found${args.sourceListName ? ` in list "${args.sourceListName}"` : ''}`);
706
+ }
707
+ taskId = result.id;
708
+ }
709
+ // Get listId from listName if needed
710
+ let listId = args.listId;
711
+ if (!listId && args.listName) {
712
+ const result = await clickup.findListIDByName(args.listName);
713
+ if (!result) {
714
+ throw new Error(`List with name "${args.listName}" not found`);
715
+ }
716
+ listId = result.id;
717
+ }
718
+ // Get the original task details for the response message
719
+ const originalTask = await clickup.getTask(taskId);
720
+ const newTask = await clickup.duplicateTask(taskId, listId);
721
+ return {
722
+ content: [{
723
+ type: "text",
724
+ text: `Duplicated task "${originalTask.name}" from "${originalTask.list.name}" to "${newTask.list.name}"`
725
+ }]
726
+ };
727
+ }
235
728
  case "update_task": {
236
729
  const args = request.params.arguments;
237
- if (!args.taskId) {
238
- throw new Error("taskId is required");
730
+ if (!args.taskId && !args.taskName) {
731
+ throw new Error("Either taskId or taskName is required");
732
+ }
733
+ let taskId = args.taskId;
734
+ if (!taskId && args.taskName) {
735
+ const result = await clickup.findTaskByName(args.taskName, undefined, args.listName);
736
+ if (!result) {
737
+ throw new Error(`Task with name "${args.taskName}" not found${args.listName ? ` in list "${args.listName}"` : ''}`);
738
+ }
739
+ taskId = result.id;
239
740
  }
240
- const { taskId, ...updateData } = args;
741
+ const { taskId: _, taskName: __, listName: ___, ...updateData } = args;
241
742
  const task = await clickup.updateTask(taskId, updateData);
242
743
  return {
243
744
  content: [{
@@ -246,13 +747,83 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
246
747
  }]
247
748
  };
248
749
  }
750
+ case "get_tasks": {
751
+ const args = request.params.arguments;
752
+ if (!args.listId && !args.listName) {
753
+ throw new Error("Either listId or listName is required");
754
+ }
755
+ let listId = args.listId;
756
+ if (!listId && args.listName) {
757
+ const result = await clickup.findListIDByName(args.listName);
758
+ if (!result) {
759
+ throw new Error(`List with name "${args.listName}" not found`);
760
+ }
761
+ listId = result.id;
762
+ }
763
+ // Remove listId and listName from filters
764
+ const { listId: _, listName: __, ...filters } = args;
765
+ const { tasks, statuses } = await clickup.getTasks(listId, filters);
766
+ return {
767
+ content: [{
768
+ type: "text",
769
+ text: JSON.stringify({ tasks, available_statuses: statuses }, null, 2)
770
+ }]
771
+ };
772
+ }
773
+ case "get_task": {
774
+ const args = request.params.arguments;
775
+ if (!args.taskId && !args.taskName) {
776
+ throw new Error("Either taskId or taskName is required");
777
+ }
778
+ let taskId = args.taskId;
779
+ if (!taskId && args.taskName) {
780
+ const result = await clickup.findTaskByName(args.taskName, undefined, args.listName);
781
+ if (!result) {
782
+ throw new Error(`Task with name "${args.taskName}" not found${args.listName ? ` in list "${args.listName}"` : ''}`);
783
+ }
784
+ taskId = result.id;
785
+ }
786
+ const task = await clickup.getTask(taskId);
787
+ return {
788
+ content: [{
789
+ type: "text",
790
+ text: JSON.stringify(task, null, 2)
791
+ }]
792
+ };
793
+ }
794
+ case "delete_task": {
795
+ const args = request.params.arguments;
796
+ if (!args.taskId && !args.taskName) {
797
+ throw new Error("Either taskId or taskName is required");
798
+ }
799
+ let taskId = args.taskId;
800
+ if (!taskId && args.taskName) {
801
+ const result = await clickup.findTaskByName(args.taskName, undefined, args.listName);
802
+ if (!result) {
803
+ throw new Error(`Task with name "${args.taskName}" not found${args.listName ? ` in list "${args.listName}"` : ''}`);
804
+ }
805
+ taskId = result.id;
806
+ }
807
+ await clickup.deleteTask(taskId);
808
+ return {
809
+ content: [{
810
+ type: "text",
811
+ text: `Successfully deleted task ${args.taskName || taskId}`
812
+ }]
813
+ };
814
+ }
249
815
  default:
250
- throw new Error("Unknown tool");
816
+ throw new Error(`Unknown tool: ${request.params.name}`);
251
817
  }
252
818
  }
253
819
  catch (error) {
254
- console.error('Error handling tool call:', error);
255
- 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
+ };
256
827
  }
257
828
  });
258
829
  /**