@hauptsache.net/clickup-mcp 1.1.1 → 1.3.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 (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +20 -21
  3. package/dist/cli.js +8 -0
  4. package/dist/clickup-text.d.ts +5 -2
  5. package/dist/clickup-text.d.ts.map +1 -1
  6. package/dist/clickup-text.js +133 -61
  7. package/dist/index.d.ts +1 -2
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +37 -13
  10. package/dist/shared/config.d.ts +1 -0
  11. package/dist/shared/config.d.ts.map +1 -1
  12. package/dist/shared/config.js +1 -0
  13. package/dist/shared/image-processing.d.ts +13 -0
  14. package/dist/shared/image-processing.d.ts.map +1 -0
  15. package/dist/shared/image-processing.js +126 -0
  16. package/dist/shared/types.d.ts +7 -1
  17. package/dist/shared/types.d.ts.map +1 -1
  18. package/dist/shared/utils.d.ts +24 -26
  19. package/dist/shared/utils.d.ts.map +1 -1
  20. package/dist/shared/utils.js +424 -101
  21. package/dist/tools/doc-tools.d.ts +4 -0
  22. package/dist/tools/doc-tools.d.ts.map +1 -0
  23. package/dist/tools/doc-tools.js +485 -0
  24. package/dist/tools/list-tools.d.ts.map +1 -1
  25. package/dist/tools/list-tools.js +26 -149
  26. package/dist/tools/search-tools.d.ts +1 -1
  27. package/dist/tools/search-tools.d.ts.map +1 -1
  28. package/dist/tools/search-tools.js +85 -62
  29. package/dist/tools/space-tools.d.ts.map +1 -1
  30. package/dist/tools/space-tools.js +153 -27
  31. package/dist/tools/task-tools.d.ts +1 -2
  32. package/dist/tools/task-tools.d.ts.map +1 -1
  33. package/dist/tools/task-tools.js +86 -405
  34. package/dist/tools/task-write-tools.d.ts +3 -0
  35. package/dist/tools/task-write-tools.d.ts.map +1 -0
  36. package/dist/tools/task-write-tools.js +375 -0
  37. package/dist/tools/time-tools.d.ts.map +1 -1
  38. package/dist/tools/time-tools.js +2 -30
  39. package/package.json +15 -5
@@ -3,21 +3,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.downloadImages = void 0;
6
7
  exports.isTaskId = isTaskId;
7
8
  exports.getCurrentUser = getCurrentUser;
8
- exports.limitImages = limitImages;
9
9
  exports.getSpaceDetails = getSpaceDetails;
10
10
  exports.getTaskSearchIndex = getTaskSearchIndex;
11
11
  exports.generateTaskUrl = generateTaskUrl;
12
12
  exports.generateListUrl = generateListUrl;
13
13
  exports.generateSpaceUrl = generateSpaceUrl;
14
14
  exports.generateFolderUrl = generateFolderUrl;
15
- exports.formatTaskLink = formatTaskLink;
16
- exports.formatListLink = formatListLink;
17
- exports.formatSpaceLink = formatSpaceLink;
18
- exports.extractTaskIdFromUrl = extractTaskIdFromUrl;
19
- exports.isClickUpUrl = isClickUpUrl;
20
- exports.formatLinksSection = formatLinksSection;
15
+ exports.generateDocumentUrl = generateDocumentUrl;
16
+ exports.getSpaceSearchIndex = getSpaceSearchIndex;
17
+ exports.getSpaceContent = getSpaceContent;
18
+ exports.getAllTeamMembers = getAllTeamMembers;
19
+ exports.getDocumentSearchIndex = getDocumentSearchIndex;
20
+ exports.performMultiTermSearch = performMultiTermSearch;
21
21
  const config_1 = require("./config");
22
22
  const fuse_js_1 = __importDefault(require("fuse.js"));
23
23
  const GLOBAL_REFRESH_INTERVAL = 60000; // 60 seconds - that is the rate limit time frame
@@ -29,62 +29,39 @@ function isTaskId(str) {
29
29
  // Task IDs are 6-9 characters long and contain only alphanumeric characters
30
30
  return /^[a-z0-9]{6,9}$/i.test(str);
31
31
  }
32
- // Cache for current user info to avoid repeated API calls
33
- let cachedUserData = null;
32
+ // Cache for current user info to avoid repeated API calls and race conditions
33
+ let cachedUserPromise = null;
34
34
  /**
35
35
  * Get current authenticated user information from ClickUp API
36
- * Caches the result after first successful fetch to avoid repeated API calls during the session
36
+ * Caches the promise to prevent race conditions on concurrent calls
37
37
  */
38
38
  async function getCurrentUser() {
39
- // Return cached data if available
40
- if (cachedUserData) {
41
- return cachedUserData;
39
+ // Return cached promise if available
40
+ if (cachedUserPromise) {
41
+ return cachedUserPromise;
42
42
  }
43
- const userResponse = await fetch("https://api.clickup.com/api/v2/user", {
44
- headers: { Authorization: config_1.CONFIG.apiKey },
45
- });
46
- if (!userResponse.ok) {
47
- throw new Error(`Error fetching user info: ${userResponse.status} ${userResponse.statusText}`);
48
- }
49
- // Cache the result for future calls
50
- cachedUserData = await userResponse.json();
51
- return cachedUserData;
52
- }
53
- /**
54
- * Limits the number of images in the content array, replacing excess images with text placeholders
55
- * Prioritizes keeping the most recent images (assumes content is ordered with newest items last)
56
- *
57
- * @param content Array of content blocks that may contain images
58
- * @param maxImages Maximum number of images to keep
59
- * @returns Modified content array with limited images
60
- */
61
- function limitImages(content, maxImages) {
62
- // Count how many images we have
63
- const imageIndices = [];
64
- // Find all image blocks
65
- content.forEach((block, index) => {
66
- if (block.type === "image") {
67
- imageIndices.push(index);
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}`);
68
50
  }
69
- });
70
- // If we have fewer images than the limit, return the original content
71
- if (imageIndices.length <= maxImages) {
72
- return content;
73
- }
74
- // Determine which images to keep (the most recent ones)
75
- // We want to keep the last 'maxImages' images
76
- const imagesToRemove = imageIndices.slice(0, imageIndices.length - maxImages);
77
- // Create a new content array with excess images replaced by text
78
- return content.map((block, index) => {
79
- if (block.type === "image" && imagesToRemove.includes(index)) {
80
- return {
81
- type: "text",
82
- text: "[Image removed due to size limitations. Only the most recent images are shown.]",
83
- };
84
- }
85
- return block;
86
- });
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;
87
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; } });
88
65
  const spaceCache = new Map(); // Global cache for space details promises
89
66
  /**
90
67
  * Function to get space details, using a cache to avoid redundant fetches
@@ -111,10 +88,11 @@ function getSpaceDetails(spaceId) {
111
88
  spaceCache.set(spaceId, fetchPromise);
112
89
  return fetchPromise;
113
90
  }
114
- // Task search index management
91
+ // Task search index management - cache promises to prevent race conditions
115
92
  const taskIndices = new Map();
116
93
  /**
117
94
  * Get or create a task search index with specified filters
95
+ * Caches promises to prevent race conditions on concurrent calls
118
96
  */
119
97
  async function getTaskSearchIndex(space_ids, list_ids, assignees) {
120
98
  // Create cache key from sorted filter arrays
@@ -123,23 +101,26 @@ async function getTaskSearchIndex(space_ids, list_ids, assignees) {
123
101
  list_ids: list_ids?.sort(),
124
102
  assignees: assignees?.sort()
125
103
  });
126
- // Check for existing valid index
127
- const cachedIndex = taskIndices.get(key);
128
- if (cachedIndex) {
129
- return cachedIndex;
104
+ // Check for existing valid index promise
105
+ const cachedPromise = taskIndices.get(key);
106
+ if (cachedPromise) {
107
+ return cachedPromise;
130
108
  }
131
- // Fetch tasks with specified filters
132
- console.error(`Refreshing task index for filters: ${key}`);
133
- const tasks = await fetchTasks(space_ids, list_ids, assignees);
134
- const index = createFuseIndex(tasks);
135
- // Store with auto-cleanup
136
- taskIndices.set(key, index);
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);
137
119
  setTimeout(() => {
138
120
  taskIndices.delete(key);
139
121
  console.error(`Auto-cleaned index for filters: ${key}`);
140
122
  }, GLOBAL_REFRESH_INTERVAL);
141
- console.error(`Task index created with ${tasks.length} tasks`);
142
- return index;
123
+ return fetchPromise;
143
124
  }
144
125
  /**
145
126
  * Fetch tasks using team endpoint with dynamic filters
@@ -188,9 +169,10 @@ function createFuseIndex(tasks) {
188
169
  { name: 'folder.name', weight: 0.2 },
189
170
  { name: 'space.name', weight: 0.1 }
190
171
  ],
172
+ findAllMatches: true,
191
173
  includeScore: true,
192
- threshold: 0.4,
193
174
  minMatchCharLength: 2,
175
+ threshold: 0.4,
194
176
  });
195
177
  }
196
178
  // ===== LINK UTILITIES =====
@@ -204,63 +186,404 @@ function generateTaskUrl(taskId) {
204
186
  * Generate a ClickUp list URL from a list ID
205
187
  */
206
188
  function generateListUrl(listId) {
207
- return `https://app.clickup.com/v/l/${listId}`;
189
+ return `https://app.clickup.com/${config_1.CONFIG.teamId}/v/li/${listId}`;
208
190
  }
209
191
  /**
210
192
  * Generate a ClickUp space URL from a space ID
211
193
  */
212
194
  function generateSpaceUrl(spaceId) {
213
- return `https://app.clickup.com/v/s/${spaceId}`;
195
+ return `https://app.clickup.com/${config_1.CONFIG.teamId}/v/s/${spaceId}`;
214
196
  }
215
197
  /**
216
198
  * Generate a ClickUp folder URL from a folder ID
217
199
  */
218
200
  function generateFolderUrl(folderId) {
219
- return `https://app.clickup.com/v/f/${folderId}`;
201
+ return `https://app.clickup.com/${config_1.CONFIG.teamId}/v/f/${folderId}`;
220
202
  }
221
203
  /**
222
- * Format a ClickUp task link as markdown
204
+ * Generate a ClickUp document URL from a document ID and optional page ID
223
205
  */
224
- function formatTaskLink(taskId, taskName) {
225
- const url = generateTaskUrl(taskId);
226
- const displayText = taskName ? `${taskName} (${taskId})` : taskId;
227
- return `[${displayText}](${url})`;
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}`;
228
211
  }
212
+ // Space search index cache - cache promise to prevent race conditions
213
+ let spaceSearchIndexPromise = null;
229
214
  /**
230
- * Format a ClickUp list link as markdown
215
+ * Get or refresh the space search index
216
+ * Caches promise to prevent race conditions on concurrent calls
231
217
  */
232
- function formatListLink(listId, listName) {
233
- const url = generateListUrl(listId);
234
- const displayText = listName ? `${listName} (${listId})` : listId;
235
- return `[${displayText}](${url})`;
218
+ async function getSpaceSearchIndex() {
219
+ // Return cached promise if available
220
+ if (spaceSearchIndexPromise) {
221
+ return spaceSearchIndexPromise;
222
+ }
223
+ // Create the fetch promise
224
+ const fetchPromise = (async () => {
225
+ try {
226
+ const url = `https://api.clickup.com/api/v2/team/${config_1.CONFIG.teamId}/space`;
227
+ const response = await fetch(url, {
228
+ headers: { Authorization: config_1.CONFIG.apiKey },
229
+ });
230
+ if (!response.ok) {
231
+ throw new Error(`Error fetching spaces: ${response.status} ${response.statusText}`);
232
+ }
233
+ const data = await response.json();
234
+ const spacesData = data.spaces || [];
235
+ // Create Fuse search index
236
+ return new fuse_js_1.default(spacesData, {
237
+ keys: [
238
+ { name: 'name', weight: 0.7 },
239
+ { name: 'id', weight: 0.6 }
240
+ ],
241
+ includeScore: true,
242
+ threshold: 0.4,
243
+ minMatchCharLength: 1,
244
+ });
245
+ }
246
+ catch (error) {
247
+ console.error('Error creating space search index:', error);
248
+ return null;
249
+ }
250
+ })();
251
+ // Cache the promise
252
+ spaceSearchIndexPromise = fetchPromise;
253
+ // Auto-cleanup after 60 seconds
254
+ setTimeout(() => {
255
+ spaceSearchIndexPromise = null;
256
+ console.error('Auto-cleaned space search index');
257
+ }, GLOBAL_REFRESH_INTERVAL);
258
+ return fetchPromise;
236
259
  }
260
+ const listCache = new Map(); // Cache for space lists/folders
237
261
  /**
238
- * Format a ClickUp space link as markdown
262
+ * Get lists, folders, and documents for a specific space with caching
239
263
  */
240
- function formatSpaceLink(spaceId, spaceName) {
241
- const url = generateSpaceUrl(spaceId);
242
- const displayText = spaceName ? `${spaceName} (${spaceId})` : spaceId;
243
- return `[${displayText}](${url})`;
264
+ async function getSpaceContent(spaceId) {
265
+ const cacheKey = `space-content-${spaceId}`;
266
+ // Check cache first
267
+ const cachedContent = listCache.get(cacheKey);
268
+ if (cachedContent) {
269
+ return cachedContent;
270
+ }
271
+ // Fetch content with parallel requests
272
+ const fetchPromise = (async () => {
273
+ try {
274
+ const [folders, lists, documents] = await Promise.all([
275
+ fetch(`https://api.clickup.com/api/v2/space/${spaceId}/folder`, {
276
+ headers: { Authorization: config_1.CONFIG.apiKey },
277
+ })
278
+ .then(response => response.json())
279
+ .then(json => json.folders || [])
280
+ .catch(e => {
281
+ console.error(e);
282
+ return [];
283
+ }),
284
+ fetch(`https://api.clickup.com/api/v2/space/${spaceId}/list`, {
285
+ headers: { Authorization: config_1.CONFIG.apiKey },
286
+ })
287
+ .then(response => response.json())
288
+ .then(json => json.lists || [])
289
+ .catch(e => {
290
+ console.error(e);
291
+ return [];
292
+ }),
293
+ fetch(`https://api.clickup.com/api/v3/workspaces/${config_1.CONFIG.teamId}/docs?parent_id=${spaceId}`, {
294
+ headers: { Authorization: config_1.CONFIG.apiKey },
295
+ })
296
+ .then(response => response.json())
297
+ .then(json => json.docs || [])
298
+ .catch(e => {
299
+ console.error(e);
300
+ return [];
301
+ })
302
+ ]);
303
+ // For each folder, also fetch its lists
304
+ const folderListPromises = folders.map(async (folder) => {
305
+ try {
306
+ const folderListResponse = await fetch(`https://api.clickup.com/api/v2/folder/${folder.id}/list`, { headers: { Authorization: config_1.CONFIG.apiKey } });
307
+ if (folderListResponse.ok) {
308
+ const folderListData = await folderListResponse.json();
309
+ folder.lists = folderListData.lists || [];
310
+ }
311
+ return folder;
312
+ }
313
+ catch (error) {
314
+ console.error(`Error fetching lists for folder ${folder.id}:`, error);
315
+ folder.lists = [];
316
+ return folder;
317
+ }
318
+ });
319
+ const foldersWithLists = await Promise.all(folderListPromises);
320
+ return { lists, folders: foldersWithLists, documents };
321
+ }
322
+ catch (error) {
323
+ console.error(`Error fetching space content for ${spaceId}:`, error);
324
+ return { lists: [], folders: [], documents: [] };
325
+ }
326
+ })();
327
+ // Cache the promise
328
+ listCache.set(cacheKey, fetchPromise);
329
+ // Auto-cleanup after 60 seconds
330
+ setTimeout(() => {
331
+ listCache.delete(cacheKey);
332
+ console.error(`Auto-cleaned space content cache for ${spaceId}`);
333
+ }, GLOBAL_REFRESH_INTERVAL);
334
+ return fetchPromise;
244
335
  }
336
+ // Cache for team members to avoid repeated API calls and race conditions
337
+ let cachedTeamMembersPromise = null;
245
338
  /**
246
- * Extract task ID from a ClickUp URL
339
+ * Gets all team members from ClickUp API with caching
247
340
  */
248
- function extractTaskIdFromUrl(url) {
249
- const match = url.match(/https?:\/\/app\.clickup\.com\/t\/([a-z0-9]{6,9})/i);
250
- return match ? match[1] : null;
341
+ async function getAllTeamMembers() {
342
+ // Return cached promise if available
343
+ if (cachedTeamMembersPromise) {
344
+ return cachedTeamMembersPromise;
345
+ }
346
+ // Create the fetch promise
347
+ const fetchPromise = (async () => {
348
+ try {
349
+ const response = await fetch(`https://api.clickup.com/api/v2/team`, {
350
+ headers: { Authorization: config_1.CONFIG.apiKey },
351
+ });
352
+ if (!response.ok) {
353
+ console.error(`Error fetching teams: ${response.status} ${response.statusText}`);
354
+ return [];
355
+ }
356
+ const data = await response.json();
357
+ if (!data.teams || !Array.isArray(data.teams)) {
358
+ return [];
359
+ }
360
+ // Find the team that matches our configured team ID and extract all user IDs
361
+ const currentTeam = data.teams.find((team) => team.id === config_1.CONFIG.teamId);
362
+ if (!currentTeam || !currentTeam.members || !Array.isArray(currentTeam.members)) {
363
+ console.error(`Team ${config_1.CONFIG.teamId} not found or has no members`);
364
+ return [];
365
+ }
366
+ return currentTeam.members.map((member) => member.user?.id).filter(Boolean);
367
+ }
368
+ catch (error) {
369
+ console.error('Error fetching team members:', error);
370
+ return [];
371
+ }
372
+ })();
373
+ // Cache the promise
374
+ cachedTeamMembersPromise = fetchPromise;
375
+ // Auto-cleanup after 60 seconds
376
+ setTimeout(() => {
377
+ cachedTeamMembersPromise = null;
378
+ console.error(`Auto-cleaned team members cache`);
379
+ }, GLOBAL_REFRESH_INTERVAL);
380
+ return fetchPromise;
251
381
  }
382
+ // Document search index management - cache promises to prevent race conditions
383
+ const documentIndices = new Map();
252
384
  /**
253
- * Validate if a string is a valid ClickUp URL
385
+ * Get or create a document search index with space name resolution
386
+ * Caches promises to prevent race conditions on concurrent calls
254
387
  */
255
- function isClickUpUrl(url) {
256
- return /^https?:\/\/app\.clickup\.com\//.test(url);
388
+ async function getDocumentSearchIndex(space_ids) {
389
+ // Create cache key from sorted filter arrays
390
+ const key = JSON.stringify({
391
+ space_ids: space_ids?.sort()
392
+ });
393
+ // Check for existing valid index promise
394
+ const cachedPromise = documentIndices.get(key);
395
+ if (cachedPromise) {
396
+ return cachedPromise;
397
+ }
398
+ // Create the fetch promise
399
+ const fetchPromise = (async () => {
400
+ console.error(`Refreshing document index for filters: ${key}`);
401
+ const documents = await fetchDocuments(space_ids);
402
+ const index = createDocumentFuseIndex(documents);
403
+ console.error(`Document index created with ${documents.length} documents`);
404
+ return index;
405
+ })();
406
+ // Store promise with auto-cleanup
407
+ documentIndices.set(key, fetchPromise);
408
+ setTimeout(() => {
409
+ documentIndices.delete(key);
410
+ console.error(`Auto-cleaned document index for filters: ${key}`);
411
+ }, GLOBAL_REFRESH_INTERVAL);
412
+ return fetchPromise;
257
413
  }
258
414
  /**
259
- * Format a prominent link section for responses
415
+ * Fetch documents and resolve space names
260
416
  */
261
- function formatLinksSection(links) {
262
- if (links.length === 0)
263
- return '';
264
- const linkLines = links.map(link => `🔗 [${link.text}](${link.url})`);
265
- return `\n\n**📌 Quick Links:**\n${linkLines.join('\n')}`;
417
+ async function fetchDocuments(space_ids) {
418
+ try {
419
+ // Fetch spaces first
420
+ const spacesResponse = await fetch(`https://api.clickup.com/api/v2/team/${config_1.CONFIG.teamId}/space`, {
421
+ headers: { Authorization: config_1.CONFIG.apiKey }
422
+ });
423
+ if (!spacesResponse.ok) {
424
+ console.error('Error fetching spaces:', spacesResponse.status);
425
+ return [];
426
+ }
427
+ const spacesData = await spacesResponse.json();
428
+ // Fetch documents with pagination
429
+ const allDocuments = [];
430
+ let nextCursor = null;
431
+ let pageCount = 0;
432
+ const maxPages = 10; // Limit to 10 pages to avoid excessive API calls
433
+ do {
434
+ const url = new URL(`https://api.clickup.com/api/v3/workspaces/${config_1.CONFIG.teamId}/docs`);
435
+ url.searchParams.set('limit', '100');
436
+ if (nextCursor) {
437
+ url.searchParams.set('next_cursor', nextCursor);
438
+ }
439
+ const documentsResponse = await fetch(url.toString(), {
440
+ headers: { Authorization: config_1.CONFIG.apiKey }
441
+ });
442
+ if (!documentsResponse.ok) {
443
+ console.error('Error fetching documents:', documentsResponse.status);
444
+ break;
445
+ }
446
+ const documentsData = await documentsResponse.json();
447
+ const pageDocs = documentsData.docs || [];
448
+ allDocuments.push(...pageDocs);
449
+ nextCursor = documentsData.next_cursor || null;
450
+ pageCount++;
451
+ console.error(`Fetched page ${pageCount} with ${pageDocs.length} documents (total: ${allDocuments.length})`);
452
+ } while (nextCursor && pageCount < maxPages);
453
+ const documents = allDocuments;
454
+ const spaces = spacesData.spaces || [];
455
+ // Create space lookup map
456
+ const spaceMap = new Map(spaces.map((space) => [space.id, space]));
457
+ // Enhance documents with parent information
458
+ const enhancedDocuments = documents.map((doc) => {
459
+ // Parent types: 4=Space, 5=Folder, 6=List, 7=Workspace
460
+ if (doc.parent?.type === 4) {
461
+ // Space parent - we can resolve the name
462
+ const space = spaceMap.get(doc.parent.id);
463
+ return {
464
+ ...doc,
465
+ space_name: space?.name || 'Unknown',
466
+ space_id: doc.parent.id,
467
+ parent_info: space?.name ? `Space: ${space.name} (${doc.parent.id})` : `Space: ${doc.parent.id}`
468
+ };
469
+ }
470
+ else if (doc.parent?.type === 6) {
471
+ // List parent - just show ID
472
+ return {
473
+ ...doc,
474
+ space_name: 'N/A',
475
+ space_id: null,
476
+ parent_info: `List: ${doc.parent.id}`
477
+ };
478
+ }
479
+ else if (doc.parent?.type === 5) {
480
+ // Folder parent - just show ID
481
+ return {
482
+ ...doc,
483
+ space_name: 'N/A',
484
+ space_id: null,
485
+ parent_info: `Folder: ${doc.parent.id}`
486
+ };
487
+ }
488
+ else if (doc.parent?.type === 7) {
489
+ // Workspace parent
490
+ return {
491
+ ...doc,
492
+ space_name: 'N/A',
493
+ space_id: null,
494
+ parent_info: `Workspace`
495
+ };
496
+ }
497
+ // Unknown parent type
498
+ return {
499
+ ...doc,
500
+ space_name: 'Unknown',
501
+ space_id: doc.parent?.id,
502
+ parent_info: doc.parent ? `Unknown (type ${doc.parent.type})` : 'Unknown'
503
+ };
504
+ });
505
+ // Filter by space_ids if provided
506
+ if (space_ids?.length) {
507
+ return enhancedDocuments.filter((doc) => space_ids.includes(doc.space_id));
508
+ }
509
+ return enhancedDocuments;
510
+ }
511
+ catch (error) {
512
+ console.error('Error fetching documents:', error);
513
+ return [];
514
+ }
515
+ }
516
+ /**
517
+ * Create a Fuse index from documents array
518
+ */
519
+ function createDocumentFuseIndex(documents) {
520
+ return new fuse_js_1.default(documents, {
521
+ keys: [
522
+ { name: 'name', weight: 0.8 },
523
+ { name: 'space_name', weight: 0.6 },
524
+ { name: 'id', weight: 0.4 }
525
+ ],
526
+ findAllMatches: true,
527
+ includeScore: true,
528
+ minMatchCharLength: 2,
529
+ threshold: 0.4,
530
+ });
531
+ }
532
+ /**
533
+ * Performs multi-term search with aggressive boosting for items matching multiple terms
534
+ * @param searchIndex Fuse search index to search within
535
+ * @param terms Array of search terms
536
+ * @returns Array of items sorted by relevance (multi-term matches ranked higher)
537
+ */
538
+ async function performMultiTermSearch(searchIndex, terms) {
539
+ // Filter valid search terms
540
+ const validTerms = terms.filter(term => term && term.trim().length > 0);
541
+ if (validTerms.length === 0) {
542
+ return [];
543
+ }
544
+ // Track multiple matches per item for aggressive boosting
545
+ const itemMatches = new Map();
546
+ // Collect all matches for each term
547
+ validTerms.forEach(term => {
548
+ const results = searchIndex.search(term);
549
+ results.forEach(result => {
550
+ if (result.item && typeof result.item.id === 'string') {
551
+ const itemId = result.item.id;
552
+ const currentScore = result.score ?? 1;
553
+ const existing = itemMatches.get(itemId);
554
+ if (!existing) {
555
+ itemMatches.set(itemId, {
556
+ item: result.item,
557
+ scores: [currentScore],
558
+ matchedTerms: [term]
559
+ });
560
+ }
561
+ else {
562
+ existing.scores.push(currentScore);
563
+ existing.matchedTerms.push(term);
564
+ }
565
+ }
566
+ });
567
+ });
568
+ // Calculate aggressively boosted scores for multi-term matches
569
+ const uniqueResults = new Map();
570
+ itemMatches.forEach((match, itemId) => {
571
+ const bestScore = Math.min(...match.scores);
572
+ const matchCount = match.scores.length;
573
+ const totalTerms = validTerms.length;
574
+ // Aggressive multi-term boost: exponential improvement for multiple matches
575
+ // 1 match: base score
576
+ // 2+ matches: exponentially better score based on match ratio
577
+ const matchRatio = matchCount / totalTerms;
578
+ const boostFactor = Math.pow(0.1, matchRatio * 4); // Very aggressive boost
579
+ const finalScore = bestScore * boostFactor;
580
+ uniqueResults.set(itemId, {
581
+ item: match.item,
582
+ score: finalScore
583
+ });
584
+ });
585
+ // Return sorted results (best scores first)
586
+ return Array.from(uniqueResults.values())
587
+ .sort((a, b) => a.score - b.score)
588
+ .map(entry => entry.item);
266
589
  }
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerDocumentToolsRead(server: McpServer): void;
3
+ export declare function registerDocumentToolsWrite(server: McpServer): void;
4
+ //# sourceMappingURL=doc-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doc-tools.d.ts","sourceRoot":"","sources":["../../src/tools/doc-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAoDpE,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,QAiR1D;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,QA8O3D"}