@taazkareem/clickup-mcp-server 0.7.0 → 0.7.2
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/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
A Model Context Protocol (MCP) server for integrating ClickUp tasks with AI applications. This server allows AI agents to interact with ClickUp tasks, spaces, lists, and folders through a standardized protocol.
|
|
8
8
|
|
|
9
|
-
> 🚀 **Status Update:** v0.7.
|
|
9
|
+
> 🚀 **Status Update:** v0.7.2 now available with complete Time Tracking support and Document Management features.
|
|
10
10
|
|
|
11
11
|
## Setup
|
|
12
12
|
|
|
@@ -61,13 +61,13 @@ Please disable tools you don't need if you are having issues with the number of
|
|
|
61
61
|
|
|
62
62
|
## Features
|
|
63
63
|
|
|
64
|
-
| 📝 Task Management
|
|
65
|
-
|
|
|
66
|
-
| • Create, update, and delete tasks`<br>`• Move and duplicate tasks anywhere`<br>`• Support for single and bulk operations`<br>`• Set start/due dates with natural language`<br>`• Create and manage subtasks`<br>`• Add comments and attachments | • Create, update, and delete space tags`<br>`• Add and remove tags from tasks`<br>`• Use natural language color commands`<br>`• Automatic contrasting foreground colors`<br>`• View all space tags`<br>`• Tag-based task organization across workspace |
|
|
67
|
-
| ⏱️**Time Tracking**
|
|
68
|
-
| • View time entries for tasks`<br>`• Start/stop time tracking on tasks`<br>`• Add manual time entries`<br>`• Delete time entries`<br>`• View currently running timer`<br>`• Track billable and non-billable time | • Navigate spaces, folders, and lists`<br>`• Create and manage folders`<br>`• Organize lists within spaces`<br>`• Create lists in folders`<br>`• View workspace hierarchy`<br>`• Efficient path navigation |
|
|
69
|
-
| ⚡**Integration Features**
|
|
70
|
-
| •
|
|
64
|
+
| 📝 Task Management | 🏷️ Tag Management |
|
|
65
|
+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
66
|
+
| • Create, update, and delete tasks`<br>`• Move and duplicate tasks anywhere `<br>`• Support for single and bulk operations `<br>`• Set start/due dates with natural language `<br>`• Create and manage subtasks `<br>`• Add comments and attachments | • Create, update, and delete space tags`<br>`• Add and remove tags from tasks `<br>`• Use natural language color commands `<br>`• Automatic contrasting foreground colors `<br>`• View all space tags `<br>`• Tag-based task organization across workspace |
|
|
67
|
+
| ⏱️**Time Tracking** | 🌳**Workspace Organization** |
|
|
68
|
+
| • View time entries for tasks`<br>`• Start/stop time tracking on tasks `<br>`• Add manual time entries `<br>`• Delete time entries `<br>`• View currently running timer `<br>`• Track billable and non-billable time | • Navigate spaces, folders, and lists`<br>`• Create and manage folders `<br>`• Organize lists within spaces `<br>`• Create lists in folders `<br>`• View workspace hierarchy `<br>`• Efficient path navigation |
|
|
69
|
+
| 📄**Document Management** | ⚡**Integration Features** |
|
|
70
|
+
| • Document Listing through all workspace`<br>` • Document Page listing `<br>` • Document Page Details `<br>` • Document Creation `<br>` • Document page update (append & prepend) | • Global name or ID-based lookups`<br>`• Case-insensitive matching `<br>`• Markdown formatting support `<br>`• Built-in rate limiting `<br>`• Error handling and validation `<br>`• Comprehensive API coverage |
|
|
71
71
|
|
|
72
72
|
## Available Tools
|
|
73
73
|
|
package/build/server.js
CHANGED
|
@@ -242,20 +242,42 @@ export class TaskServiceCore extends BaseClickUpService {
|
|
|
242
242
|
/**
|
|
243
243
|
* Get a task by its custom ID
|
|
244
244
|
* @param customTaskId The custom ID of the task (e.g., "ABC-123")
|
|
245
|
-
* @param listId Optional list ID to limit the search
|
|
245
|
+
* @param listId Optional list ID to limit the search (Note: ClickUp API might not filter by list_id when using custom_task_id)
|
|
246
246
|
* @returns The task details
|
|
247
247
|
*/
|
|
248
248
|
async getTaskByCustomId(customTaskId, listId) {
|
|
249
|
+
// Log the operation, including listId even if the API might ignore it for this specific lookup type
|
|
249
250
|
this.logOperation('getTaskByCustomId', { customTaskId, listId });
|
|
250
251
|
try {
|
|
251
252
|
return await this.makeRequest(async () => {
|
|
252
|
-
//
|
|
253
|
-
const url = `/task
|
|
254
|
-
|
|
255
|
-
|
|
253
|
+
// Use the standard task endpoint with the custom task ID
|
|
254
|
+
const url = `/task/${encodeURIComponent(customTaskId)}`;
|
|
255
|
+
// Add required query parameters for custom ID lookup
|
|
256
|
+
const params = new URLSearchParams({
|
|
257
|
+
custom_task_ids: 'true',
|
|
258
|
+
team_id: this.teamId // team_id is required when custom_task_ids is true
|
|
259
|
+
});
|
|
260
|
+
// Note: The ClickUp API documentation for GET /task/{task_id} doesn't explicitly mention
|
|
261
|
+
// filtering by list_id when custom_task_ids=true. This parameter might be ignored.
|
|
262
|
+
if (listId) {
|
|
263
|
+
this.logger.warn('listId provided to getTaskByCustomId, but the ClickUp API endpoint might not support it directly for custom ID lookups.', { customTaskId, listId });
|
|
264
|
+
// If ClickUp API were to support it, you would add it like this:
|
|
265
|
+
// params.append('list_id', listId);
|
|
266
|
+
}
|
|
267
|
+
const response = await this.client.get(url, { params });
|
|
268
|
+
// Handle potential non-JSON responses (though less likely with GET)
|
|
269
|
+
const data = response.data;
|
|
270
|
+
if (typeof data === 'string') {
|
|
271
|
+
throw new ClickUpServiceError('Received unexpected text response from API when fetching by custom ID', ErrorCode.UNKNOWN, data);
|
|
272
|
+
}
|
|
273
|
+
return data;
|
|
256
274
|
});
|
|
257
275
|
}
|
|
258
276
|
catch (error) {
|
|
277
|
+
// Provide more specific error context if possible
|
|
278
|
+
if (error instanceof ClickUpServiceError && error.code === ErrorCode.NOT_FOUND) {
|
|
279
|
+
throw new ClickUpServiceError(`Task with custom ID ${customTaskId} not found or not accessible for team ${this.teamId}.`, ErrorCode.NOT_FOUND, error.data);
|
|
280
|
+
}
|
|
259
281
|
throw this.handleError(error, `Failed to get task with custom ID ${customTaskId}`);
|
|
260
282
|
}
|
|
261
283
|
}
|
|
@@ -51,6 +51,34 @@ function getCachedTaskContext(taskName) {
|
|
|
51
51
|
//=============================================================================
|
|
52
52
|
// SHARED UTILITY FUNCTIONS
|
|
53
53
|
//=============================================================================
|
|
54
|
+
/**
|
|
55
|
+
* Parse time estimate string into minutes
|
|
56
|
+
* Supports formats like "2h 30m", "150m", "2.5h"
|
|
57
|
+
*/
|
|
58
|
+
function parseTimeEstimate(timeEstimate) {
|
|
59
|
+
// If it's already a number, return it directly
|
|
60
|
+
if (typeof timeEstimate === 'number') {
|
|
61
|
+
return timeEstimate;
|
|
62
|
+
}
|
|
63
|
+
if (!timeEstimate || typeof timeEstimate !== 'string')
|
|
64
|
+
return 0;
|
|
65
|
+
// If it's just a number as string, parse it
|
|
66
|
+
if (/^\d+$/.test(timeEstimate)) {
|
|
67
|
+
return parseInt(timeEstimate, 10);
|
|
68
|
+
}
|
|
69
|
+
let totalMinutes = 0;
|
|
70
|
+
// Extract hours
|
|
71
|
+
const hoursMatch = timeEstimate.match(/(\d+\.?\d*)h/);
|
|
72
|
+
if (hoursMatch) {
|
|
73
|
+
totalMinutes += parseFloat(hoursMatch[1]) * 60;
|
|
74
|
+
}
|
|
75
|
+
// Extract minutes
|
|
76
|
+
const minutesMatch = timeEstimate.match(/(\d+)m/);
|
|
77
|
+
if (minutesMatch) {
|
|
78
|
+
totalMinutes += parseInt(minutesMatch[1], 10);
|
|
79
|
+
}
|
|
80
|
+
return Math.round(totalMinutes); // Return minutes
|
|
81
|
+
}
|
|
54
82
|
/**
|
|
55
83
|
* Build task update data from parameters
|
|
56
84
|
*/
|
|
@@ -75,6 +103,15 @@ function buildUpdateData(params) {
|
|
|
75
103
|
updateData.start_date = parseDueDate(params.startDate);
|
|
76
104
|
updateData.start_date_time = true;
|
|
77
105
|
}
|
|
106
|
+
// Handle time estimate if provided - convert from string to minutes
|
|
107
|
+
if (params.time_estimate !== undefined) {
|
|
108
|
+
// Log the time estimate for debugging
|
|
109
|
+
console.log(`Original time_estimate: ${params.time_estimate}, typeof: ${typeof params.time_estimate}`);
|
|
110
|
+
// Parse and convert to number in minutes
|
|
111
|
+
const minutes = parseTimeEstimate(params.time_estimate);
|
|
112
|
+
console.log(`Converted time_estimate: ${minutes}`);
|
|
113
|
+
updateData.time_estimate = minutes;
|
|
114
|
+
}
|
|
78
115
|
// Handle custom fields if provided
|
|
79
116
|
if (params.custom_fields !== undefined) {
|
|
80
117
|
updateData.custom_fields = params.custom_fields;
|
|
@@ -174,6 +174,10 @@ export const updateTaskTool = {
|
|
|
174
174
|
type: "string",
|
|
175
175
|
description: "New start date. Supports both Unix timestamps (in milliseconds) and natural language expressions."
|
|
176
176
|
},
|
|
177
|
+
time_estimate: {
|
|
178
|
+
type: "string",
|
|
179
|
+
description: "Time estimate for the task. For best compatibility with the ClickUp API, use a numeric value in minutes (e.g., '150' for 2h 30m)"
|
|
180
|
+
},
|
|
177
181
|
custom_fields: {
|
|
178
182
|
type: "array",
|
|
179
183
|
items: {
|
|
@@ -112,14 +112,6 @@ export function validateListIdentification(listId, listName) {
|
|
|
112
112
|
* Ensures at least one update field is provided
|
|
113
113
|
*/
|
|
114
114
|
export function validateTaskUpdateData(updateData) {
|
|
115
|
-
// Check if there are any valid update fields present
|
|
116
|
-
const hasUpdates = Object.keys(updateData).some(key => {
|
|
117
|
-
return ['name', 'description', 'markdown_description', 'status', 'priority',
|
|
118
|
-
'dueDate', 'startDate', 'custom_fields'].includes(key);
|
|
119
|
-
});
|
|
120
|
-
if (!hasUpdates) {
|
|
121
|
-
throw new Error("At least one field to update must be provided");
|
|
122
|
-
}
|
|
123
115
|
// Validate custom_fields if provided
|
|
124
116
|
if (updateData.custom_fields) {
|
|
125
117
|
if (!Array.isArray(updateData.custom_fields)) {
|
|
@@ -131,6 +123,10 @@ export function validateTaskUpdateData(updateData) {
|
|
|
131
123
|
}
|
|
132
124
|
}
|
|
133
125
|
}
|
|
126
|
+
// Ensure there's at least one field to update
|
|
127
|
+
if (Object.keys(updateData).length === 0) {
|
|
128
|
+
throw new Error("At least one field to update must be provided");
|
|
129
|
+
}
|
|
134
130
|
}
|
|
135
131
|
/**
|
|
136
132
|
* Validate bulk task array and task identification
|
package/package.json
CHANGED