@alanse/clickup-multi-mcp-server 1.0.0
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/Dockerfile +38 -0
- package/LICENSE +21 -0
- package/README.md +470 -0
- package/build/config.js +237 -0
- package/build/index.js +87 -0
- package/build/logger.js +163 -0
- package/build/middleware/security.js +231 -0
- package/build/server.js +288 -0
- package/build/services/clickup/base.js +432 -0
- package/build/services/clickup/bulk.js +180 -0
- package/build/services/clickup/document.js +159 -0
- package/build/services/clickup/folder.js +136 -0
- package/build/services/clickup/index.js +76 -0
- package/build/services/clickup/list.js +191 -0
- package/build/services/clickup/tag.js +239 -0
- package/build/services/clickup/task/index.js +32 -0
- package/build/services/clickup/task/task-attachments.js +105 -0
- package/build/services/clickup/task/task-comments.js +114 -0
- package/build/services/clickup/task/task-core.js +604 -0
- package/build/services/clickup/task/task-custom-fields.js +107 -0
- package/build/services/clickup/task/task-search.js +986 -0
- package/build/services/clickup/task/task-service.js +104 -0
- package/build/services/clickup/task/task-tags.js +113 -0
- package/build/services/clickup/time.js +244 -0
- package/build/services/clickup/types.js +33 -0
- package/build/services/clickup/workspace.js +397 -0
- package/build/services/shared.js +61 -0
- package/build/sse_server.js +277 -0
- package/build/tools/documents.js +489 -0
- package/build/tools/folder.js +331 -0
- package/build/tools/index.js +16 -0
- package/build/tools/list.js +428 -0
- package/build/tools/member.js +106 -0
- package/build/tools/tag.js +833 -0
- package/build/tools/task/attachments.js +357 -0
- package/build/tools/task/attachments.types.js +9 -0
- package/build/tools/task/bulk-operations.js +338 -0
- package/build/tools/task/handlers.js +919 -0
- package/build/tools/task/index.js +30 -0
- package/build/tools/task/main.js +233 -0
- package/build/tools/task/single-operations.js +469 -0
- package/build/tools/task/time-tracking.js +575 -0
- package/build/tools/task/utilities.js +310 -0
- package/build/tools/task/workspace-operations.js +258 -0
- package/build/tools/tool-enhancer.js +37 -0
- package/build/tools/utils.js +12 -0
- package/build/tools/workspace-helper.js +44 -0
- package/build/tools/workspace.js +73 -0
- package/build/utils/color-processor.js +183 -0
- package/build/utils/concurrency-utils.js +248 -0
- package/build/utils/date-utils.js +542 -0
- package/build/utils/resolver-utils.js +135 -0
- package/build/utils/sponsor-service.js +93 -0
- package/build/utils/token-utils.js +49 -0
- package/package.json +77 -0
- package/smithery.yaml +23 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* ClickUp MCP Task Utilities
|
|
6
|
+
*
|
|
7
|
+
* This module provides utility functions for task-related operations including
|
|
8
|
+
* data formatting, validation, and resolution of IDs from names.
|
|
9
|
+
*/
|
|
10
|
+
import { formatDueDate } from '../utils.js';
|
|
11
|
+
import { clickUpServices } from '../../services/shared.js';
|
|
12
|
+
import { findListIDByName } from '../../tools/list.js';
|
|
13
|
+
// Use shared services instance for ID resolution
|
|
14
|
+
const { workspace: workspaceService, task: taskService } = clickUpServices;
|
|
15
|
+
//=============================================================================
|
|
16
|
+
// DATA FORMATTING UTILITIES
|
|
17
|
+
//=============================================================================
|
|
18
|
+
/**
|
|
19
|
+
* Formats task data for response
|
|
20
|
+
*/
|
|
21
|
+
export function formatTaskData(task, additional = {}) {
|
|
22
|
+
return {
|
|
23
|
+
id: task.id,
|
|
24
|
+
custom_id: task.custom_id,
|
|
25
|
+
name: task.name,
|
|
26
|
+
text_content: task.text_content,
|
|
27
|
+
description: task.description,
|
|
28
|
+
url: task.url,
|
|
29
|
+
status: task.status?.status || "Unknown",
|
|
30
|
+
status_color: task.status?.color,
|
|
31
|
+
orderindex: task.orderindex,
|
|
32
|
+
date_created: task.date_created,
|
|
33
|
+
date_updated: task.date_updated,
|
|
34
|
+
date_closed: task.date_closed,
|
|
35
|
+
creator: task.creator,
|
|
36
|
+
assignees: task.assignees,
|
|
37
|
+
watchers: task.watchers,
|
|
38
|
+
checklists: task.checklists,
|
|
39
|
+
tags: task.tags,
|
|
40
|
+
parent: task.parent,
|
|
41
|
+
priority: task.priority,
|
|
42
|
+
due_date: task.due_date ? formatDueDate(Number(task.due_date)) : undefined,
|
|
43
|
+
start_date: task.start_date ? formatDueDate(Number(task.start_date)) : undefined,
|
|
44
|
+
time_estimate: task.time_estimate,
|
|
45
|
+
time_spent: task.time_spent,
|
|
46
|
+
custom_fields: task.custom_fields,
|
|
47
|
+
dependencies: task.dependencies,
|
|
48
|
+
linked_tasks: task.linked_tasks,
|
|
49
|
+
team_id: task.team_id,
|
|
50
|
+
list: {
|
|
51
|
+
id: task.list.id,
|
|
52
|
+
name: task.list.name,
|
|
53
|
+
access: task.list.access
|
|
54
|
+
},
|
|
55
|
+
folder: task.folder ? {
|
|
56
|
+
id: task.folder.id,
|
|
57
|
+
name: task.folder.name,
|
|
58
|
+
hidden: task.folder.hidden,
|
|
59
|
+
access: task.folder.access
|
|
60
|
+
} : null,
|
|
61
|
+
space: {
|
|
62
|
+
id: task.space.id,
|
|
63
|
+
name: task.space.name
|
|
64
|
+
},
|
|
65
|
+
...additional
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//=============================================================================
|
|
69
|
+
// TASK ID DETECTION UTILITIES
|
|
70
|
+
//=============================================================================
|
|
71
|
+
/**
|
|
72
|
+
* Detects if a task ID is a custom task ID based on common patterns
|
|
73
|
+
* Custom task IDs typically:
|
|
74
|
+
* - Contain hyphens (e.g., "DEV-1234", "PROJ-456")
|
|
75
|
+
* - Have uppercase prefixes followed by numbers
|
|
76
|
+
* - Are not 9-character alphanumeric strings (regular ClickUp task IDs)
|
|
77
|
+
*
|
|
78
|
+
* @param taskId The task ID to check
|
|
79
|
+
* @returns true if the ID appears to be a custom task ID
|
|
80
|
+
*/
|
|
81
|
+
export function isCustomTaskId(taskId) {
|
|
82
|
+
if (!taskId || typeof taskId !== 'string') {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
// Trim whitespace
|
|
86
|
+
taskId = taskId.trim();
|
|
87
|
+
// Regular ClickUp task IDs are typically 9 characters, alphanumeric
|
|
88
|
+
// Custom task IDs usually have different patterns
|
|
89
|
+
// Check if it's a standard 9-character ClickUp ID (letters and numbers only)
|
|
90
|
+
const standardIdPattern = /^[a-zA-Z0-9]{9}$/;
|
|
91
|
+
if (standardIdPattern.test(taskId)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
// Check for common custom task ID patterns:
|
|
95
|
+
// 1. Contains hyphens (most common pattern: PREFIX-NUMBER)
|
|
96
|
+
if (taskId.includes('-')) {
|
|
97
|
+
// Additional validation: should have letters before hyphen and numbers after
|
|
98
|
+
const hyphenPattern = /^[A-Za-z]+[-][0-9]+$/;
|
|
99
|
+
return hyphenPattern.test(taskId);
|
|
100
|
+
}
|
|
101
|
+
// 2. Contains underscores (another common pattern: PREFIX_NUMBER)
|
|
102
|
+
if (taskId.includes('_')) {
|
|
103
|
+
const underscorePattern = /^[A-Za-z]+[_][0-9]+$/;
|
|
104
|
+
return underscorePattern.test(taskId);
|
|
105
|
+
}
|
|
106
|
+
// 3. Contains uppercase letters followed by numbers (without separators)
|
|
107
|
+
const customIdPattern = /^[A-Z]+\d+$/;
|
|
108
|
+
if (customIdPattern.test(taskId)) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
// 4. Mixed case with numbers but not 9 characters (less common)
|
|
112
|
+
const mixedCasePattern = /^[A-Za-z]+\d+$/;
|
|
113
|
+
if (mixedCasePattern.test(taskId) && taskId.length !== 9) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
// 5. Contains dots (some organizations use PROJECT.TASK format)
|
|
117
|
+
if (taskId.includes('.')) {
|
|
118
|
+
const dotPattern = /^[A-Za-z]+[.][0-9]+$/;
|
|
119
|
+
return dotPattern.test(taskId);
|
|
120
|
+
}
|
|
121
|
+
// If none of the patterns match, assume it's a regular task ID
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Validates task identification parameters
|
|
126
|
+
*
|
|
127
|
+
* @param params - Task identification parameters
|
|
128
|
+
* @param options - Validation options
|
|
129
|
+
* @returns Validation result with error message if any
|
|
130
|
+
*/
|
|
131
|
+
export function validateTaskIdentification(params, options = {}) {
|
|
132
|
+
const { taskId, taskName, customTaskId, listName } = params;
|
|
133
|
+
const { requireTaskId = false, useGlobalLookup = true } = options;
|
|
134
|
+
// If taskId is required, it must be provided
|
|
135
|
+
if (requireTaskId && !taskId) {
|
|
136
|
+
return {
|
|
137
|
+
isValid: false,
|
|
138
|
+
errorMessage: 'Task ID is required for this operation'
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// At least one identification method must be provided
|
|
142
|
+
if (!taskId && !taskName && !customTaskId) {
|
|
143
|
+
return {
|
|
144
|
+
isValid: false,
|
|
145
|
+
errorMessage: 'Either taskId, taskName, or customTaskId must be provided to identify the task'
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// When using taskName without global lookup, listName is required
|
|
149
|
+
if (taskName && !taskId && !customTaskId && !useGlobalLookup && !listName) {
|
|
150
|
+
return {
|
|
151
|
+
isValid: false,
|
|
152
|
+
errorMessage: 'When identifying a task by name, you must also provide the listName parameter'
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return { isValid: true };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Validates list identification parameters
|
|
159
|
+
* Ensures either listId or listName is provided
|
|
160
|
+
*/
|
|
161
|
+
export function validateListIdentification(listId, listName) {
|
|
162
|
+
if (!listId && !listName) {
|
|
163
|
+
throw new Error("Either listId or listName must be provided");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Validates task update data
|
|
168
|
+
* Ensures at least one update field is provided
|
|
169
|
+
*/
|
|
170
|
+
export function validateTaskUpdateData(updateData) {
|
|
171
|
+
// Validate custom_fields if provided
|
|
172
|
+
if (updateData.custom_fields) {
|
|
173
|
+
if (!Array.isArray(updateData.custom_fields)) {
|
|
174
|
+
throw new Error("custom_fields must be an array");
|
|
175
|
+
}
|
|
176
|
+
for (const field of updateData.custom_fields) {
|
|
177
|
+
if (!field.id || field.value === undefined) {
|
|
178
|
+
throw new Error("Each custom field must have both id and value properties");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// Ensure there's at least one field to update
|
|
183
|
+
if (Object.keys(updateData).length === 0) {
|
|
184
|
+
throw new Error("At least one field to update must be provided");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Validate bulk task array and task identification
|
|
189
|
+
* @param tasks Array of tasks to validate
|
|
190
|
+
* @param operation The bulk operation type ('create', 'update', 'move', 'delete')
|
|
191
|
+
*/
|
|
192
|
+
export function validateBulkTasks(tasks, operation = 'update') {
|
|
193
|
+
if (!Array.isArray(tasks) || tasks.length === 0) {
|
|
194
|
+
throw new Error("tasks must be a non-empty array");
|
|
195
|
+
}
|
|
196
|
+
tasks.forEach((task, index) => {
|
|
197
|
+
if (!task || typeof task !== 'object') {
|
|
198
|
+
throw new Error(`Task at index ${index} must be an object`);
|
|
199
|
+
}
|
|
200
|
+
// Skip task identification validation for create operations
|
|
201
|
+
if (operation === 'create') {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
// For bulk operations, require listName when using taskName
|
|
205
|
+
if (task.taskName && !task.listName) {
|
|
206
|
+
throw new Error(`Task at index ${index} using taskName must also provide listName`);
|
|
207
|
+
}
|
|
208
|
+
// At least one identifier is required for non-create operations
|
|
209
|
+
if (!task.taskId && !task.taskName && !task.customTaskId) {
|
|
210
|
+
throw new Error(`Task at index ${index} must provide either taskId, taskName + listName, or customTaskId`);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Parse options for bulk operations
|
|
216
|
+
*/
|
|
217
|
+
export function parseBulkOptions(rawOptions) {
|
|
218
|
+
if (typeof rawOptions === 'string') {
|
|
219
|
+
try {
|
|
220
|
+
return JSON.parse(rawOptions);
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return rawOptions;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Resolves a list ID from either direct ID or name
|
|
230
|
+
* Handles validation and throws appropriate errors
|
|
231
|
+
*/
|
|
232
|
+
export async function resolveListIdWithValidation(listId, listName) {
|
|
233
|
+
// Validate parameters
|
|
234
|
+
validateListIdentification(listId, listName);
|
|
235
|
+
// If listId is provided, use it directly
|
|
236
|
+
if (listId)
|
|
237
|
+
return listId;
|
|
238
|
+
// At this point we know we have listName (validation ensures this)
|
|
239
|
+
const listInfo = await findListIDByName(workspaceService, listName);
|
|
240
|
+
if (!listInfo) {
|
|
241
|
+
throw new Error(`List "${listName}" not found`);
|
|
242
|
+
}
|
|
243
|
+
return listInfo.id;
|
|
244
|
+
}
|
|
245
|
+
//=============================================================================
|
|
246
|
+
// PATH EXTRACTION HELPER FUNCTIONS
|
|
247
|
+
//=============================================================================
|
|
248
|
+
/**
|
|
249
|
+
* Extract path from node to root
|
|
250
|
+
*/
|
|
251
|
+
export function extractPath(node) {
|
|
252
|
+
if (!node)
|
|
253
|
+
return '';
|
|
254
|
+
if (!node.parent)
|
|
255
|
+
return node.name;
|
|
256
|
+
return `${extractPath(node.parent)} > ${node.name}`;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Extract path from root to a specific node
|
|
260
|
+
*/
|
|
261
|
+
export function extractTreePath(root, targetId) {
|
|
262
|
+
if (!root)
|
|
263
|
+
return [];
|
|
264
|
+
// If this node is the target, return it in an array
|
|
265
|
+
if (root.id === targetId) {
|
|
266
|
+
return [root];
|
|
267
|
+
}
|
|
268
|
+
// Check children if they exist
|
|
269
|
+
if (root.children) {
|
|
270
|
+
for (const child of root.children) {
|
|
271
|
+
const path = extractTreePath(child, targetId);
|
|
272
|
+
if (path.length > 0) {
|
|
273
|
+
return [root, ...path];
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// Not found in this branch
|
|
278
|
+
return [];
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Get task ID from various identification methods
|
|
282
|
+
*/
|
|
283
|
+
export async function getTaskId(taskId, taskName, listName, customTaskId, requireId = false) {
|
|
284
|
+
// Validate task identification
|
|
285
|
+
const validationResult = validateTaskIdentification({ taskId, taskName, listName, customTaskId }, { requireTaskId: requireId, useGlobalLookup: true });
|
|
286
|
+
if (!validationResult.isValid) {
|
|
287
|
+
throw new Error(validationResult.errorMessage);
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
const result = await taskService.findTasks({
|
|
291
|
+
taskId,
|
|
292
|
+
customTaskId,
|
|
293
|
+
taskName,
|
|
294
|
+
listName,
|
|
295
|
+
allowMultipleMatches: false,
|
|
296
|
+
useSmartDisambiguation: true,
|
|
297
|
+
includeFullDetails: false
|
|
298
|
+
});
|
|
299
|
+
if (!result || Array.isArray(result)) {
|
|
300
|
+
throw new Error(`Task not found with the provided identification`);
|
|
301
|
+
}
|
|
302
|
+
return result.id;
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
if (error.message.includes('Multiple tasks found')) {
|
|
306
|
+
throw new Error(`Multiple tasks found with name "${taskName}". Please provide list name to disambiguate.`);
|
|
307
|
+
}
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* ClickUp MCP Workspace Task Operations
|
|
6
|
+
*
|
|
7
|
+
* This module defines tools for workspace-wide task operations, including
|
|
8
|
+
* filtering tasks across the entire workspace with tag-based filtering.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Tool definition for getting workspace tasks
|
|
12
|
+
*/
|
|
13
|
+
export const getWorkspaceTasksTool = {
|
|
14
|
+
name: "get_workspace_tasks",
|
|
15
|
+
description: `Purpose: Retrieve tasks from across the entire workspace with powerful filtering options, including tag-based filtering.
|
|
16
|
+
|
|
17
|
+
Valid Usage:
|
|
18
|
+
1. Apply any combination of filters (tags, lists, folders, spaces, statuses, etc.)
|
|
19
|
+
2. Use pagination to manage large result sets
|
|
20
|
+
3. Include subtasks by setting subtasks=true
|
|
21
|
+
|
|
22
|
+
Requirements:
|
|
23
|
+
- At least one filter parameter is REQUIRED (tags, list_ids, folder_ids, space_ids, statuses, assignees, or date filters)
|
|
24
|
+
- Pagination parameters (page, order_by, reverse) alone are not considered filters
|
|
25
|
+
|
|
26
|
+
Notes:
|
|
27
|
+
- Provides workspace-wide task access (unlike get_tasks which only searches in one list)
|
|
28
|
+
- Returns complete task details including descriptions, assignees, custom fields, and all metadata
|
|
29
|
+
- Tag filtering is especially useful for cross-list organization (e.g., "project-x", "blocker", "needs-review")
|
|
30
|
+
- Combine multiple filters to narrow down your search scope
|
|
31
|
+
- Use pagination for large result sets
|
|
32
|
+
- Set subtasks=true to include subtask details in the response
|
|
33
|
+
IMPORTANT: subtasks=true enables subtasks to appear in results, but subtasks must still match your other filter criteria (tags, lists, etc.) to be returned. To see all subtasks of a specific task regardless of filters, use the get_task tool with subtasks=true instead.
|
|
34
|
+
- Use the detail_level parameter to control the amount of data returned:
|
|
35
|
+
- "summary": Returns lightweight task data (name, status, list, tags)
|
|
36
|
+
- "detailed": Returns complete task data with all fields (DEFAULT if not specified)
|
|
37
|
+
- Responses exceeding 50,000 tokens automatically switch to summary format to avoid hitting LLM token limits
|
|
38
|
+
- **Enhanced List Filtering**: When list_ids are provided, the tool leverages ClickUp's Views API to include tasks that are *associated with* the specified lists, including tasks that have been added to multiple lists. This provides comprehensive coverage of all tasks related to your specified lists, not just tasks that were originally created in those lists.
|
|
39
|
+
`,
|
|
40
|
+
parameters: {
|
|
41
|
+
type: 'object',
|
|
42
|
+
properties: {
|
|
43
|
+
tags: {
|
|
44
|
+
type: 'array',
|
|
45
|
+
items: { type: 'string' },
|
|
46
|
+
description: 'Filter tasks by tag names. Only tasks with ALL specified tags will be returned.'
|
|
47
|
+
},
|
|
48
|
+
list_ids: {
|
|
49
|
+
type: 'array',
|
|
50
|
+
items: { type: 'string' },
|
|
51
|
+
description: 'Filter tasks by list IDs. Narrows the search to specific lists.'
|
|
52
|
+
},
|
|
53
|
+
folder_ids: {
|
|
54
|
+
type: 'array',
|
|
55
|
+
items: { type: 'string' },
|
|
56
|
+
description: 'Filter tasks by folder IDs. Narrows the search to specific folders.'
|
|
57
|
+
},
|
|
58
|
+
space_ids: {
|
|
59
|
+
type: 'array',
|
|
60
|
+
items: { type: 'string' },
|
|
61
|
+
description: 'Filter tasks by space IDs. Narrows the search to specific spaces.'
|
|
62
|
+
},
|
|
63
|
+
statuses: {
|
|
64
|
+
type: 'array',
|
|
65
|
+
items: { type: 'string' },
|
|
66
|
+
description: 'Filter tasks by status names (e.g., [\'To Do\', \'In Progress\']).'
|
|
67
|
+
},
|
|
68
|
+
assignees: {
|
|
69
|
+
type: 'array',
|
|
70
|
+
items: { type: 'string' },
|
|
71
|
+
description: 'Filter tasks by assignee IDs.'
|
|
72
|
+
},
|
|
73
|
+
date_created_gt: {
|
|
74
|
+
type: 'number',
|
|
75
|
+
description: 'Filter for tasks created after this timestamp.'
|
|
76
|
+
},
|
|
77
|
+
date_created_lt: {
|
|
78
|
+
type: 'number',
|
|
79
|
+
description: 'Filter for tasks created before this timestamp.'
|
|
80
|
+
},
|
|
81
|
+
date_updated_gt: {
|
|
82
|
+
type: 'number',
|
|
83
|
+
description: 'Filter for tasks updated after this timestamp.'
|
|
84
|
+
},
|
|
85
|
+
date_updated_lt: {
|
|
86
|
+
type: 'number',
|
|
87
|
+
description: 'Filter for tasks updated before this timestamp.'
|
|
88
|
+
},
|
|
89
|
+
due_date_gt: {
|
|
90
|
+
type: 'number',
|
|
91
|
+
description: 'Filter for tasks with due date greater than this timestamp.'
|
|
92
|
+
},
|
|
93
|
+
due_date_lt: {
|
|
94
|
+
type: 'number',
|
|
95
|
+
description: 'Filter for tasks with due date less than this timestamp.'
|
|
96
|
+
},
|
|
97
|
+
include_closed: {
|
|
98
|
+
type: 'boolean',
|
|
99
|
+
description: 'Include closed tasks in the results.'
|
|
100
|
+
},
|
|
101
|
+
include_archived_lists: {
|
|
102
|
+
type: 'boolean',
|
|
103
|
+
description: 'Include tasks from archived lists.'
|
|
104
|
+
},
|
|
105
|
+
include_closed_lists: {
|
|
106
|
+
type: 'boolean',
|
|
107
|
+
description: 'Include tasks from closed lists.'
|
|
108
|
+
},
|
|
109
|
+
archived: {
|
|
110
|
+
type: 'boolean',
|
|
111
|
+
description: 'Include archived tasks in the results.'
|
|
112
|
+
},
|
|
113
|
+
order_by: {
|
|
114
|
+
type: 'string',
|
|
115
|
+
enum: ['id', 'created', 'updated', 'due_date'],
|
|
116
|
+
description: 'Sort field for ordering results.'
|
|
117
|
+
},
|
|
118
|
+
reverse: {
|
|
119
|
+
type: 'boolean',
|
|
120
|
+
description: 'Reverse sort order (descending).'
|
|
121
|
+
},
|
|
122
|
+
page: {
|
|
123
|
+
type: 'number',
|
|
124
|
+
description: 'Page number for pagination (0-based).'
|
|
125
|
+
},
|
|
126
|
+
detail_level: {
|
|
127
|
+
type: 'string',
|
|
128
|
+
enum: ['summary', 'detailed'],
|
|
129
|
+
description: 'Level of detail to return. Use summary for lightweight responses or detailed for full task data. If not specified, defaults to "detailed".'
|
|
130
|
+
},
|
|
131
|
+
subtasks: {
|
|
132
|
+
type: 'boolean',
|
|
133
|
+
description: 'Include subtasks in the response. Set to true to retrieve subtask details for all returned tasks. Note: subtasks must still match your other filter criteria to appear in results.'
|
|
134
|
+
},
|
|
135
|
+
include_subtasks: {
|
|
136
|
+
type: 'boolean',
|
|
137
|
+
description: 'Alternative parameter for including subtasks (legacy support).'
|
|
138
|
+
},
|
|
139
|
+
include_compact_time_entries: {
|
|
140
|
+
type: 'boolean',
|
|
141
|
+
description: 'Include compact time entry data in the response.'
|
|
142
|
+
},
|
|
143
|
+
custom_fields: {
|
|
144
|
+
type: 'object',
|
|
145
|
+
description: 'Filter by custom field values. Provide as key-value pairs where keys are custom field IDs.'
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
inputSchema: {
|
|
150
|
+
type: 'object',
|
|
151
|
+
properties: {
|
|
152
|
+
tags: {
|
|
153
|
+
type: 'array',
|
|
154
|
+
items: { type: 'string' },
|
|
155
|
+
description: 'Filter tasks by tag names. Only tasks with ALL specified tags will be returned.'
|
|
156
|
+
},
|
|
157
|
+
list_ids: {
|
|
158
|
+
type: 'array',
|
|
159
|
+
items: { type: 'string' },
|
|
160
|
+
description: 'Filter tasks by list IDs. Narrows the search to specific lists.'
|
|
161
|
+
},
|
|
162
|
+
folder_ids: {
|
|
163
|
+
type: 'array',
|
|
164
|
+
items: { type: 'string' },
|
|
165
|
+
description: 'Filter tasks by folder IDs. Narrows the search to specific folders.'
|
|
166
|
+
},
|
|
167
|
+
space_ids: {
|
|
168
|
+
type: 'array',
|
|
169
|
+
items: { type: 'string' },
|
|
170
|
+
description: 'Filter tasks by space IDs. Narrows the search to specific spaces.'
|
|
171
|
+
},
|
|
172
|
+
statuses: {
|
|
173
|
+
type: 'array',
|
|
174
|
+
items: { type: 'string' },
|
|
175
|
+
description: 'Filter tasks by status names (e.g., [\'To Do\', \'In Progress\']).'
|
|
176
|
+
},
|
|
177
|
+
assignees: {
|
|
178
|
+
type: 'array',
|
|
179
|
+
items: { type: 'string' },
|
|
180
|
+
description: 'Filter tasks by assignee IDs.'
|
|
181
|
+
},
|
|
182
|
+
date_created_gt: {
|
|
183
|
+
type: 'number',
|
|
184
|
+
description: 'Filter for tasks created after this timestamp.'
|
|
185
|
+
},
|
|
186
|
+
date_created_lt: {
|
|
187
|
+
type: 'number',
|
|
188
|
+
description: 'Filter for tasks created before this timestamp.'
|
|
189
|
+
},
|
|
190
|
+
date_updated_gt: {
|
|
191
|
+
type: 'number',
|
|
192
|
+
description: 'Filter for tasks updated after this timestamp.'
|
|
193
|
+
},
|
|
194
|
+
date_updated_lt: {
|
|
195
|
+
type: 'number',
|
|
196
|
+
description: 'Filter for tasks updated before this timestamp.'
|
|
197
|
+
},
|
|
198
|
+
due_date_gt: {
|
|
199
|
+
type: 'number',
|
|
200
|
+
description: 'Filter for tasks with due date greater than this timestamp.'
|
|
201
|
+
},
|
|
202
|
+
due_date_lt: {
|
|
203
|
+
type: 'number',
|
|
204
|
+
description: 'Filter for tasks with due date less than this timestamp.'
|
|
205
|
+
},
|
|
206
|
+
include_closed: {
|
|
207
|
+
type: 'boolean',
|
|
208
|
+
description: 'Include closed tasks in the results.'
|
|
209
|
+
},
|
|
210
|
+
include_archived_lists: {
|
|
211
|
+
type: 'boolean',
|
|
212
|
+
description: 'Include tasks from archived lists.'
|
|
213
|
+
},
|
|
214
|
+
include_closed_lists: {
|
|
215
|
+
type: 'boolean',
|
|
216
|
+
description: 'Include tasks from closed lists.'
|
|
217
|
+
},
|
|
218
|
+
archived: {
|
|
219
|
+
type: 'boolean',
|
|
220
|
+
description: 'Include archived tasks in the results.'
|
|
221
|
+
},
|
|
222
|
+
order_by: {
|
|
223
|
+
type: 'string',
|
|
224
|
+
enum: ['id', 'created', 'updated', 'due_date'],
|
|
225
|
+
description: 'Sort field for ordering results.'
|
|
226
|
+
},
|
|
227
|
+
reverse: {
|
|
228
|
+
type: 'boolean',
|
|
229
|
+
description: 'Reverse sort order (descending).'
|
|
230
|
+
},
|
|
231
|
+
page: {
|
|
232
|
+
type: 'number',
|
|
233
|
+
description: 'Page number for pagination (0-based).'
|
|
234
|
+
},
|
|
235
|
+
detail_level: {
|
|
236
|
+
type: 'string',
|
|
237
|
+
enum: ['summary', 'detailed'],
|
|
238
|
+
description: 'Level of detail to return. Use summary for lightweight responses or detailed for full task data. If not specified, defaults to "detailed".'
|
|
239
|
+
},
|
|
240
|
+
subtasks: {
|
|
241
|
+
type: 'boolean',
|
|
242
|
+
description: 'Include subtasks in the response. Set to true to retrieve subtask details for all returned tasks. Note: subtasks must still match your other filter criteria to appear in results.'
|
|
243
|
+
},
|
|
244
|
+
include_subtasks: {
|
|
245
|
+
type: 'boolean',
|
|
246
|
+
description: 'Alternative parameter for including subtasks (legacy support).'
|
|
247
|
+
},
|
|
248
|
+
include_compact_time_entries: {
|
|
249
|
+
type: 'boolean',
|
|
250
|
+
description: 'Include compact time entry data in the response.'
|
|
251
|
+
},
|
|
252
|
+
custom_fields: {
|
|
253
|
+
type: 'object',
|
|
254
|
+
description: 'Filter by custom field values. Provide as key-value pairs where keys are custom field IDs.'
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Tool Enhancer Utilities
|
|
6
|
+
*
|
|
7
|
+
* This module provides utilities to enhance existing tool definitions with workspace support.
|
|
8
|
+
*/
|
|
9
|
+
import { workspaceParameter } from './workspace-helper.js';
|
|
10
|
+
/**
|
|
11
|
+
* Enhance a tool definition with workspace parameter
|
|
12
|
+
* @param tool - Original tool definition
|
|
13
|
+
* @returns Enhanced tool with workspace parameter
|
|
14
|
+
*/
|
|
15
|
+
export function enhanceToolWithWorkspace(tool) {
|
|
16
|
+
// Clone the tool to avoid modifying the original
|
|
17
|
+
const enhanced = {
|
|
18
|
+
...tool,
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
...(tool.inputSchema || {}),
|
|
22
|
+
properties: {
|
|
23
|
+
...(tool.inputSchema?.properties || {}),
|
|
24
|
+
...workspaceParameter
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
return enhanced;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Enhance multiple tools with workspace parameter
|
|
32
|
+
* @param tools - Array of tool definitions
|
|
33
|
+
* @returns Array of enhanced tools
|
|
34
|
+
*/
|
|
35
|
+
export function enhanceToolsWithWorkspace(tools) {
|
|
36
|
+
return tools.map(enhanceToolWithWorkspace);
|
|
37
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Utility functions for ClickUp MCP tools
|
|
6
|
+
*
|
|
7
|
+
* Re-exports specialized utilities from dedicated modules.
|
|
8
|
+
*/
|
|
9
|
+
// Re-export date utilities
|
|
10
|
+
export { getRelativeTimestamp, parseDueDate, formatDueDate, formatRelativeTime } from '../utils/date-utils.js';
|
|
11
|
+
// Re-export resolver utilities
|
|
12
|
+
export { resolveListId } from '../utils/resolver-utils.js';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Workspace Helper Utilities
|
|
6
|
+
*
|
|
7
|
+
* This module provides helper functions for workspace-aware tool operations.
|
|
8
|
+
*/
|
|
9
|
+
import { getClickUpServices } from '../services/shared.js';
|
|
10
|
+
import { getAvailableWorkspaces } from '../config.js';
|
|
11
|
+
/**
|
|
12
|
+
* Common workspace parameter schema for all tools
|
|
13
|
+
*/
|
|
14
|
+
export const workspaceParameter = {
|
|
15
|
+
workspace: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'Workspace identifier (optional, uses default if not specified)',
|
|
18
|
+
optional: true
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Get services for a specific workspace from tool parameters
|
|
23
|
+
* @param params - Tool parameters that may contain workspace property
|
|
24
|
+
* @returns ClickUp services instance for the specified or default workspace
|
|
25
|
+
*/
|
|
26
|
+
export function getServicesForWorkspace(params) {
|
|
27
|
+
try {
|
|
28
|
+
return getClickUpServices(params.workspace);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
// Enhance error message with available workspaces
|
|
32
|
+
const available = getAvailableWorkspaces().join(', ');
|
|
33
|
+
throw new Error(`${error.message}\nAvailable workspaces: ${available}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Extract workspace ID from parameters and remove it before passing to service methods
|
|
38
|
+
* @param params - Tool parameters
|
|
39
|
+
* @returns Tuple of [workspace ID or undefined, params without workspace]
|
|
40
|
+
*/
|
|
41
|
+
export function extractWorkspace(params) {
|
|
42
|
+
const { workspace, ...rest } = params;
|
|
43
|
+
return [workspace, rest];
|
|
44
|
+
}
|