@hauptsache.net/clickup-mcp 1.2.0 → 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.
- package/LICENSE +21 -0
- package/README.md +18 -24
- package/dist/cli.js +7 -0
- package/dist/clickup-text.d.ts +1 -1
- package/dist/clickup-text.d.ts.map +1 -1
- package/dist/clickup-text.js +17 -13
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -9
- package/dist/shared/utils.d.ts +16 -26
- package/dist/shared/utils.d.ts.map +1 -1
- package/dist/shared/utils.js +247 -61
- package/dist/tools/doc-tools.d.ts +4 -0
- package/dist/tools/doc-tools.d.ts.map +1 -0
- package/dist/tools/doc-tools.js +485 -0
- package/dist/tools/list-tools.d.ts.map +1 -1
- package/dist/tools/search-tools.d.ts.map +1 -1
- package/dist/tools/search-tools.js +7 -29
- package/dist/tools/space-tools.d.ts.map +1 -1
- package/dist/tools/space-tools.js +94 -98
- package/dist/tools/task-tools.js +1 -1
- package/package.json +3 -3
package/dist/shared/utils.js
CHANGED
|
@@ -12,15 +12,12 @@ exports.generateTaskUrl = generateTaskUrl;
|
|
|
12
12
|
exports.generateListUrl = generateListUrl;
|
|
13
13
|
exports.generateSpaceUrl = generateSpaceUrl;
|
|
14
14
|
exports.generateFolderUrl = generateFolderUrl;
|
|
15
|
-
exports.
|
|
16
|
-
exports.formatListLink = formatListLink;
|
|
17
|
-
exports.formatSpaceLink = formatSpaceLink;
|
|
18
|
-
exports.extractTaskIdFromUrl = extractTaskIdFromUrl;
|
|
19
|
-
exports.isClickUpUrl = isClickUpUrl;
|
|
20
|
-
exports.formatLinksSection = formatLinksSection;
|
|
15
|
+
exports.generateDocumentUrl = generateDocumentUrl;
|
|
21
16
|
exports.getSpaceSearchIndex = getSpaceSearchIndex;
|
|
22
17
|
exports.getSpaceContent = getSpaceContent;
|
|
23
18
|
exports.getAllTeamMembers = getAllTeamMembers;
|
|
19
|
+
exports.getDocumentSearchIndex = getDocumentSearchIndex;
|
|
20
|
+
exports.performMultiTermSearch = performMultiTermSearch;
|
|
24
21
|
const config_1 = require("./config");
|
|
25
22
|
const fuse_js_1 = __importDefault(require("fuse.js"));
|
|
26
23
|
const GLOBAL_REFRESH_INTERVAL = 60000; // 60 seconds - that is the rate limit time frame
|
|
@@ -172,9 +169,10 @@ function createFuseIndex(tasks) {
|
|
|
172
169
|
{ name: 'folder.name', weight: 0.2 },
|
|
173
170
|
{ name: 'space.name', weight: 0.1 }
|
|
174
171
|
],
|
|
172
|
+
findAllMatches: true,
|
|
175
173
|
includeScore: true,
|
|
176
|
-
threshold: 0.4,
|
|
177
174
|
minMatchCharLength: 2,
|
|
175
|
+
threshold: 0.4,
|
|
178
176
|
});
|
|
179
177
|
}
|
|
180
178
|
// ===== LINK UTILITIES =====
|
|
@@ -188,65 +186,28 @@ function generateTaskUrl(taskId) {
|
|
|
188
186
|
* Generate a ClickUp list URL from a list ID
|
|
189
187
|
*/
|
|
190
188
|
function generateListUrl(listId) {
|
|
191
|
-
return `https://app.clickup.com/v/
|
|
189
|
+
return `https://app.clickup.com/${config_1.CONFIG.teamId}/v/li/${listId}`;
|
|
192
190
|
}
|
|
193
191
|
/**
|
|
194
192
|
* Generate a ClickUp space URL from a space ID
|
|
195
193
|
*/
|
|
196
194
|
function generateSpaceUrl(spaceId) {
|
|
197
|
-
return `https://app.clickup.com/v/s/${spaceId}`;
|
|
195
|
+
return `https://app.clickup.com/${config_1.CONFIG.teamId}/v/s/${spaceId}`;
|
|
198
196
|
}
|
|
199
197
|
/**
|
|
200
198
|
* Generate a ClickUp folder URL from a folder ID
|
|
201
199
|
*/
|
|
202
200
|
function generateFolderUrl(folderId) {
|
|
203
|
-
return `https://app.clickup.com/v/f/${folderId}`;
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Format a ClickUp task link as markdown
|
|
207
|
-
*/
|
|
208
|
-
function formatTaskLink(taskId, taskName) {
|
|
209
|
-
const url = generateTaskUrl(taskId);
|
|
210
|
-
const displayText = taskName ? `${taskName} (${taskId})` : taskId;
|
|
211
|
-
return `[${displayText}](${url})`;
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Format a ClickUp list link as markdown
|
|
215
|
-
*/
|
|
216
|
-
function formatListLink(listId, listName) {
|
|
217
|
-
const url = generateListUrl(listId);
|
|
218
|
-
const displayText = listName ? `${listName} (${listId})` : listId;
|
|
219
|
-
return `[${displayText}](${url})`;
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Format a ClickUp space link as markdown
|
|
223
|
-
*/
|
|
224
|
-
function formatSpaceLink(spaceId, spaceName) {
|
|
225
|
-
const url = generateSpaceUrl(spaceId);
|
|
226
|
-
const displayText = spaceName ? `${spaceName} (${spaceId})` : spaceId;
|
|
227
|
-
return `[${displayText}](${url})`;
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* Extract task ID from a ClickUp URL
|
|
231
|
-
*/
|
|
232
|
-
function extractTaskIdFromUrl(url) {
|
|
233
|
-
const match = url.match(/https?:\/\/app\.clickup\.com\/t\/([a-z0-9]{6,9})/i);
|
|
234
|
-
return match ? match[1] : null;
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Validate if a string is a valid ClickUp URL
|
|
238
|
-
*/
|
|
239
|
-
function isClickUpUrl(url) {
|
|
240
|
-
return /^https?:\/\/app\.clickup\.com\//.test(url);
|
|
201
|
+
return `https://app.clickup.com/${config_1.CONFIG.teamId}/v/f/${folderId}`;
|
|
241
202
|
}
|
|
242
203
|
/**
|
|
243
|
-
*
|
|
204
|
+
* Generate a ClickUp document URL from a document ID and optional page ID
|
|
244
205
|
*/
|
|
245
|
-
function
|
|
246
|
-
if (
|
|
247
|
-
return
|
|
248
|
-
|
|
249
|
-
return
|
|
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}`;
|
|
250
211
|
}
|
|
251
212
|
// Space search index cache - cache promise to prevent race conditions
|
|
252
213
|
let spaceSearchIndexPromise = null;
|
|
@@ -298,7 +259,7 @@ async function getSpaceSearchIndex() {
|
|
|
298
259
|
}
|
|
299
260
|
const listCache = new Map(); // Cache for space lists/folders
|
|
300
261
|
/**
|
|
301
|
-
* Get lists and
|
|
262
|
+
* Get lists, folders, and documents for a specific space with caching
|
|
302
263
|
*/
|
|
303
264
|
async function getSpaceContent(spaceId) {
|
|
304
265
|
const cacheKey = `space-content-${spaceId}`;
|
|
@@ -310,18 +271,35 @@ async function getSpaceContent(spaceId) {
|
|
|
310
271
|
// Fetch content with parallel requests
|
|
311
272
|
const fetchPromise = (async () => {
|
|
312
273
|
try {
|
|
313
|
-
const [
|
|
274
|
+
const [folders, lists, documents] = await Promise.all([
|
|
314
275
|
fetch(`https://api.clickup.com/api/v2/space/${spaceId}/folder`, {
|
|
315
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 [];
|
|
316
283
|
}),
|
|
317
284
|
fetch(`https://api.clickup.com/api/v2/space/${spaceId}/list`, {
|
|
318
285
|
headers: { Authorization: config_1.CONFIG.apiKey },
|
|
319
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
|
+
})
|
|
320
302
|
]);
|
|
321
|
-
const folders = foldersResponse.ok ?
|
|
322
|
-
(await foldersResponse.json()).folders || [] : [];
|
|
323
|
-
const lists = listsResponse.ok ?
|
|
324
|
-
(await listsResponse.json()).lists || [] : [];
|
|
325
303
|
// For each folder, also fetch its lists
|
|
326
304
|
const folderListPromises = folders.map(async (folder) => {
|
|
327
305
|
try {
|
|
@@ -339,11 +317,11 @@ async function getSpaceContent(spaceId) {
|
|
|
339
317
|
}
|
|
340
318
|
});
|
|
341
319
|
const foldersWithLists = await Promise.all(folderListPromises);
|
|
342
|
-
return { lists, folders: foldersWithLists };
|
|
320
|
+
return { lists, folders: foldersWithLists, documents };
|
|
343
321
|
}
|
|
344
322
|
catch (error) {
|
|
345
323
|
console.error(`Error fetching space content for ${spaceId}:`, error);
|
|
346
|
-
return { lists: [], folders: [] };
|
|
324
|
+
return { lists: [], folders: [], documents: [] };
|
|
347
325
|
}
|
|
348
326
|
})();
|
|
349
327
|
// Cache the promise
|
|
@@ -401,3 +379,211 @@ async function getAllTeamMembers() {
|
|
|
401
379
|
}, GLOBAL_REFRESH_INTERVAL);
|
|
402
380
|
return fetchPromise;
|
|
403
381
|
}
|
|
382
|
+
// Document search index management - cache promises to prevent race conditions
|
|
383
|
+
const documentIndices = new Map();
|
|
384
|
+
/**
|
|
385
|
+
* Get or create a document search index with space name resolution
|
|
386
|
+
* Caches promises to prevent race conditions on concurrent calls
|
|
387
|
+
*/
|
|
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;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Fetch documents and resolve space names
|
|
416
|
+
*/
|
|
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);
|
|
589
|
+
}
|
|
@@ -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"}
|