@taazkareem/clickup-mcp-server 0.4.62 → 0.4.64
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.
|
@@ -239,15 +239,6 @@ export class BaseClickUpService {
|
|
|
239
239
|
* @param details - Details about the operation
|
|
240
240
|
*/
|
|
241
241
|
logOperation(operation, details) {
|
|
242
|
-
|
|
243
|
-
console.log(`[ClickUpService] ${operation}:`, details);
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* Protected helper method to check if cache is available
|
|
247
|
-
* @param cacheService Optional cache service instance
|
|
248
|
-
* @returns True if caching is available and enabled
|
|
249
|
-
*/
|
|
250
|
-
isCacheEnabled(cacheService) {
|
|
251
|
-
return !!cacheService && typeof cacheService.isEnabled === 'function' && cacheService.isEnabled();
|
|
242
|
+
console.log(`[${new Date().toISOString()}] ${operation}:`, details);
|
|
252
243
|
}
|
|
253
244
|
}
|
|
@@ -13,13 +13,11 @@ export { WorkspaceService } from './workspace.js';
|
|
|
13
13
|
export { TaskService } from './task.js';
|
|
14
14
|
export { ListService } from './list.js';
|
|
15
15
|
export { FolderService } from './folder.js';
|
|
16
|
-
export { InitializationService } from './initialization.js';
|
|
17
16
|
// Import service classes for the factory function
|
|
18
17
|
import { WorkspaceService } from './workspace.js';
|
|
19
18
|
import { TaskService } from './task.js';
|
|
20
19
|
import { ListService } from './list.js';
|
|
21
20
|
import { FolderService } from './folder.js';
|
|
22
|
-
import { InitializationService } from './initialization.js';
|
|
23
21
|
/**
|
|
24
22
|
* Factory function to create instances of all ClickUp services
|
|
25
23
|
* @param config Configuration for the services
|
|
@@ -33,11 +31,6 @@ export function createClickUpServices(config) {
|
|
|
33
31
|
workspace: workspaceService,
|
|
34
32
|
task: new TaskService(apiKey, teamId, baseUrl, workspaceService),
|
|
35
33
|
list: new ListService(apiKey, teamId, baseUrl, workspaceService),
|
|
36
|
-
folder: new FolderService(apiKey, teamId, baseUrl, workspaceService)
|
|
37
|
-
initialization: new InitializationService({
|
|
38
|
-
apiKey,
|
|
39
|
-
teamId,
|
|
40
|
-
baseUrl
|
|
41
|
-
})
|
|
34
|
+
folder: new FolderService(apiKey, teamId, baseUrl, workspaceService)
|
|
42
35
|
};
|
|
43
36
|
}
|
|
@@ -99,7 +99,7 @@ export class WorkspaceService extends BaseClickUpService {
|
|
|
99
99
|
*/
|
|
100
100
|
async getWorkspaceHierarchy(forceRefresh = false) {
|
|
101
101
|
try {
|
|
102
|
-
// If we have
|
|
102
|
+
// If we have the hierarchy in memory and not forcing refresh, return it
|
|
103
103
|
if (this.workspaceHierarchy && !forceRefresh) {
|
|
104
104
|
return this.workspaceHierarchy;
|
|
105
105
|
}
|
|
@@ -164,9 +164,9 @@ export class WorkspaceService extends BaseClickUpService {
|
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
/**
|
|
167
|
-
*
|
|
167
|
+
* Clear the stored workspace hierarchy, forcing a fresh fetch on next request
|
|
168
168
|
*/
|
|
169
|
-
|
|
169
|
+
clearWorkspaceHierarchy() {
|
|
170
170
|
this.workspaceHierarchy = null;
|
|
171
171
|
}
|
|
172
172
|
/**
|
package/build/tools/task.js
CHANGED
|
@@ -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
|
-
|
|
552
|
-
|
|
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
|
-
|
|
1215
|
-
|
|
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(
|
|
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
|
}
|
package/build/tools/utils.js
CHANGED
|
@@ -41,25 +41,52 @@ export function parseDueDate(dateString) {
|
|
|
41
41
|
today.setHours(23, 59, 59, 999);
|
|
42
42
|
return today.getTime();
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
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.
|
|
3
|
+
"version": "0.4.64",
|
|
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",
|
|
@@ -18,9 +18,7 @@
|
|
|
18
18
|
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
|
|
19
19
|
"start": "node build/index.js",
|
|
20
20
|
"dev": "tsc -w",
|
|
21
|
-
"prepare": "npm run build"
|
|
22
|
-
"prepublishOnly": "npm test",
|
|
23
|
-
"test": "echo \"No tests specified\" && exit 0"
|
|
21
|
+
"prepare": "npm run build"
|
|
24
22
|
},
|
|
25
23
|
"keywords": [
|
|
26
24
|
"clickup",
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { WorkspaceService } from "./workspace.js";
|
|
2
|
-
/**
|
|
3
|
-
* Service responsible for initializing the server state
|
|
4
|
-
* Handles preloading workspace data
|
|
5
|
-
*/
|
|
6
|
-
export class InitializationService {
|
|
7
|
-
constructor(config) {
|
|
8
|
-
// Create workspace service
|
|
9
|
-
this.workspaceService = new WorkspaceService(config.apiKey, config.teamId, config.baseUrl);
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Preload workspace hierarchy data on server startup
|
|
13
|
-
* This loads the entire workspace tree for faster initial access
|
|
14
|
-
*/
|
|
15
|
-
async preloadWorkspaceData() {
|
|
16
|
-
try {
|
|
17
|
-
console.log("Preloading workspace data...");
|
|
18
|
-
const startTime = Date.now();
|
|
19
|
-
// Force refresh to get the latest data
|
|
20
|
-
await this.workspaceService.getWorkspaceHierarchy(true);
|
|
21
|
-
const duration = (Date.now() - startTime) / 1000;
|
|
22
|
-
console.log(`Workspace data preloaded successfully in ${duration.toFixed(2)}s`);
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
console.error("Failed to preload workspace data:", error);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|