@pripanggalih/clickup-mcp 1.6.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.
Files changed (57) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +295 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +184 -0
  6. package/dist/clickup-text.d.ts +83 -0
  7. package/dist/clickup-text.d.ts.map +1 -0
  8. package/dist/clickup-text.js +563 -0
  9. package/dist/index.d.ts +5 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +135 -0
  12. package/dist/resources/space-resources.d.ts +6 -0
  13. package/dist/resources/space-resources.d.ts.map +1 -0
  14. package/dist/resources/space-resources.js +95 -0
  15. package/dist/shared/config.d.ts +11 -0
  16. package/dist/shared/config.d.ts.map +1 -0
  17. package/dist/shared/config.js +61 -0
  18. package/dist/shared/data-uri.d.ts +14 -0
  19. package/dist/shared/data-uri.d.ts.map +1 -0
  20. package/dist/shared/data-uri.js +34 -0
  21. package/dist/shared/image-processing.d.ts +13 -0
  22. package/dist/shared/image-processing.d.ts.map +1 -0
  23. package/dist/shared/image-processing.js +199 -0
  24. package/dist/shared/types.d.ts +21 -0
  25. package/dist/shared/types.d.ts.map +1 -0
  26. package/dist/shared/types.js +2 -0
  27. package/dist/shared/utils.d.ts +71 -0
  28. package/dist/shared/utils.d.ts.map +1 -0
  29. package/dist/shared/utils.js +508 -0
  30. package/dist/test-utils.d.ts +23 -0
  31. package/dist/test-utils.d.ts.map +1 -0
  32. package/dist/test-utils.js +44 -0
  33. package/dist/tools/admin-tools.d.ts +3 -0
  34. package/dist/tools/admin-tools.d.ts.map +1 -0
  35. package/dist/tools/admin-tools.js +288 -0
  36. package/dist/tools/doc-tools.d.ts +4 -0
  37. package/dist/tools/doc-tools.d.ts.map +1 -0
  38. package/dist/tools/doc-tools.js +436 -0
  39. package/dist/tools/list-tools.d.ts +4 -0
  40. package/dist/tools/list-tools.d.ts.map +1 -0
  41. package/dist/tools/list-tools.js +175 -0
  42. package/dist/tools/search-tools.d.ts +3 -0
  43. package/dist/tools/search-tools.d.ts.map +1 -0
  44. package/dist/tools/search-tools.js +161 -0
  45. package/dist/tools/space-tools.d.ts +3 -0
  46. package/dist/tools/space-tools.d.ts.map +1 -0
  47. package/dist/tools/space-tools.js +128 -0
  48. package/dist/tools/task-tools.d.ts +8 -0
  49. package/dist/tools/task-tools.d.ts.map +1 -0
  50. package/dist/tools/task-tools.js +329 -0
  51. package/dist/tools/task-write-tools.d.ts +3 -0
  52. package/dist/tools/task-write-tools.d.ts.map +1 -0
  53. package/dist/tools/task-write-tools.js +567 -0
  54. package/dist/tools/time-tools.d.ts +4 -0
  55. package/dist/tools/time-tools.d.ts.map +1 -0
  56. package/dist/tools/time-tools.js +338 -0
  57. package/package.json +74 -0
@@ -0,0 +1,567 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerTaskToolsWrite = registerTaskToolsWrite;
4
+ const zod_1 = require("zod");
5
+ const config_1 = require("../shared/config");
6
+ const utils_1 = require("../shared/utils");
7
+ const clickup_text_1 = require("../clickup-text");
8
+ // Shared schemas for task parameters
9
+ const taskNameSchema = zod_1.z.string().min(1).describe("The name/title of the task");
10
+ const taskPrioritySchema = zod_1.z.enum(["urgent", "high", "normal", "low"]).optional().describe("Optional priority level");
11
+ const taskDueDateSchema = zod_1.z.string().optional().describe("Optional due date as ISO date string (e.g., '2024-10-06T23:59:59+02:00')");
12
+ const taskStartDateSchema = zod_1.z.string().optional().describe("Optional start date as ISO date string (e.g., '2024-10-06T09:00:00+02:00')");
13
+ const taskTimeEstimateSchema = zod_1.z.number().optional().describe("Optional time estimate in hours (will be converted to milliseconds)");
14
+ const taskTagsSchema = zod_1.z.array(zod_1.z.string()).optional().describe("Optional array of tag names");
15
+ function registerTaskToolsWrite(server, userData) {
16
+ server.tool("addComment", (() => {
17
+ const descriptionBase = [
18
+ "Adds a comment to a specific task.",
19
+ "LINKING BEST PRACTICES:",
20
+ "- Always reference related tasks using ClickUp URLs (https://app.clickup.com/t/TASK_ID)",
21
+ "- Include task links when mentioning dependencies, related work, or follow-ups",
22
+ "- Link to relevant lists, spaces, or other ClickUp entities when applicable",
23
+ "PROGRESS UPDATES: Include current status, progress information, and next steps.",
24
+ "If external links are provided, verify they are publicly accessible and incorporate relevant information.",
25
+ "Check the task's current status - if it's in 'backlog' or similar inactive states, suggest moving it to an active status like 'in progress' when work is being done."
26
+ ];
27
+ if (config_1.CONFIG.primaryLanguageHint && config_1.CONFIG.primaryLanguageHint.toLowerCase() !== 'en') {
28
+ descriptionBase.splice(1, 0, `For optimal results, consider writing comments in '${config_1.CONFIG.primaryLanguageHint}' unless the task is already in another language.`);
29
+ }
30
+ return descriptionBase.join("\n");
31
+ })(), {
32
+ task_id: zod_1.z.string().min(6).max(9).describe("The 6-9 character task ID to comment on"),
33
+ comment: zod_1.z.string().min(1).describe("The comment text to add to the task"),
34
+ }, {
35
+ readOnlyHint: false,
36
+ destructiveHint: false,
37
+ idempotentHint: false,
38
+ }, async ({ task_id, comment }) => {
39
+ try {
40
+ // Convert markdown to ClickUp formatted blocks
41
+ const commentBlocks = (0, clickup_text_1.convertMarkdownToClickUpBlocks)(comment);
42
+ const requestBody = {
43
+ comment: commentBlocks,
44
+ notify_all: true
45
+ };
46
+ const response = await fetch(`https://api.clickup.com/api/v2/task/${task_id}/comment`, {
47
+ method: 'POST',
48
+ headers: {
49
+ Authorization: config_1.CONFIG.apiKey,
50
+ 'Content-Type': 'application/json'
51
+ },
52
+ body: JSON.stringify(requestBody)
53
+ });
54
+ if (!response.ok) {
55
+ const errorData = await response.json().catch(() => ({}));
56
+ throw new Error(`Error adding comment: ${response.status} ${response.statusText} - ${JSON.stringify(errorData)}`);
57
+ }
58
+ const commentData = await response.json();
59
+ return {
60
+ content: [
61
+ {
62
+ type: "text",
63
+ text: [
64
+ `Comment added successfully!`,
65
+ `comment_id: ${commentData.id || 'N/A'}`,
66
+ `task_id: ${task_id}`,
67
+ `comment: ${comment}`,
68
+ `date: ${timestampToIso(commentData.date || Date.now())}`,
69
+ `user: ${commentData.user?.username || 'Current user'}`,
70
+ ].join('\n')
71
+ }
72
+ ],
73
+ };
74
+ }
75
+ catch (error) {
76
+ console.error('Error adding comment:', error);
77
+ return {
78
+ content: [
79
+ {
80
+ type: "text",
81
+ text: `Error adding comment: ${error instanceof Error ? error.message : 'Unknown error'}`,
82
+ },
83
+ ],
84
+ };
85
+ }
86
+ });
87
+ server.tool("updateTask", (() => {
88
+ const descriptionBase = [
89
+ "Updates various aspects of an existing task including dependencies and relationships.",
90
+ "ALWAYS include the task URL (https://app.clickup.com/t/TASK_ID) when updating or referencing tasks.",
91
+ "Use getListInfo first to see valid status options.",
92
+ "SAFETY FEATURE: Description updates are APPEND-ONLY to prevent data loss - existing content is preserved.",
93
+ "STATUS UPDATES: Use the `addComment` tool for progress reports, work logs, and status updates rather than the task description.",
94
+ "Task descriptions should contain requirements, specifications, and core task information.",
95
+ "LINKING IN DESCRIPTIONS: When appending descriptions, include links to related tasks, lists, or external resources.",
96
+ "IMPORTANT: When updating tasks (especially when booking time or adding progress), ensure the status makes sense for the work being done - tasks in 'backlog' or 'closed' states usually shouldn't have active work.",
97
+ "Suggest appropriate status transitions and always provide the clickable task URL in responses."
98
+ ];
99
+ if (config_1.CONFIG.primaryLanguageHint && config_1.CONFIG.primaryLanguageHint.toLowerCase() !== 'en') {
100
+ descriptionBase.splice(1, 0, `For optimal results, consider writing task names and descriptions in '${config_1.CONFIG.primaryLanguageHint}' unless the task is already in another language.`);
101
+ }
102
+ return descriptionBase.join("\n");
103
+ })(), {
104
+ task_id: zod_1.z.string().min(6).max(9).describe("The 6-9 character task ID to update"),
105
+ name: taskNameSchema.optional(),
106
+ append_description: zod_1.z.string().optional().describe("Optional markdown content to APPEND to existing task description (preserves existing content for safety)"),
107
+ status: zod_1.z.string().optional().describe("Optional new status name - use getListInfo to see valid options"),
108
+ priority: taskPrioritySchema,
109
+ due_date: taskDueDateSchema,
110
+ start_date: taskStartDateSchema,
111
+ time_estimate: taskTimeEstimateSchema,
112
+ tags: taskTagsSchema.describe("Optional array of tag names (will replace existing tags)"),
113
+ parent_task_id: zod_1.z.string().optional().describe("Optional parent task ID to change parent/child relationships"),
114
+ assignees: zod_1.z.array(zod_1.z.string()).optional().describe(createAssigneeDescription(userData)),
115
+ waiting_on: zod_1.z.array(zod_1.z.string()).optional().describe("Optional array of task IDs that this task should wait on (will replace existing waiting_on relationships)"),
116
+ blocking: zod_1.z.array(zod_1.z.string()).optional().describe("Optional array of task IDs that this task should block. Note: This creates dependencies FROM those tasks TO this task (those tasks will wait on this one)"),
117
+ linked_tasks: zod_1.z.array(zod_1.z.string()).optional().describe("Optional array of task IDs to link as related tasks without blocking (will replace existing linked tasks)")
118
+ }, {
119
+ readOnlyHint: false,
120
+ destructiveHint: true,
121
+ idempotentHint: false,
122
+ openWorldHint: true
123
+ }, async ({ task_id, name, append_description, status, priority, due_date, start_date, time_estimate, tags, parent_task_id, assignees, blocking, waiting_on, linked_tasks }) => {
124
+ try {
125
+ const userData = await (0, utils_1.getCurrentUser)();
126
+ // Get task details including current markdown description
127
+ const taskResponse = await fetch(`https://api.clickup.com/api/v2/task/${task_id}?include_markdown_description=true`, {
128
+ headers: { Authorization: config_1.CONFIG.apiKey },
129
+ });
130
+ if (!taskResponse.ok) {
131
+ throw new Error(`Error fetching task: ${taskResponse.status} ${taskResponse.statusText}`);
132
+ }
133
+ const taskData = await taskResponse.json();
134
+ // Handle dependencies separately since they need individual API calls
135
+ let dependencyUpdateResults = [];
136
+ if (blocking !== undefined || waiting_on !== undefined || linked_tasks !== undefined) {
137
+ const dependencyResults = await updateTaskDependencies(task_id, taskData, { blocking, waiting_on, linked_tasks });
138
+ dependencyUpdateResults = dependencyResults;
139
+ }
140
+ // Handle tags separately since they need individual API calls
141
+ let tagUpdateResults = [];
142
+ if (tags !== undefined) {
143
+ // Get current tags
144
+ const currentTags = taskData.tags?.map((t) => t.name) || [];
145
+ const tagsToAdd = tags.filter(tag => !currentTags.includes(tag));
146
+ const tagsToRemove = currentTags.filter((tag) => !tags.includes(tag));
147
+ // Add new tags
148
+ for (const tagName of tagsToAdd) {
149
+ try {
150
+ const addTagResponse = await fetch(`https://api.clickup.com/api/v2/task/${task_id}/tag/${encodeURIComponent(tagName)}`, {
151
+ method: 'POST',
152
+ headers: { Authorization: config_1.CONFIG.apiKey }
153
+ });
154
+ if (!addTagResponse.ok) {
155
+ console.error(`Failed to add tag "${tagName}": ${addTagResponse.status}`);
156
+ tagUpdateResults.push(`Failed to add tag: ${tagName}`);
157
+ }
158
+ }
159
+ catch (error) {
160
+ console.error(`Error adding tag "${tagName}":`, error);
161
+ tagUpdateResults.push(`Error adding tag: ${tagName}`);
162
+ }
163
+ }
164
+ // Remove old tags
165
+ for (const tagName of tagsToRemove) {
166
+ try {
167
+ const removeTagResponse = await fetch(`https://api.clickup.com/api/v2/task/${task_id}/tag/${encodeURIComponent(tagName)}`, {
168
+ method: 'DELETE',
169
+ headers: { Authorization: config_1.CONFIG.apiKey }
170
+ });
171
+ if (!removeTagResponse.ok) {
172
+ console.error(`Failed to remove tag "${tagName}": ${removeTagResponse.status}`);
173
+ tagUpdateResults.push(`Failed to remove tag: ${tagName}`);
174
+ }
175
+ }
176
+ catch (error) {
177
+ console.error(`Error removing tag "${tagName}":`, error);
178
+ tagUpdateResults.push(`Error removing tag: ${tagName}`);
179
+ }
180
+ }
181
+ }
182
+ // Handle append-only description update with markdown support
183
+ let finalDescription;
184
+ if (append_description) {
185
+ const currentDescription = taskData.markdown_description || "";
186
+ const timestamp = new Date().toISOString().split('T')[0]; // YYYY-MM-DD format
187
+ const separator = currentDescription.trim() ? "\n\n---\n" : "";
188
+ finalDescription = currentDescription + separator + `**Edit (${timestamp}):** ${append_description}`;
189
+ }
190
+ // Build update body without tags (they're handled separately)
191
+ const updateBody = buildTaskRequestBody({
192
+ name, status, priority, due_date, start_date, time_estimate, parent_task_id, assignees
193
+ });
194
+ // Add markdown description if we have content to append
195
+ if (finalDescription !== undefined) {
196
+ updateBody.markdown_description = finalDescription;
197
+ }
198
+ // Handle assignees for updates (different from creates)
199
+ if (assignees !== undefined) {
200
+ updateBody.assignees = { add: assignees, rem: [] }; // Add new assignees, remove none
201
+ }
202
+ // Check if there's anything to update (including tags and dependencies which were handled separately)
203
+ if (Object.keys(updateBody).length === 0 && tags === undefined && blocking === undefined && waiting_on === undefined && linked_tasks === undefined) {
204
+ return {
205
+ content: [
206
+ {
207
+ type: "text",
208
+ text: "No updates provided. Please specify at least one field to update.",
209
+ },
210
+ ],
211
+ };
212
+ }
213
+ // Update the task (if there are non-tag updates)
214
+ let updatedTask = taskData;
215
+ if (Object.keys(updateBody).length > 0) {
216
+ const updateResponse = await fetch(`https://api.clickup.com/api/v2/task/${task_id}`, {
217
+ method: 'PUT',
218
+ headers: {
219
+ Authorization: config_1.CONFIG.apiKey,
220
+ 'Content-Type': 'application/json'
221
+ },
222
+ body: JSON.stringify(updateBody)
223
+ });
224
+ if (!updateResponse.ok) {
225
+ const errorData = await updateResponse.json().catch(() => ({}));
226
+ throw new Error(`Error updating task: ${updateResponse.status} ${updateResponse.statusText} - ${JSON.stringify(errorData)}`);
227
+ }
228
+ updatedTask = await updateResponse.json();
229
+ }
230
+ // If only tags or dependencies were updated, fetch the task again to get the updated state
231
+ if ((tags !== undefined || blocking !== undefined || waiting_on !== undefined || linked_tasks !== undefined) && Object.keys(updateBody).length === 0) {
232
+ const refreshResponse = await fetch(`https://api.clickup.com/api/v2/task/${task_id}`, {
233
+ headers: { Authorization: config_1.CONFIG.apiKey },
234
+ });
235
+ if (refreshResponse.ok) {
236
+ updatedTask = await refreshResponse.json();
237
+ }
238
+ }
239
+ const responseLines = formatTaskResponse(updatedTask, 'updated', {
240
+ name, append_description, status, priority, due_date, start_date, time_estimate, tags, parent_task_id, assignees, blocking, waiting_on, linked_tasks
241
+ }, userData);
242
+ // Add dependency update results if any
243
+ if (dependencyUpdateResults.length > 0) {
244
+ responseLines.push('dependency_warnings: ' + dependencyUpdateResults.join('; '));
245
+ }
246
+ // Add tag update results if any
247
+ if (tagUpdateResults.length > 0) {
248
+ responseLines.push('tag_warnings: ' + tagUpdateResults.join('; '));
249
+ }
250
+ return {
251
+ content: [
252
+ {
253
+ type: "text",
254
+ text: responseLines.join('\n')
255
+ }
256
+ ],
257
+ };
258
+ }
259
+ catch (error) {
260
+ console.error('Error updating task:', error);
261
+ return {
262
+ content: [
263
+ {
264
+ type: "text",
265
+ text: `Error updating task: ${error instanceof Error ? error.message : 'Unknown error'}`,
266
+ },
267
+ ],
268
+ };
269
+ }
270
+ });
271
+ server.tool("createTask", (() => {
272
+ const descriptionBase = [
273
+ "Creates a new task in a specific list and assigns it to specified users (defaults to current user).",
274
+ "CRITICAL LINKING REQUIREMENTS:",
275
+ "- ALWAYS search for similar existing tasks first using searchTasks to avoid duplicates",
276
+ "- Include links to related tasks in the description (format: https://app.clickup.com/t/TASK_ID)",
277
+ "- Reference parent/child tasks, dependencies, and related work with clickable links",
278
+ "- The response will include the new task's clickable URL - always share this link",
279
+ "Use getListInfo first to understand the list context and available statuses.",
280
+ "Task descriptions support full markdown formatting including **bold**, *italic*, lists, links, and code blocks.",
281
+ "BEST PRACTICE: Every task creation should result in sharing the clickable task URL for future reference."
282
+ ];
283
+ if (config_1.CONFIG.primaryLanguageHint && config_1.CONFIG.primaryLanguageHint.toLowerCase() !== 'en') {
284
+ descriptionBase.splice(1, 0, `For optimal results, consider writing task names and descriptions in '${config_1.CONFIG.primaryLanguageHint}' unless specified otherwise or unless the context requires another language.`);
285
+ }
286
+ return descriptionBase.join("\n");
287
+ })(), {
288
+ list_id: zod_1.z.string().min(1).describe("The ID of the list where the task will be created. Note: ClickUp API does not support moving tasks between lists after creation - this must be done manually in the ClickUp interface"),
289
+ name: taskNameSchema,
290
+ description: zod_1.z.string().optional().describe("Optional markdown description for the task - supports full markdown formatting"),
291
+ status: zod_1.z.string().optional().describe("Optional status name - use getListInfo to see valid options"),
292
+ priority: taskPrioritySchema,
293
+ due_date: taskDueDateSchema,
294
+ start_date: taskStartDateSchema,
295
+ time_estimate: taskTimeEstimateSchema,
296
+ tags: taskTagsSchema,
297
+ parent_task_id: zod_1.z.string().optional().describe("Optional parent task ID to create this as a subtask"),
298
+ assignees: zod_1.z.array(zod_1.z.string()).optional().describe(createAssigneeDescription(userData))
299
+ }, {
300
+ readOnlyHint: false,
301
+ destructiveHint: false,
302
+ idempotentHint: false,
303
+ openWorldHint: true
304
+ }, async ({ list_id, name, description, status, priority, due_date, start_date, time_estimate, tags, parent_task_id, assignees }) => {
305
+ try {
306
+ const userData = await (0, utils_1.getCurrentUser)();
307
+ const currentUserId = userData.user.id;
308
+ const requestBody = buildTaskRequestBody({
309
+ name, status, priority, due_date, start_date, time_estimate, tags, assignees, parent_task_id
310
+ }, currentUserId);
311
+ // Add markdown description if provided
312
+ if (description) {
313
+ requestBody.markdown_description = description;
314
+ }
315
+ const response = await fetch(`https://api.clickup.com/api/v2/list/${list_id}/task`, {
316
+ method: 'POST',
317
+ headers: {
318
+ Authorization: config_1.CONFIG.apiKey,
319
+ 'Content-Type': 'application/json'
320
+ },
321
+ body: JSON.stringify(requestBody)
322
+ });
323
+ if (!response.ok) {
324
+ const errorData = await response.json().catch(() => ({}));
325
+ throw new Error(`Error creating task: ${response.status} ${response.statusText} - ${JSON.stringify(errorData)}`);
326
+ }
327
+ const createdTask = await response.json();
328
+ const responseLines = formatTaskResponse(createdTask, 'created', {
329
+ list_id, name, description, status, priority, due_date, start_date, time_estimate, tags, parent_task_id, assignees
330
+ }, userData);
331
+ return {
332
+ content: [
333
+ {
334
+ type: "text",
335
+ text: responseLines.join('\n')
336
+ }
337
+ ],
338
+ };
339
+ }
340
+ catch (error) {
341
+ console.error('Error creating task:', error);
342
+ return {
343
+ content: [
344
+ {
345
+ type: "text",
346
+ text: `Error creating task: ${error instanceof Error ? error.message : 'Unknown error'}`,
347
+ },
348
+ ],
349
+ };
350
+ }
351
+ });
352
+ }
353
+ // Write-specific utility functions
354
+ function createAssigneeDescription(userData) {
355
+ const user = userData.user;
356
+ return `Optional array of user IDs to assign to the task (defaults to current user: ${user.username} (${user.id}))`;
357
+ }
358
+ function convertPriorityToNumber(priority) {
359
+ switch (priority) {
360
+ case "urgent": return 1;
361
+ case "high": return 2;
362
+ case "normal": return 3;
363
+ case "low": return 4;
364
+ default: return 3;
365
+ }
366
+ }
367
+ function convertPriorityToString(priority) {
368
+ const priorityMap = { 1: 'urgent', 2: 'high', 3: 'normal', 4: 'low' };
369
+ return priorityMap[priority] || 'unknown';
370
+ }
371
+ function formatTimeEstimate(hours) {
372
+ const displayHours = Math.floor(hours);
373
+ const displayMinutes = Math.round((hours - displayHours) * 60);
374
+ return displayHours > 0 ? `${displayHours}h ${displayMinutes}m` : `${displayMinutes}m`;
375
+ }
376
+ /**
377
+ * Formats timestamp to ISO string with local timezone (not UTC)
378
+ */
379
+ function timestampToIso(timestamp) {
380
+ const date = new Date(+timestamp);
381
+ const year = date.getFullYear();
382
+ const month = String(date.getMonth() + 1).padStart(2, '0');
383
+ const day = String(date.getDate()).padStart(2, '0');
384
+ const hours = String(date.getHours()).padStart(2, '0');
385
+ const minutes = String(date.getMinutes()).padStart(2, '0');
386
+ // Calculate timezone offset
387
+ const offset = date.getTimezoneOffset();
388
+ const offsetHours = Math.floor(Math.abs(offset) / 60);
389
+ const offsetMinutes = Math.abs(offset) % 60;
390
+ const sign = offset <= 0 ? '+' : '-';
391
+ const timezoneOffset = sign + String(offsetHours).padStart(2, '0') + ':' + String(offsetMinutes).padStart(2, '0');
392
+ return `${year}-${month}-${day}T${hours}:${minutes}${timezoneOffset}`;
393
+ }
394
+ function buildTaskRequestBody(params, currentUserId) {
395
+ const requestBody = {};
396
+ if (params.name !== undefined) {
397
+ requestBody.name = params.name;
398
+ }
399
+ if (params.status !== undefined) {
400
+ requestBody.status = params.status;
401
+ }
402
+ if (params.priority !== undefined) {
403
+ requestBody.priority = convertPriorityToNumber(params.priority);
404
+ }
405
+ if (params.due_date !== undefined) {
406
+ requestBody.due_date = new Date(params.due_date).getTime();
407
+ }
408
+ if (params.start_date !== undefined) {
409
+ requestBody.start_date = new Date(params.start_date).getTime();
410
+ }
411
+ if (params.time_estimate !== undefined) {
412
+ requestBody.time_estimate = Math.round(params.time_estimate * 60 * 60 * 1000);
413
+ }
414
+ // Tags are handled separately via dedicated API endpoints
415
+ // Do not include in the update request body
416
+ if (params.assignees !== undefined) {
417
+ requestBody.assignees = params.assignees;
418
+ }
419
+ else if (currentUserId) {
420
+ requestBody.assignees = [currentUserId];
421
+ }
422
+ if (params.parent_task_id !== undefined) {
423
+ requestBody.parent = params.parent_task_id;
424
+ }
425
+ return requestBody;
426
+ }
427
+ // Helper function to manage task dependencies
428
+ async function updateTaskDependencies(taskId, taskData, dependencies) {
429
+ const errors = [];
430
+ // Get current dependencies
431
+ const currentBlocking = taskData.blocking?.map((dep) => dep.id) || [];
432
+ const currentWaitingOn = taskData.waiting_on?.map((dep) => dep.id) || [];
433
+ const currentLinked = taskData.linked_tasks?.map((task) => task.id) || [];
434
+ // Helper function to make dependency API calls
435
+ async function modifyDependency(operation, type, fromTaskId, toTaskId, dependsOn) {
436
+ try {
437
+ let url;
438
+ let options;
439
+ if (type === 'linked') {
440
+ // Linked tasks use a different endpoint
441
+ url = `https://api.clickup.com/api/v2/task/${fromTaskId}/link/${toTaskId}`;
442
+ options = {
443
+ method: operation === 'add' ? 'POST' : 'DELETE',
444
+ headers: { Authorization: config_1.CONFIG.apiKey }
445
+ };
446
+ }
447
+ else {
448
+ // Dependencies (blocking/waiting_on)
449
+ if (operation === 'add') {
450
+ url = `https://api.clickup.com/api/v2/task/${fromTaskId}/dependency`;
451
+ options = {
452
+ method: 'POST',
453
+ headers: {
454
+ Authorization: config_1.CONFIG.apiKey,
455
+ 'Content-Type': 'application/json'
456
+ },
457
+ body: JSON.stringify({
458
+ depends_on: dependsOn,
459
+ dependency_type: 1 // Always use 1 for waiting_on type
460
+ })
461
+ };
462
+ }
463
+ else {
464
+ // Remove dependency
465
+ url = `https://api.clickup.com/api/v2/task/${fromTaskId}/dependency?depends_on=${dependsOn}`;
466
+ options = {
467
+ method: 'DELETE',
468
+ headers: { Authorization: config_1.CONFIG.apiKey }
469
+ };
470
+ }
471
+ }
472
+ const response = await fetch(url, options);
473
+ if (!response.ok) {
474
+ const action = operation === 'add' ? 'add' : 'remove';
475
+ const typeLabel = type === 'linked' ? 'link' : type.replace('_', ' ');
476
+ console.error(`Failed to ${action} ${typeLabel} "${toTaskId}": ${response.status}`);
477
+ errors.push(`Failed to ${action} ${typeLabel}: ${toTaskId}`);
478
+ }
479
+ }
480
+ catch (error) {
481
+ const action = operation === 'add' ? 'adding' : 'removing';
482
+ const typeLabel = type === 'linked' ? 'link' : type.replace('_', ' ');
483
+ console.error(`Error ${action} ${typeLabel} "${toTaskId}":`, error);
484
+ errors.push(`Error ${action} ${typeLabel}: ${toTaskId}`);
485
+ }
486
+ }
487
+ // Process blocking dependencies (tasks that should depend on this task)
488
+ if (dependencies.blocking !== undefined) {
489
+ const toAdd = dependencies.blocking.filter(id => !currentBlocking.includes(id));
490
+ const toRemove = currentBlocking.filter((id) => !dependencies.blocking.includes(id));
491
+ // To make another task depend on this one, add dependency FROM that task TO this task
492
+ for (const depTaskId of toAdd) {
493
+ await modifyDependency('add', 'blocking', depTaskId, depTaskId, taskId);
494
+ }
495
+ for (const depTaskId of toRemove) {
496
+ await modifyDependency('remove', 'blocking', depTaskId, depTaskId, taskId);
497
+ }
498
+ }
499
+ // Process waiting_on dependencies
500
+ if (dependencies.waiting_on !== undefined) {
501
+ const toAdd = dependencies.waiting_on.filter(id => !currentWaitingOn.includes(id));
502
+ const toRemove = currentWaitingOn.filter((id) => !dependencies.waiting_on.includes(id));
503
+ for (const depTaskId of toAdd) {
504
+ await modifyDependency('add', 'waiting_on', taskId, depTaskId, depTaskId);
505
+ }
506
+ for (const depTaskId of toRemove) {
507
+ await modifyDependency('remove', 'waiting_on', taskId, depTaskId, depTaskId);
508
+ }
509
+ }
510
+ // Process linked tasks
511
+ if (dependencies.linked_tasks !== undefined) {
512
+ const toAdd = dependencies.linked_tasks.filter(id => !currentLinked.includes(id));
513
+ const toRemove = currentLinked.filter((id) => !dependencies.linked_tasks.includes(id));
514
+ for (const linkedTaskId of toAdd) {
515
+ await modifyDependency('add', 'linked', taskId, linkedTaskId, linkedTaskId);
516
+ }
517
+ for (const linkedTaskId of toRemove) {
518
+ await modifyDependency('remove', 'linked', taskId, linkedTaskId, linkedTaskId);
519
+ }
520
+ }
521
+ return errors;
522
+ }
523
+ function formatTaskResponse(task, operation, params, userData) {
524
+ const responseLines = [
525
+ `Task ${operation} successfully!`,
526
+ `task_id: ${task.id}`,
527
+ `name: ${task.name}`,
528
+ ...(operation === 'created' ? [`url: ${task.url}`] : []),
529
+ `status: ${task.status?.status || 'Unknown'}`,
530
+ `assignees: ${task.assignees?.map((a) => `${a.username} (${a.id})`).join(', ') || 'None'}`,
531
+ ...(operation === 'created' && params.list_id ? [`list_id: ${params.list_id}`] : []),
532
+ ...(operation === 'updated' ? [
533
+ `updated_by: ${userData.user.username} (${userData.user.id})`,
534
+ `updated_at: ${timestampToIso(Date.now())}`
535
+ ] : [])
536
+ ];
537
+ if (params.priority !== undefined || task.priority) {
538
+ const priority = task.priority ? convertPriorityToString(task.priority.priority) :
539
+ params.priority ? params.priority : 'unknown';
540
+ responseLines.push(`priority: ${priority}`);
541
+ }
542
+ if (params.due_date !== undefined) {
543
+ responseLines.push(`due_date: ${params.due_date}`);
544
+ }
545
+ if (params.start_date !== undefined) {
546
+ responseLines.push(`start_date: ${params.start_date}`);
547
+ }
548
+ if (params.time_estimate !== undefined) {
549
+ responseLines.push(`time_estimate: ${formatTimeEstimate(params.time_estimate)}`);
550
+ }
551
+ if (params.tags !== undefined && params.tags.length > 0) {
552
+ responseLines.push(`tags: ${params.tags.join(', ')}`);
553
+ }
554
+ if (params.parent_task_id !== undefined) {
555
+ responseLines.push(`parent_task_id: ${params.parent_task_id}`);
556
+ }
557
+ if (params.blocking !== undefined && params.blocking.length > 0) {
558
+ responseLines.push(`blocking: ${params.blocking.join(', ')}`);
559
+ }
560
+ if (params.waiting_on !== undefined && params.waiting_on.length > 0) {
561
+ responseLines.push(`waiting_on: ${params.waiting_on.join(', ')}`);
562
+ }
563
+ if (params.linked_tasks !== undefined && params.linked_tasks.length > 0) {
564
+ responseLines.push(`linked_tasks: ${params.linked_tasks.join(', ')}`);
565
+ }
566
+ return responseLines;
567
+ }
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerTimeToolsRead(server: McpServer): void;
3
+ export declare function registerTimeToolsWrite(server: McpServer): void;
4
+ //# sourceMappingURL=time-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-tools.d.ts","sourceRoot":"","sources":["../../src/tools/time-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAyDpE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,QAgFtD;AAyLD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,QAwFvD"}