@taazkareem/clickup-mcp-server 0.4.56 → 0.4.57

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
@@ -109,7 +109,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
109
109
  description: "Due date of the task (Unix timestamp in milliseconds). Convert dates to this format before submitting."
110
110
  }
111
111
  },
112
- required: ["name"]
112
+ required: []
113
113
  }
114
114
  },
115
115
  {
@@ -164,11 +164,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
164
164
  description: "Array of user IDs to assign to the task"
165
165
  }
166
166
  },
167
- required: ["name"]
167
+ required: []
168
168
  }
169
169
  }
170
170
  },
171
- required: ["listId", "tasks"]
171
+ required: []
172
172
  }
173
173
  },
174
174
  {
@@ -210,7 +210,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
210
210
  description: "Status of the list"
211
211
  }
212
212
  },
213
- required: ["name"]
213
+ required: []
214
214
  }
215
215
  },
216
216
  {
@@ -236,7 +236,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
236
236
  description: "Whether to override space statuses with folder-specific statuses"
237
237
  }
238
238
  },
239
- required: ["name"]
239
+ required: []
240
240
  }
241
241
  },
242
242
  {
@@ -274,7 +274,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
274
274
  description: "Status of the list (uses folder default if not specified)"
275
275
  }
276
276
  },
277
- required: ["name"]
277
+ required: []
278
278
  }
279
279
  },
280
280
  {
@@ -304,7 +304,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
304
304
  description: "Name of the destination list - will automatically find the list by name (optional if using listId instead). Only use this if you don't already have the list ID from previous responses."
305
305
  }
306
306
  },
307
- required: ["taskName", "listName"]
307
+ required: []
308
308
  }
309
309
  },
310
310
  {
@@ -334,7 +334,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
334
334
  description: "Name of the list to create the duplicate in - will automatically find the list by name (optional if using listId instead). Only use this if you don't already have the list ID from previous responses."
335
335
  }
336
336
  },
337
- required: ["taskName", "listName"]
337
+ required: []
338
338
  }
339
339
  },
340
340
  {
@@ -363,20 +363,22 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
363
363
  type: "string",
364
364
  description: "New plain text description for the task"
365
365
  },
366
+ markdown_description: {
367
+ type: "string",
368
+ description: "New markdown formatted description for the task. If provided, this takes precedence over description"
369
+ },
366
370
  status: {
367
371
  type: "string",
368
372
  description: "New status for the task (must be a valid status in the task's list)"
369
373
  },
370
374
  priority: {
371
- type: "number",
372
- description: "New priority for the task (1-4), where 1 is urgent/highest priority and 4 is lowest priority"
373
- },
374
- dueDate: {
375
- type: "string",
376
- description: "New due date for the task (Unix timestamp in milliseconds). Convert dates to this format before submitting."
375
+ type: ["number", "null"],
376
+ enum: [1, 2, 3, 4, null],
377
+ description: "New priority for the task (1-4 or null), where 1 is urgent/highest priority and 4 is lowest priority. Set to null to clear priority.",
378
+ optional: true
377
379
  }
378
380
  },
379
- required: ["taskName"]
381
+ required: []
380
382
  }
381
383
  },
382
384
  {
@@ -456,7 +458,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
456
458
  description: "Object with custom field IDs as keys and desired values for filtering"
457
459
  }
458
460
  },
459
- required: ["listName"]
461
+ required: []
460
462
  }
461
463
  },
462
464
  {
@@ -478,7 +480,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
478
480
  description: "Optional: Name of the list to narrow down task search when multiple tasks have the same name"
479
481
  }
480
482
  },
481
- required: ["taskName"]
483
+ required: []
482
484
  }
483
485
  },
484
486
  {
@@ -500,7 +502,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
500
502
  description: "Optional: Name of the list to narrow down task search when multiple tasks have the same name"
501
503
  }
502
504
  },
503
- required: ["taskId"]
505
+ required: []
504
506
  }
505
507
  },
506
508
  {
@@ -730,12 +732,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
730
732
  }
731
733
  case "create_bulk_tasks": {
732
734
  const args = request.params.arguments;
733
- if (!args.listId && !args.listName) {
734
- throw new Error("Either listId or listName is required");
735
- }
736
- if (!args.tasks || args.tasks.length === 0) {
735
+ // First validate tasks array
736
+ if (!args.tasks || !Array.isArray(args.tasks) || args.tasks.length === 0) {
737
737
  throw new Error("tasks array is required and must not be empty");
738
738
  }
739
+ // Validate each task has required fields
740
+ args.tasks.forEach((task, index) => {
741
+ if (!task.name) {
742
+ throw new Error(`Task at index ${index} is missing required field 'name'`);
743
+ }
744
+ });
745
+ // Get listId from name if needed
739
746
  let listId = args.listId;
740
747
  if (!listId && args.listName) {
741
748
  const result = await clickup.findListIDByName(args.listName);
@@ -744,12 +751,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
744
751
  }
745
752
  listId = result.id;
746
753
  }
754
+ // Now validate we have a listId
755
+ if (!listId) {
756
+ throw new Error("Either listId or listName must be provided");
757
+ }
747
758
  const { listId: _, listName: __, tasks } = args;
748
759
  const createdTasks = await clickup.createBulkTasks(listId, { tasks });
749
760
  return {
750
761
  content: [{
751
762
  type: "text",
752
- text: `Created ${createdTasks.length} tasks`
763
+ text: JSON.stringify({
764
+ message: `Created ${createdTasks.length} tasks`,
765
+ tasks: createdTasks.map(task => ({
766
+ id: task.id,
767
+ name: task.name,
768
+ url: task.url
769
+ }))
770
+ }, null, 2)
753
771
  }]
754
772
  };
755
773
  }
@@ -902,9 +920,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
902
920
  }
903
921
  case "update_task": {
904
922
  const args = request.params.arguments;
923
+ // Require either taskId or taskName
905
924
  if (!args.taskId && !args.taskName) {
906
925
  throw new Error("Either taskId or taskName is required");
907
926
  }
927
+ // Get taskId from taskName if needed
908
928
  let taskId = args.taskId;
909
929
  if (!taskId && args.taskName) {
910
930
  const result = await clickup.findTaskByName(args.taskName, undefined, args.listName);
@@ -913,7 +933,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
913
933
  }
914
934
  taskId = result.id;
915
935
  }
936
+ // Remove helper fields before updating
916
937
  const { taskId: _, taskName: __, listName: ___, ...updateData } = args;
938
+ // Ensure priority is properly handled
939
+ if (updateData.priority !== undefined && updateData.priority !== null) {
940
+ const priority = Number(updateData.priority);
941
+ if (isNaN(priority) || ![1, 2, 3, 4].includes(priority)) {
942
+ throw new Error("Priority must be a number between 1 and 4, or null to clear priority");
943
+ }
944
+ updateData.priority = priority;
945
+ }
917
946
  const task = await clickup.updateTask(taskId, updateData);
918
947
  return {
919
948
  content: [{
@@ -59,16 +59,7 @@ export class ClickUpService {
59
59
  */
60
60
  async makeRequest(requestFn) {
61
61
  await this.checkRateLimit();
62
- try {
63
- return await requestFn();
64
- }
65
- catch (error) {
66
- if (error.response?.status === 429) {
67
- // Let the interceptor handle it
68
- throw error;
69
- }
70
- throw error;
71
- }
62
+ return await requestFn();
72
63
  }
73
64
  /**
74
65
  * Initializes the ClickUpService singleton instance.
@@ -183,10 +174,15 @@ export class ClickUpService {
183
174
  async createTask(listId, data) {
184
175
  return this.makeRequest(async () => {
185
176
  const taskData = { ...data };
186
- if (taskData.description && /[#*`\-\[\]>]/.test(taskData.description)) {
187
- taskData.markdown_description = taskData.description;
177
+ // If markdown_description is provided, it takes precedence
178
+ if (taskData.markdown_description) {
179
+ // Ensure we don't send both to avoid confusion
188
180
  delete taskData.description;
189
181
  }
182
+ else if (taskData.description) {
183
+ // Only use description as-is, don't auto-convert to markdown
184
+ taskData.description = taskData.description.trim();
185
+ }
190
186
  const response = await this.client.post(`/list/${listId}/task`, taskData);
191
187
  return response.data;
192
188
  });
@@ -200,10 +196,15 @@ export class ClickUpService {
200
196
  for (const taskData of data.tasks) {
201
197
  await this.makeRequest(async () => {
202
198
  const processedTask = { ...taskData };
203
- if (processedTask.description && /[#*`\-\[\]>]/.test(processedTask.description)) {
204
- processedTask.markdown_description = processedTask.description;
199
+ // If markdown_description is provided, it takes precedence
200
+ if (processedTask.markdown_description) {
201
+ // Ensure we don't send both to avoid confusion
205
202
  delete processedTask.description;
206
203
  }
204
+ else if (processedTask.description) {
205
+ // Only use description as-is, don't auto-convert to markdown
206
+ processedTask.description = processedTask.description.trim();
207
+ }
207
208
  const response = await this.client.post(`/list/${listId}/task`, processedTask);
208
209
  createdTasks.push(response.data);
209
210
  });
@@ -216,7 +217,21 @@ export class ClickUpService {
216
217
  */
217
218
  async updateTask(taskId, data) {
218
219
  return this.makeRequest(async () => {
219
- const response = await this.client.put(`/task/${taskId}`, data);
220
+ const updateData = { ...data };
221
+ // If markdown_description is provided, it takes precedence
222
+ if (updateData.markdown_description) {
223
+ // Ensure we don't send both to avoid confusion
224
+ delete updateData.description;
225
+ }
226
+ else if (updateData.description) {
227
+ // Only use description as-is, don't auto-convert to markdown
228
+ updateData.description = updateData.description.trim();
229
+ }
230
+ // Handle null priority explicitly
231
+ if (updateData.priority === null) {
232
+ updateData.priority = null;
233
+ }
234
+ const response = await this.client.put(`/task/${taskId}`, updateData);
220
235
  return response.data;
221
236
  });
222
237
  }
@@ -730,11 +745,4 @@ export class ClickUpService {
730
745
  : `${task.space.name} > ${task.list.name} > ${task.name}`;
731
746
  return { id: task.id, path };
732
747
  }
733
- async getTaskStatuses(listId) {
734
- const response = await this.getTasks(listId);
735
- const statuses = [...new Set(response.tasks
736
- .filter((task) => task.status !== undefined)
737
- .map((task) => task.status.status))];
738
- return statuses;
739
- }
740
748
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taazkareem/clickup-mcp-server",
3
- "version": "0.4.56",
3
+ "version": "0.4.57",
4
4
  "description": "ClickUp MCP Server - Integrate ClickUp tasks with AI through Model Context Protocol",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
@@ -44,10 +44,8 @@
44
44
  "homepage": "https://github.com/taazkareem/clickup-mcp-server#readme",
45
45
  "dependencies": {
46
46
  "@modelcontextprotocol/sdk": "0.6.0",
47
- "@types/express": "^5.0.0",
48
47
  "axios": "^1.6.7",
49
- "dotenv": "^16.4.1",
50
- "express": "^4.21.2"
48
+ "dotenv": "^16.4.1"
51
49
  },
52
50
  "devDependencies": {
53
51
  "@types/node": "^20.11.16",