@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,71 @@
1
+ import Fuse from 'fuse.js';
2
+ /**
3
+ * Checks if a string looks like a valid ClickUp task ID
4
+ * Valid task IDs are 6-9 characters long and contain only alphanumeric characters
5
+ */
6
+ export declare function isTaskId(str: string): boolean;
7
+ /**
8
+ * Get current authenticated user information from ClickUp API
9
+ * Caches the promise to prevent race conditions on concurrent calls
10
+ */
11
+ export declare function getCurrentUser(): Promise<any>;
12
+ export { downloadImages } from "./image-processing";
13
+ /**
14
+ * Function to get space details, using a cache to avoid redundant fetches
15
+ */
16
+ export declare function getSpaceDetails(spaceId: string): Promise<any>;
17
+ /**
18
+ * Get or create a task search index with specified filters
19
+ * Caches promises to prevent race conditions on concurrent calls
20
+ */
21
+ export declare function getTaskSearchIndex(space_ids?: string[], list_ids?: string[], assignees?: string[]): Promise<Fuse<any> | null>;
22
+ /**
23
+ * Generate a ClickUp task URL from a task ID
24
+ */
25
+ export declare function generateTaskUrl(taskId: string): string;
26
+ /**
27
+ * Generate a ClickUp list URL from a list ID
28
+ */
29
+ export declare function generateListUrl(listId: string): string;
30
+ /**
31
+ * Generate a ClickUp space URL from a space ID
32
+ */
33
+ export declare function generateSpaceUrl(spaceId: string): string;
34
+ /**
35
+ * Generate a ClickUp folder URL from a folder ID
36
+ */
37
+ export declare function generateFolderUrl(folderId: string): string;
38
+ /**
39
+ * Generate a ClickUp document URL from a document ID and optional page ID
40
+ */
41
+ export declare function generateDocumentUrl(docId: string, pageId?: string): string;
42
+ /**
43
+ * Format space content as tree structure
44
+ * Shared function used by both searchSpaces tool and space resources
45
+ */
46
+ export declare function formatSpaceTree(space: any, lists: any[], folders: any[], documents: any[]): string;
47
+ /**
48
+ * Get or refresh the space search index
49
+ * Caches promise to prevent race conditions on concurrent calls
50
+ */
51
+ export declare function getSpaceSearchIndex(): Promise<Fuse<any> | null>;
52
+ /**
53
+ * Get lists, folders, and documents for a specific space with caching
54
+ */
55
+ export declare function getSpaceContent(spaceId: string): Promise<{
56
+ lists: any[];
57
+ folders: any[];
58
+ documents: any[];
59
+ }>;
60
+ /**
61
+ * Gets all team members from ClickUp API with caching
62
+ */
63
+ export declare function getAllTeamMembers(): Promise<string[]>;
64
+ /**
65
+ * Performs multi-term search with aggressive boosting for items matching multiple terms
66
+ * @param searchIndex Fuse search index to search within
67
+ * @param terms Array of search terms
68
+ * @returns Array of items sorted by relevance (multi-term matches ranked higher)
69
+ */
70
+ export declare function performMultiTermSearch<T>(searchIndex: Fuse<T>, terms: string[]): Promise<T[]>;
71
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/shared/utils.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,SAAS,CAAC;AAI3B;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAG7C;AAKD;;;GAGG;AACH,wBAAsB,cAAc,iBA6BnC;AAGD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIpD;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CA0B7D;AAKD;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,CAAC,EAAE,MAAM,EAAE,EACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,EACnB,SAAS,CAAC,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CA+B3B;AAkED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAK1E;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,MAAM,CA4ElG;AAKD;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CA+CrE;AAKD;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IAAC,OAAO,EAAE,GAAG,EAAE,CAAC;IAAC,SAAS,EAAE,GAAG,EAAE,CAAA;CAAE,CAAC,CAgFlH;AAKD;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CA+C3D;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,CAAC,EAC5C,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,EACpB,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,CAAC,EAAE,CAAC,CA8Dd"}
@@ -0,0 +1,508 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.downloadImages = void 0;
7
+ exports.isTaskId = isTaskId;
8
+ exports.getCurrentUser = getCurrentUser;
9
+ exports.getSpaceDetails = getSpaceDetails;
10
+ exports.getTaskSearchIndex = getTaskSearchIndex;
11
+ exports.generateTaskUrl = generateTaskUrl;
12
+ exports.generateListUrl = generateListUrl;
13
+ exports.generateSpaceUrl = generateSpaceUrl;
14
+ exports.generateFolderUrl = generateFolderUrl;
15
+ exports.generateDocumentUrl = generateDocumentUrl;
16
+ exports.formatSpaceTree = formatSpaceTree;
17
+ exports.getSpaceSearchIndex = getSpaceSearchIndex;
18
+ exports.getSpaceContent = getSpaceContent;
19
+ exports.getAllTeamMembers = getAllTeamMembers;
20
+ exports.performMultiTermSearch = performMultiTermSearch;
21
+ const config_1 = require("./config");
22
+ const fuse_js_1 = __importDefault(require("fuse.js"));
23
+ const GLOBAL_REFRESH_INTERVAL = 60000; // 60 seconds - that is the rate limit time frame
24
+ /**
25
+ * Checks if a string looks like a valid ClickUp task ID
26
+ * Valid task IDs are 6-9 characters long and contain only alphanumeric characters
27
+ */
28
+ function isTaskId(str) {
29
+ // Task IDs are 6-9 characters long and contain only alphanumeric characters
30
+ return /^[a-z0-9]{6,9}$/i.test(str);
31
+ }
32
+ // Cache for current user info to avoid repeated API calls and race conditions
33
+ let cachedUserPromise = null;
34
+ /**
35
+ * Get current authenticated user information from ClickUp API
36
+ * Caches the promise to prevent race conditions on concurrent calls
37
+ */
38
+ async function getCurrentUser() {
39
+ // Return cached promise if available
40
+ if (cachedUserPromise) {
41
+ return cachedUserPromise;
42
+ }
43
+ // Create the fetch promise
44
+ const fetchPromise = (async () => {
45
+ const userResponse = await fetch("https://api.clickup.com/api/v2/user", {
46
+ headers: { Authorization: config_1.CONFIG.apiKey },
47
+ });
48
+ if (!userResponse.ok) {
49
+ throw new Error(`Error fetching user info: ${userResponse.status} ${userResponse.statusText}`);
50
+ }
51
+ return await userResponse.json();
52
+ })();
53
+ // Cache the promise
54
+ cachedUserPromise = fetchPromise;
55
+ // Auto-cleanup after 60 seconds
56
+ setTimeout(() => {
57
+ cachedUserPromise = null;
58
+ console.error(`Auto-cleaned user data cache`);
59
+ }, GLOBAL_REFRESH_INTERVAL);
60
+ return fetchPromise;
61
+ }
62
+ // Re-export image processing functions for backward compatibility
63
+ var image_processing_1 = require("./image-processing");
64
+ Object.defineProperty(exports, "downloadImages", { enumerable: true, get: function () { return image_processing_1.downloadImages; } });
65
+ const spaceCache = new Map(); // Global cache for space details promises
66
+ /**
67
+ * Function to get space details, using a cache to avoid redundant fetches
68
+ */
69
+ function getSpaceDetails(spaceId) {
70
+ if (!spaceId) {
71
+ return Promise.reject(new Error('Invalid space ID'));
72
+ }
73
+ const cachedSpace = spaceCache.get(spaceId);
74
+ if (cachedSpace) {
75
+ return cachedSpace;
76
+ }
77
+ const fetchPromise = fetch(`https://api.clickup.com/api/v2/space/${spaceId}`, { headers: { Authorization: config_1.CONFIG.apiKey } })
78
+ .then(res => {
79
+ if (!res.ok) {
80
+ throw new Error(`Error fetching space ${spaceId}: ${res.status}`);
81
+ }
82
+ return res.json();
83
+ })
84
+ .catch(error => {
85
+ console.error(`Network error fetching space ${spaceId}:`, error);
86
+ throw new Error(`Error fetching space ${spaceId}: ${error}`);
87
+ });
88
+ spaceCache.set(spaceId, fetchPromise);
89
+ return fetchPromise;
90
+ }
91
+ // Task search index management - cache promises to prevent race conditions
92
+ const taskIndices = new Map();
93
+ /**
94
+ * Get or create a task search index with specified filters
95
+ * Caches promises to prevent race conditions on concurrent calls
96
+ */
97
+ async function getTaskSearchIndex(space_ids, list_ids, assignees) {
98
+ // Create cache key from sorted filter arrays
99
+ const key = JSON.stringify({
100
+ space_ids: space_ids?.sort(),
101
+ list_ids: list_ids?.sort(),
102
+ assignees: assignees?.sort()
103
+ });
104
+ // Check for existing valid index promise
105
+ const cachedPromise = taskIndices.get(key);
106
+ if (cachedPromise) {
107
+ return cachedPromise;
108
+ }
109
+ // Create the fetch promise
110
+ const fetchPromise = (async () => {
111
+ console.error(`Refreshing task index for filters: ${key}`);
112
+ const tasks = await fetchTasks(space_ids, list_ids, assignees);
113
+ const index = createFuseIndex(tasks);
114
+ console.error(`Task index created with ${tasks.length} tasks`);
115
+ return index;
116
+ })();
117
+ // Store promise with auto-cleanup
118
+ taskIndices.set(key, fetchPromise);
119
+ setTimeout(() => {
120
+ taskIndices.delete(key);
121
+ console.error(`Auto-cleaned index for filters: ${key}`);
122
+ }, GLOBAL_REFRESH_INTERVAL);
123
+ return fetchPromise;
124
+ }
125
+ /**
126
+ * Fetch tasks using team endpoint with dynamic filters
127
+ */
128
+ async function fetchTasks(space_ids, list_ids, assignees) {
129
+ const queryParams = ['order_by=updated', 'subtasks=true'];
130
+ // Add filter parameters
131
+ if (space_ids?.length) {
132
+ space_ids.forEach(id => queryParams.push(`space_ids[]=${id}`));
133
+ }
134
+ if (list_ids?.length) {
135
+ list_ids.forEach(id => queryParams.push(`list_ids[]=${id}`));
136
+ }
137
+ if (assignees?.length) {
138
+ assignees.forEach(id => queryParams.push(`assignees[]=${id}`));
139
+ }
140
+ const queryString = queryParams.join('&');
141
+ // Fetch multiple pages in parallel
142
+ const maxPages = space_ids?.length || list_ids?.length || assignees?.length ? 10 : 30; // Fewer pages for filtered searches
143
+ const taskListsPromises = [...Array(maxPages)].map(async (_, i) => {
144
+ const url = `https://api.clickup.com/api/v2/team/${config_1.CONFIG.teamId}/task?${queryString}&page=${i}`;
145
+ try {
146
+ const res = await fetch(url, { headers: { Authorization: config_1.CONFIG.apiKey } });
147
+ return await res.json();
148
+ }
149
+ catch (e) {
150
+ console.error(`Error fetching page ${i}:`, e);
151
+ return { tasks: [] };
152
+ }
153
+ });
154
+ const taskLists = await Promise.all(taskListsPromises);
155
+ return taskLists.flatMap(taskList => taskList.tasks || []);
156
+ }
157
+ /**
158
+ * Create a Fuse index from tasks array
159
+ */
160
+ function createFuseIndex(tasks) {
161
+ return new fuse_js_1.default(tasks, {
162
+ keys: [
163
+ { name: 'name', weight: 0.7 },
164
+ { name: 'id', weight: 0.6 },
165
+ { name: 'text_content', weight: 0.5 },
166
+ { name: 'tags.name', weight: 0.4 },
167
+ { name: 'assignees.username', weight: 0.4 },
168
+ { name: 'list.name', weight: 0.3 },
169
+ { name: 'folder.name', weight: 0.2 },
170
+ { name: 'space.name', weight: 0.1 }
171
+ ],
172
+ findAllMatches: true,
173
+ includeScore: true,
174
+ minMatchCharLength: 2,
175
+ threshold: 0.4,
176
+ });
177
+ }
178
+ // ===== LINK UTILITIES =====
179
+ /**
180
+ * Generate a ClickUp task URL from a task ID
181
+ */
182
+ function generateTaskUrl(taskId) {
183
+ return `https://app.clickup.com/t/${taskId}`;
184
+ }
185
+ /**
186
+ * Generate a ClickUp list URL from a list ID
187
+ */
188
+ function generateListUrl(listId) {
189
+ return `https://app.clickup.com/${config_1.CONFIG.teamId}/v/li/${listId}`;
190
+ }
191
+ /**
192
+ * Generate a ClickUp space URL from a space ID
193
+ */
194
+ function generateSpaceUrl(spaceId) {
195
+ return `https://app.clickup.com/${config_1.CONFIG.teamId}/v/s/${spaceId}`;
196
+ }
197
+ /**
198
+ * Generate a ClickUp folder URL from a folder ID
199
+ */
200
+ function generateFolderUrl(folderId) {
201
+ return `https://app.clickup.com/${config_1.CONFIG.teamId}/v/f/${folderId}`;
202
+ }
203
+ /**
204
+ * Generate a ClickUp document URL from a document ID and optional page ID
205
+ */
206
+ function generateDocumentUrl(docId, pageId) {
207
+ if (pageId) {
208
+ return `https://app.clickup.com/${config_1.CONFIG.teamId}/v/dc/${docId}/${pageId}`;
209
+ }
210
+ return `https://app.clickup.com/${config_1.CONFIG.teamId}/v/dc/${docId}`;
211
+ }
212
+ /**
213
+ * Format space content as tree structure
214
+ * Shared function used by both searchSpaces tool and space resources
215
+ */
216
+ function formatSpaceTree(space, lists, folders, documents) {
217
+ const spaceLines = [];
218
+ const totalLists = lists.length + folders.reduce((sum, f) => sum + (f.lists?.length || 0), 0);
219
+ // Space header
220
+ spaceLines.push(`🏢 SPACE: ${space.name} (space_id: ${space.id}${space.private ? ', private' : ''}${space.archived ? ', archived' : ''}) ${generateSpaceUrl(space.id)}`, ` ${totalLists} lists, ${folders.length} folders, ${documents.length} documents`);
221
+ // Create a tree structure
222
+ const hasDirectLists = lists.length > 0;
223
+ const hasFolders = folders.length > 0;
224
+ const hasDocuments = documents.length > 0;
225
+ // Direct lists (not in folders)
226
+ if (hasDirectLists) {
227
+ lists.forEach((list, listIndex) => {
228
+ const isLastDirectList = listIndex === lists.length - 1;
229
+ const isLastOverall = !hasFolders && !hasDocuments && isLastDirectList;
230
+ const treeChar = isLastOverall ? '└──' : '├──';
231
+ const extraInfo = [
232
+ ...(list.task_count ? [`${list.task_count} tasks`] : []),
233
+ ...(list.private ? ['private'] : []),
234
+ ...(list.archived ? ['archived'] : [])
235
+ ].join(', ');
236
+ const listLine = `${treeChar} 📝 ${list.name} (list_id: ${list.id}${extraInfo ? `, ${extraInfo}` : ''}) ${generateListUrl(list.id)}`;
237
+ spaceLines.push(listLine);
238
+ });
239
+ }
240
+ // Folders and their lists
241
+ if (hasFolders) {
242
+ folders.forEach((folder, folderIndex) => {
243
+ const isLastFolder = folderIndex === folders.length - 1;
244
+ const isLastOverall = !hasDocuments && isLastFolder;
245
+ const folderTreeChar = isLastOverall ? '└──' : '├──';
246
+ const folderContinuation = isLastOverall ? ' ' : '│ ';
247
+ const folderExtraInfo = [
248
+ ...(folder.lists?.length ? [`${folder.lists.length} lists`] : []),
249
+ ...(folder.private ? ['private'] : []),
250
+ ...(folder.archived ? ['archived'] : [])
251
+ ].join(', ');
252
+ const folderLine = `${folderTreeChar} 📂 ${folder.name} (folder_id: ${folder.id}${folderExtraInfo ? `, ${folderExtraInfo}` : ''}) ${generateFolderUrl(folder.id)}`;
253
+ spaceLines.push(folderLine);
254
+ // Lists within this folder
255
+ if (folder.lists && folder.lists.length > 0) {
256
+ folder.lists.forEach((list, listIndex) => {
257
+ const isLastListInFolder = listIndex === folder.lists.length - 1;
258
+ const listTreeChar = isLastListInFolder ? '└──' : '├──';
259
+ const listExtraInfo = [
260
+ ...(list.task_count ? [`${list.task_count} tasks`] : []),
261
+ ...(list.private ? ['private'] : []),
262
+ ...(list.archived ? ['archived'] : [])
263
+ ].join(', ');
264
+ const listLine = `${folderContinuation}${listTreeChar} 📝 ${list.name} (list_id: ${list.id}${listExtraInfo ? `, ${listExtraInfo}` : ''}) ${generateListUrl(list.id)}`;
265
+ spaceLines.push(listLine);
266
+ });
267
+ }
268
+ });
269
+ }
270
+ // Documents attached to this space
271
+ if (hasDocuments) {
272
+ documents.forEach((document, docIndex) => {
273
+ const isLastDocument = docIndex === documents.length - 1;
274
+ const docTreeChar = isLastDocument ? '└──' : '├──';
275
+ const docLine = `${docTreeChar} 📄 ${document.name} (doc_id: ${document.id}) ${generateDocumentUrl(document.id)}`;
276
+ spaceLines.push(docLine);
277
+ });
278
+ }
279
+ return spaceLines.join('\n');
280
+ }
281
+ // Space search index cache - cache promise to prevent race conditions
282
+ let spaceSearchIndexPromise = null;
283
+ /**
284
+ * Get or refresh the space search index
285
+ * Caches promise to prevent race conditions on concurrent calls
286
+ */
287
+ async function getSpaceSearchIndex() {
288
+ // Return cached promise if available
289
+ if (spaceSearchIndexPromise) {
290
+ return spaceSearchIndexPromise;
291
+ }
292
+ // Create the fetch promise
293
+ const fetchPromise = (async () => {
294
+ try {
295
+ const url = `https://api.clickup.com/api/v2/team/${config_1.CONFIG.teamId}/space`;
296
+ const response = await fetch(url, {
297
+ headers: { Authorization: config_1.CONFIG.apiKey },
298
+ });
299
+ if (!response.ok) {
300
+ throw new Error(`Error fetching spaces: ${response.status} ${response.statusText}`);
301
+ }
302
+ const data = await response.json();
303
+ const spacesData = data.spaces || [];
304
+ // Create Fuse search index
305
+ return new fuse_js_1.default(spacesData, {
306
+ keys: [
307
+ { name: 'name', weight: 0.7 },
308
+ { name: 'id', weight: 0.6 }
309
+ ],
310
+ includeScore: true,
311
+ threshold: 0.4,
312
+ minMatchCharLength: 1,
313
+ });
314
+ }
315
+ catch (error) {
316
+ console.error('Error creating space search index:', error);
317
+ return null;
318
+ }
319
+ })();
320
+ // Cache the promise
321
+ spaceSearchIndexPromise = fetchPromise;
322
+ // Auto-cleanup after 60 seconds
323
+ setTimeout(() => {
324
+ spaceSearchIndexPromise = null;
325
+ console.error('Auto-cleaned space search index');
326
+ }, GLOBAL_REFRESH_INTERVAL);
327
+ return fetchPromise;
328
+ }
329
+ const listCache = new Map(); // Cache for space lists/folders
330
+ /**
331
+ * Get lists, folders, and documents for a specific space with caching
332
+ */
333
+ async function getSpaceContent(spaceId) {
334
+ const cacheKey = `space-content-${spaceId}`;
335
+ // Check cache first
336
+ const cachedContent = listCache.get(cacheKey);
337
+ if (cachedContent) {
338
+ return cachedContent;
339
+ }
340
+ // Fetch content with parallel requests
341
+ const fetchPromise = (async () => {
342
+ try {
343
+ const [folders, lists, documents] = await Promise.all([
344
+ fetch(`https://api.clickup.com/api/v2/space/${spaceId}/folder`, {
345
+ headers: { Authorization: config_1.CONFIG.apiKey },
346
+ })
347
+ .then(response => response.json())
348
+ .then(json => json.folders || [])
349
+ .catch(e => {
350
+ console.error(e);
351
+ return [];
352
+ }),
353
+ fetch(`https://api.clickup.com/api/v2/space/${spaceId}/list`, {
354
+ headers: { Authorization: config_1.CONFIG.apiKey },
355
+ })
356
+ .then(response => response.json())
357
+ .then(json => json.lists || [])
358
+ .catch(e => {
359
+ console.error(e);
360
+ return [];
361
+ }),
362
+ fetch(`https://api.clickup.com/api/v3/workspaces/${config_1.CONFIG.teamId}/docs?parent_id=${spaceId}`, {
363
+ headers: { Authorization: config_1.CONFIG.apiKey },
364
+ })
365
+ .then(response => response.json())
366
+ .then(json => json.docs || [])
367
+ .catch(e => {
368
+ console.error(e);
369
+ return [];
370
+ })
371
+ ]);
372
+ // For each folder, also fetch its lists
373
+ const folderListPromises = folders.map(async (folder) => {
374
+ try {
375
+ const folderListResponse = await fetch(`https://api.clickup.com/api/v2/folder/${folder.id}/list`, { headers: { Authorization: config_1.CONFIG.apiKey } });
376
+ if (folderListResponse.ok) {
377
+ const folderListData = await folderListResponse.json();
378
+ folder.lists = folderListData.lists || [];
379
+ }
380
+ return folder;
381
+ }
382
+ catch (error) {
383
+ console.error(`Error fetching lists for folder ${folder.id}:`, error);
384
+ folder.lists = [];
385
+ return folder;
386
+ }
387
+ });
388
+ const foldersWithLists = await Promise.all(folderListPromises);
389
+ return { lists, folders: foldersWithLists, documents };
390
+ }
391
+ catch (error) {
392
+ console.error(`Error fetching space content for ${spaceId}:`, error);
393
+ return { lists: [], folders: [], documents: [] };
394
+ }
395
+ })();
396
+ // Cache the promise
397
+ listCache.set(cacheKey, fetchPromise);
398
+ // Auto-cleanup after 60 seconds
399
+ setTimeout(() => {
400
+ listCache.delete(cacheKey);
401
+ console.error(`Auto-cleaned space content cache for ${spaceId}`);
402
+ }, GLOBAL_REFRESH_INTERVAL);
403
+ return fetchPromise;
404
+ }
405
+ // Cache for team members to avoid repeated API calls and race conditions
406
+ let cachedTeamMembersPromise = null;
407
+ /**
408
+ * Gets all team members from ClickUp API with caching
409
+ */
410
+ async function getAllTeamMembers() {
411
+ // Return cached promise if available
412
+ if (cachedTeamMembersPromise) {
413
+ return cachedTeamMembersPromise;
414
+ }
415
+ // Create the fetch promise
416
+ const fetchPromise = (async () => {
417
+ try {
418
+ const response = await fetch(`https://api.clickup.com/api/v2/team`, {
419
+ headers: { Authorization: config_1.CONFIG.apiKey },
420
+ });
421
+ if (!response.ok) {
422
+ console.error(`Error fetching teams: ${response.status} ${response.statusText}`);
423
+ return [];
424
+ }
425
+ const data = await response.json();
426
+ if (!data.teams || !Array.isArray(data.teams)) {
427
+ return [];
428
+ }
429
+ // Find the team that matches our configured team ID and extract all user IDs
430
+ const currentTeam = data.teams.find((team) => team.id === config_1.CONFIG.teamId);
431
+ if (!currentTeam || !currentTeam.members || !Array.isArray(currentTeam.members)) {
432
+ console.error(`Team ${config_1.CONFIG.teamId} not found or has no members`);
433
+ return [];
434
+ }
435
+ return currentTeam.members.map((member) => member.user?.id).filter(Boolean);
436
+ }
437
+ catch (error) {
438
+ console.error('Error fetching team members:', error);
439
+ return [];
440
+ }
441
+ })();
442
+ // Cache the promise
443
+ cachedTeamMembersPromise = fetchPromise;
444
+ // Auto-cleanup after 60 seconds
445
+ setTimeout(() => {
446
+ cachedTeamMembersPromise = null;
447
+ console.error(`Auto-cleaned team members cache`);
448
+ }, GLOBAL_REFRESH_INTERVAL);
449
+ return fetchPromise;
450
+ }
451
+ /**
452
+ * Performs multi-term search with aggressive boosting for items matching multiple terms
453
+ * @param searchIndex Fuse search index to search within
454
+ * @param terms Array of search terms
455
+ * @returns Array of items sorted by relevance (multi-term matches ranked higher)
456
+ */
457
+ async function performMultiTermSearch(searchIndex, terms) {
458
+ // Filter valid search terms
459
+ const validTerms = terms.filter(term => term && term.trim().length > 0);
460
+ if (validTerms.length === 0) {
461
+ return [];
462
+ }
463
+ // Track multiple matches per item for aggressive boosting
464
+ const itemMatches = new Map();
465
+ // Collect all matches for each term
466
+ validTerms.forEach(term => {
467
+ const results = searchIndex.search(term);
468
+ results.forEach(result => {
469
+ if (result.item && typeof result.item.id === 'string') {
470
+ const itemId = result.item.id;
471
+ const currentScore = result.score ?? 1;
472
+ const existing = itemMatches.get(itemId);
473
+ if (!existing) {
474
+ itemMatches.set(itemId, {
475
+ item: result.item,
476
+ scores: [currentScore],
477
+ matchedTerms: [term]
478
+ });
479
+ }
480
+ else {
481
+ existing.scores.push(currentScore);
482
+ existing.matchedTerms.push(term);
483
+ }
484
+ }
485
+ });
486
+ });
487
+ // Calculate aggressively boosted scores for multi-term matches
488
+ const uniqueResults = new Map();
489
+ itemMatches.forEach((match, itemId) => {
490
+ const bestScore = Math.min(...match.scores);
491
+ const matchCount = match.scores.length;
492
+ const totalTerms = validTerms.length;
493
+ // Aggressive multi-term boost: exponential improvement for multiple matches
494
+ // 1 match: base score
495
+ // 2+ matches: exponentially better score based on match ratio
496
+ const matchRatio = matchCount / totalTerms;
497
+ const boostFactor = Math.pow(0.1, matchRatio * 4); // Very aggressive boost
498
+ const finalScore = bestScore * boostFactor;
499
+ uniqueResults.set(itemId, {
500
+ item: match.item,
501
+ score: finalScore
502
+ });
503
+ });
504
+ // Return sorted results (best scores first)
505
+ return Array.from(uniqueResults.values())
506
+ .sort((a, b) => a.score - b.score)
507
+ .map(entry => entry.item);
508
+ }
@@ -0,0 +1,23 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types";
3
+ /**
4
+ * A simple utility to test MCP tools directly without using the StdioServerTransport
5
+ */
6
+ export declare class ToolTester {
7
+ private server;
8
+ constructor(server: McpServer);
9
+ /**
10
+ * Test a tool by name with the provided parameters
11
+ * @param toolName The name of the tool to test
12
+ * @param params The parameters to pass to the tool
13
+ * @returns A promise that resolves to the tool's result
14
+ */
15
+ testTool(toolName: string, params: Record<string, any>): Promise<CallToolResult>;
16
+ }
17
+ /**
18
+ * Create a tool tester for the provided server
19
+ * @param server The MCP server instance
20
+ * @returns A ToolTester instance
21
+ */
22
+ export declare function createToolTester(server: McpServer): ToolTester;
23
+ //# sourceMappingURL=test-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEjE;;GAEG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAY;gBAEd,MAAM,EAAE,SAAS;IAI7B;;;;;OAKG;IACG,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC;CAwBvF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG,UAAU,CAE9D"}
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ToolTester = void 0;
4
+ exports.createToolTester = createToolTester;
5
+ /**
6
+ * A simple utility to test MCP tools directly without using the StdioServerTransport
7
+ */
8
+ class ToolTester {
9
+ constructor(server) {
10
+ this.server = server;
11
+ }
12
+ /**
13
+ * Test a tool by name with the provided parameters
14
+ * @param toolName The name of the tool to test
15
+ * @param params The parameters to pass to the tool
16
+ * @returns A promise that resolves to the tool's result
17
+ */
18
+ async testTool(toolName, params) {
19
+ // @ts-ignore - Accessing private property for testing purposes
20
+ const tools = this.server._registeredTools;
21
+ if (!tools || !tools[toolName]) {
22
+ throw new Error(`Tool "${toolName}" not found`);
23
+ }
24
+ const tool = tools[toolName];
25
+ // Validate parameters using the tool's schema
26
+ try {
27
+ tool.inputSchema.parse(params);
28
+ }
29
+ catch (error) {
30
+ throw new Error(`Parameter validation error: ${error instanceof Error ? error.message : 'Unknown error'}`);
31
+ }
32
+ // Call the tool's callback directly with the provided parameters
33
+ return await tool.callback(params);
34
+ }
35
+ }
36
+ exports.ToolTester = ToolTester;
37
+ /**
38
+ * Create a tool tester for the provided server
39
+ * @param server The MCP server instance
40
+ * @returns A ToolTester instance
41
+ */
42
+ function createToolTester(server) {
43
+ return new ToolTester(server);
44
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerAdminToolsWrite(server: McpServer): void;
3
+ //# sourceMappingURL=admin-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-tools.d.ts","sourceRoot":"","sources":["../../src/tools/admin-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAYpE,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,QAoOxD"}