@taazkareem/clickup-mcp-server 0.4.62 → 0.4.63

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.
@@ -8,7 +8,7 @@
8
8
  import { createClickUpServices } from '../services/clickup/index.js';
9
9
  import config from '../config.js';
10
10
  import { findListIDByName } from './list.js';
11
- import { parseDueDate } from './utils.js';
11
+ import { parseDueDate, formatDueDate } from './utils.js';
12
12
  // Initialize ClickUp services using the factory function
13
13
  const services = createClickUpServices({
14
14
  apiKey: config.clickupApiKey,
@@ -99,6 +99,7 @@ export const createTaskTool = {
99
99
  name: createdTask.name,
100
100
  url: createdTask.url,
101
101
  status: createdTask.status?.status || "New",
102
+ due_date: createdTask.due_date ? formatDueDate(Number(createdTask.due_date)) : undefined,
102
103
  list: createdTask.list.name,
103
104
  space: createdTask.space.name,
104
105
  folder: createdTask.folder?.name
@@ -211,7 +212,10 @@ export const updateTaskTool = {
211
212
  name: updatedTask.name,
212
213
  url: updatedTask.url,
213
214
  status: updatedTask.status?.status || "Unknown",
214
- updated: true
215
+ updated: true,
216
+ due_date: updatedTask.due_date ? formatDueDate(Number(updatedTask.due_date)) : undefined,
217
+ list: updatedTask.list.name,
218
+ folder: updatedTask.folder?.name
215
219
  }, null, 2)
216
220
  }]
217
221
  };
@@ -298,6 +302,7 @@ export const moveTaskTool = {
298
302
  name: movedTask.name,
299
303
  url: movedTask.url,
300
304
  status: movedTask.status?.status || "Unknown",
305
+ due_date: movedTask.due_date ? formatDueDate(Number(movedTask.due_date)) : undefined,
301
306
  list: movedTask.list.name,
302
307
  moved: true
303
308
  }, null, 2)
@@ -389,6 +394,7 @@ export const duplicateTaskTool = {
389
394
  name: task.name,
390
395
  url: task.url,
391
396
  duplicated: true,
397
+ due_date: task.due_date ? formatDueDate(Number(task.due_date)) : undefined,
392
398
  list: task.list.name,
393
399
  space: task.space.name,
394
400
  folder: task.folder?.name
@@ -457,7 +463,8 @@ export const getTaskTool = {
457
463
  description: task.description,
458
464
  status: task.status?.status || "Unknown",
459
465
  priority: task.priority,
460
- due_date: task.due_date,
466
+ due_date: task.due_date ? formatDueDate(Number(task.due_date)) : undefined,
467
+ due_date_raw: task.due_date, // Keep raw timestamp for reference if needed
461
468
  url: task.url,
462
469
  list: task.list.name,
463
470
  space: task.space.name,
@@ -543,21 +550,24 @@ export const getTasksTool = {
543
550
  };
544
551
  // Get tasks with filters
545
552
  const tasks = await taskService.getTasks(targetListId, filters);
553
+ // Format the tasks data to be more API friendly
554
+ const formattedTasks = tasks.map(task => ({
555
+ id: task.id,
556
+ name: task.name,
557
+ status: task.status?.status || 'Unknown',
558
+ url: task.url,
559
+ due_date: task.due_date ? formatDueDate(Number(task.due_date)) : undefined,
560
+ due_date_raw: task.due_date,
561
+ priority: task.priority?.priority,
562
+ assignees: task.assignees?.map(a => a.username) || []
563
+ }));
546
564
  // Format response
547
565
  return {
548
566
  content: [{
549
567
  type: "text",
550
568
  text: JSON.stringify({
551
- list_id: targetListId,
552
- task_count: tasks.length,
553
- tasks: tasks.map((task) => ({
554
- id: task.id,
555
- name: task.name,
556
- status: task.status?.status || "Unknown",
557
- priority: task.priority,
558
- due_date: task.due_date,
559
- url: task.url
560
- }))
569
+ total: tasks.length,
570
+ tasks: formattedTasks
561
571
  }, null, 2)
562
572
  }]
563
573
  };
@@ -974,6 +984,7 @@ export async function handleCreateTask(parameters) {
974
984
  name: task.name,
975
985
  url: task.url,
976
986
  status: task.status?.status || "New",
987
+ due_date: task.due_date ? formatDueDate(Number(task.due_date)) : undefined,
977
988
  list: task.list.name,
978
989
  space: task.space.name,
979
990
  folder: task.folder?.name
@@ -1041,8 +1052,8 @@ export async function handleUpdateTask(parameters) {
1041
1052
  url: task.url,
1042
1053
  status: task.status?.status || "Unknown",
1043
1054
  updated: true,
1055
+ due_date: task.due_date ? formatDueDate(Number(task.due_date)) : undefined,
1044
1056
  list: task.list.name,
1045
- space: task.space.name,
1046
1057
  folder: task.folder?.name
1047
1058
  }, null, 2)
1048
1059
  }]
@@ -1108,6 +1119,7 @@ export async function handleMoveTask(parameters) {
1108
1119
  name: task.name,
1109
1120
  url: task.url,
1110
1121
  moved: true,
1122
+ due_date: task.due_date ? formatDueDate(Number(task.due_date)) : undefined,
1111
1123
  list: task.list.name,
1112
1124
  space: task.space.name,
1113
1125
  folder: task.folder?.name
@@ -1171,6 +1183,7 @@ export async function handleDuplicateTask(parameters) {
1171
1183
  name: task.name,
1172
1184
  url: task.url,
1173
1185
  duplicated: true,
1186
+ due_date: task.due_date ? formatDueDate(Number(task.due_date)) : undefined,
1174
1187
  list: task.list.name,
1175
1188
  space: task.space.name,
1176
1189
  folder: task.folder?.name
@@ -1206,21 +1219,24 @@ export async function handleGetTasks(parameters) {
1206
1219
  };
1207
1220
  // Get tasks with filters
1208
1221
  const tasks = await taskService.getTasks(targetListId, filters);
1222
+ // Format the tasks data to be more API friendly
1223
+ const formattedTasks = tasks.map(task => ({
1224
+ id: task.id,
1225
+ name: task.name,
1226
+ status: task.status?.status || 'Unknown',
1227
+ url: task.url,
1228
+ due_date: task.due_date ? formatDueDate(Number(task.due_date)) : undefined,
1229
+ due_date_raw: task.due_date,
1230
+ priority: task.priority?.priority,
1231
+ assignees: task.assignees?.map(a => a.username) || []
1232
+ }));
1209
1233
  // Format response
1210
1234
  return {
1211
1235
  content: [{
1212
1236
  type: "text",
1213
1237
  text: JSON.stringify({
1214
- list_id: targetListId,
1215
- task_count: tasks.length,
1216
- tasks: tasks.map((task) => ({
1217
- id: task.id,
1218
- name: task.name,
1219
- status: task.status?.status || "Unknown",
1220
- priority: task.priority,
1221
- due_date: task.due_date,
1222
- url: task.url
1223
- }))
1238
+ total: tasks.length,
1239
+ tasks: formattedTasks
1224
1240
  }, null, 2)
1225
1241
  }]
1226
1242
  };
@@ -1260,7 +1276,24 @@ export async function handleGetTask(parameters) {
1260
1276
  return {
1261
1277
  content: [{
1262
1278
  type: "text",
1263
- text: JSON.stringify(task, null, 2)
1279
+ text: JSON.stringify({
1280
+ id: task.id,
1281
+ name: task.name,
1282
+ description: task.description,
1283
+ status: task.status?.status || "Unknown",
1284
+ priority: task.priority,
1285
+ due_date: task.due_date ? formatDueDate(Number(task.due_date)) : undefined,
1286
+ due_date_raw: task.due_date, // Keep raw timestamp for reference if needed
1287
+ url: task.url,
1288
+ list: task.list.name,
1289
+ space: task.space.name,
1290
+ folder: task.folder?.name,
1291
+ creator: task.creator,
1292
+ assignees: task.assignees,
1293
+ tags: task.tags,
1294
+ time_estimate: task.time_estimate,
1295
+ time_spent: task.time_spent,
1296
+ }, null, 2)
1264
1297
  }]
1265
1298
  };
1266
1299
  }
@@ -41,25 +41,52 @@ export function parseDueDate(dateString) {
41
41
  today.setHours(23, 59, 59, 999);
42
42
  return today.getTime();
43
43
  }
44
- if (lowerDate === 'tomorrow') {
45
- const tomorrow = new Date();
46
- tomorrow.setDate(tomorrow.getDate() + 1);
47
- tomorrow.setHours(23, 59, 59, 999);
48
- return tomorrow.getTime();
49
- }
50
- if (lowerDate.includes('next week')) {
51
- const nextWeek = new Date();
52
- nextWeek.setDate(nextWeek.getDate() + 7);
53
- nextWeek.setHours(23, 59, 59, 999);
54
- return nextWeek.getTime();
55
- }
56
- if (lowerDate.includes('next month')) {
57
- const nextMonth = new Date();
58
- nextMonth.setMonth(nextMonth.getMonth() + 1);
59
- nextMonth.setHours(23, 59, 59, 999);
60
- return nextMonth.getTime();
44
+ // Handle relative dates with specific times
45
+ const relativeTimeRegex = /(?:(\d+)\s*(days?|weeks?|months?)\s*from\s*now|tomorrow|next\s+(?:week|month))\s*(?:at\s+(\d+)(?::(\d+))?\s*(am|pm)?)?/i;
46
+ const match = lowerDate.match(relativeTimeRegex);
47
+ if (match) {
48
+ const date = new Date();
49
+ const [_, amount, unit, hours, minutes, meridian] = match;
50
+ // Calculate the future date
51
+ if (amount && unit) {
52
+ const value = parseInt(amount);
53
+ if (unit.startsWith('day')) {
54
+ date.setDate(date.getDate() + value);
55
+ }
56
+ else if (unit.startsWith('week')) {
57
+ date.setDate(date.getDate() + (value * 7));
58
+ }
59
+ else if (unit.startsWith('month')) {
60
+ date.setMonth(date.getMonth() + value);
61
+ }
62
+ }
63
+ else if (lowerDate.startsWith('tomorrow')) {
64
+ date.setDate(date.getDate() + 1);
65
+ }
66
+ else if (lowerDate.includes('next week')) {
67
+ date.setDate(date.getDate() + 7);
68
+ }
69
+ else if (lowerDate.includes('next month')) {
70
+ date.setMonth(date.getMonth() + 1);
71
+ }
72
+ // Set the time if specified
73
+ if (hours) {
74
+ let parsedHours = parseInt(hours);
75
+ const parsedMinutes = minutes ? parseInt(minutes) : 0;
76
+ // Convert to 24-hour format if meridian is specified
77
+ if (meridian?.toLowerCase() === 'pm' && parsedHours < 12)
78
+ parsedHours += 12;
79
+ if (meridian?.toLowerCase() === 'am' && parsedHours === 12)
80
+ parsedHours = 0;
81
+ date.setHours(parsedHours, parsedMinutes, 0, 0);
82
+ }
83
+ else {
84
+ // Default to end of day if no time specified
85
+ date.setHours(23, 59, 59, 999);
86
+ }
87
+ return date.getTime();
61
88
  }
62
- // Handle hours/days/weeks/months from now
89
+ // Handle hours from now
63
90
  const hoursRegex = /(\d+)\s*hours?\s*from\s*now/i;
64
91
  const daysRegex = /(\d+)\s*days?\s*from\s*now/i;
65
92
  const weeksRegex = /(\d+)\s*weeks?\s*from\s*now/i;
@@ -93,3 +120,31 @@ export function parseDueDate(dateString) {
93
120
  return undefined;
94
121
  }
95
122
  }
123
+ /**
124
+ * Format a due date timestamp into a human-readable string
125
+ *
126
+ * @param timestamp Unix timestamp in milliseconds
127
+ * @returns Formatted date string or undefined if timestamp is invalid
128
+ */
129
+ export function formatDueDate(timestamp) {
130
+ if (!timestamp)
131
+ return undefined;
132
+ try {
133
+ const date = new Date(timestamp);
134
+ if (isNaN(date.getTime()))
135
+ return undefined;
136
+ // Format: "March 10, 2025 at 10:56 PM"
137
+ return date.toLocaleString('en-US', {
138
+ year: 'numeric',
139
+ month: 'long',
140
+ day: 'numeric',
141
+ hour: 'numeric',
142
+ minute: '2-digit',
143
+ hour12: true
144
+ }).replace(' at', ',');
145
+ }
146
+ catch (error) {
147
+ console.warn(`Failed to format due date: ${timestamp}`, error);
148
+ return undefined;
149
+ }
150
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taazkareem/clickup-mcp-server",
3
- "version": "0.4.62",
3
+ "version": "0.4.63",
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",