@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.
Files changed (56) hide show
  1. package/Dockerfile +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +470 -0
  4. package/build/config.js +237 -0
  5. package/build/index.js +87 -0
  6. package/build/logger.js +163 -0
  7. package/build/middleware/security.js +231 -0
  8. package/build/server.js +288 -0
  9. package/build/services/clickup/base.js +432 -0
  10. package/build/services/clickup/bulk.js +180 -0
  11. package/build/services/clickup/document.js +159 -0
  12. package/build/services/clickup/folder.js +136 -0
  13. package/build/services/clickup/index.js +76 -0
  14. package/build/services/clickup/list.js +191 -0
  15. package/build/services/clickup/tag.js +239 -0
  16. package/build/services/clickup/task/index.js +32 -0
  17. package/build/services/clickup/task/task-attachments.js +105 -0
  18. package/build/services/clickup/task/task-comments.js +114 -0
  19. package/build/services/clickup/task/task-core.js +604 -0
  20. package/build/services/clickup/task/task-custom-fields.js +107 -0
  21. package/build/services/clickup/task/task-search.js +986 -0
  22. package/build/services/clickup/task/task-service.js +104 -0
  23. package/build/services/clickup/task/task-tags.js +113 -0
  24. package/build/services/clickup/time.js +244 -0
  25. package/build/services/clickup/types.js +33 -0
  26. package/build/services/clickup/workspace.js +397 -0
  27. package/build/services/shared.js +61 -0
  28. package/build/sse_server.js +277 -0
  29. package/build/tools/documents.js +489 -0
  30. package/build/tools/folder.js +331 -0
  31. package/build/tools/index.js +16 -0
  32. package/build/tools/list.js +428 -0
  33. package/build/tools/member.js +106 -0
  34. package/build/tools/tag.js +833 -0
  35. package/build/tools/task/attachments.js +357 -0
  36. package/build/tools/task/attachments.types.js +9 -0
  37. package/build/tools/task/bulk-operations.js +338 -0
  38. package/build/tools/task/handlers.js +919 -0
  39. package/build/tools/task/index.js +30 -0
  40. package/build/tools/task/main.js +233 -0
  41. package/build/tools/task/single-operations.js +469 -0
  42. package/build/tools/task/time-tracking.js +575 -0
  43. package/build/tools/task/utilities.js +310 -0
  44. package/build/tools/task/workspace-operations.js +258 -0
  45. package/build/tools/tool-enhancer.js +37 -0
  46. package/build/tools/utils.js +12 -0
  47. package/build/tools/workspace-helper.js +44 -0
  48. package/build/tools/workspace.js +73 -0
  49. package/build/utils/color-processor.js +183 -0
  50. package/build/utils/concurrency-utils.js +248 -0
  51. package/build/utils/date-utils.js +542 -0
  52. package/build/utils/resolver-utils.js +135 -0
  53. package/build/utils/sponsor-service.js +93 -0
  54. package/build/utils/token-utils.js +49 -0
  55. package/package.json +77 -0
  56. 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
+ }