@hauptsache.net/clickup-mcp 1.2.0 → 1.3.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.
@@ -0,0 +1,491 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerDocumentToolsRead = registerDocumentToolsRead;
4
+ exports.registerDocumentToolsWrite = registerDocumentToolsWrite;
5
+ const zod_1 = require("zod");
6
+ const config_1 = require("../shared/config");
7
+ const utils_1 = require("../shared/utils");
8
+ /**
9
+ * Helper function to recursively extract all pages from nested page structure
10
+ */
11
+ function extractAllPages(pageGroup) {
12
+ const allPages = [];
13
+ // Add the page itself
14
+ allPages.push({
15
+ id: pageGroup.id,
16
+ name: pageGroup.name,
17
+ doc_id: pageGroup.doc_id,
18
+ parent_page_id: pageGroup.parent_page_id || null
19
+ });
20
+ // Recursively add nested pages
21
+ if (pageGroup.pages && Array.isArray(pageGroup.pages)) {
22
+ pageGroup.pages.forEach((nestedPage) => {
23
+ allPages.push(...extractAllPages(nestedPage));
24
+ });
25
+ }
26
+ return allPages;
27
+ }
28
+ /**
29
+ * Helper function to display page hierarchy with proper indentation
30
+ */
31
+ function displayPageHierarchy(pageGroup, currentPageId, depth = 0) {
32
+ const result = [];
33
+ const indent = ' '.repeat(depth); // 2 spaces per level
34
+ const isCurrentPage = pageGroup.id === currentPageId;
35
+ const prefix = isCurrentPage ? '▶️ ' : ' ';
36
+ const pageIndicator = isCurrentPage ? ' ← **Currently viewing**' : '';
37
+ // Display this page
38
+ result.push(`${indent}${prefix}${pageGroup.name} (${pageGroup.id})${pageIndicator}`);
39
+ // Recursively display nested pages
40
+ if (pageGroup.pages && Array.isArray(pageGroup.pages)) {
41
+ pageGroup.pages.forEach((nestedPage) => {
42
+ result.push(...displayPageHierarchy(nestedPage, currentPageId, depth + 1));
43
+ });
44
+ }
45
+ return result;
46
+ }
47
+ function registerDocumentToolsRead(server) {
48
+ server.tool("readDocument", [
49
+ "Get a ClickUp document with page structure and content.",
50
+ "Always use the document URL when referencing documents in conversations or sharing with others.",
51
+ "The response provides complete document metadata, page structure, and requested page content.",
52
+ `Document URLs look like this: ${(0, utils_1.generateDocumentUrl)('doc_id', 'page_id')}`,
53
+ ].join("\n"), {
54
+ doc_id: zod_1.z
55
+ .string()
56
+ .min(1)
57
+ .describe("The document ID to read"),
58
+ page: zod_1.z
59
+ .string()
60
+ .optional()
61
+ .describe("Optional specific page ID or name to read (defaults to first page)")
62
+ }, {
63
+ readOnlyHint: true
64
+ }, async ({ doc_id, page }) => {
65
+ try {
66
+ // First get the document details and page structure
67
+ const [docResponse, pagesResponse] = await Promise.all([
68
+ fetch(`https://api.clickup.com/api/v3/workspaces/${config_1.CONFIG.teamId}/docs/${doc_id}`, {
69
+ headers: { Authorization: config_1.CONFIG.apiKey },
70
+ }),
71
+ fetch(`https://api.clickup.com/api/v3/workspaces/${config_1.CONFIG.teamId}/docs/${doc_id}/pageListing`, {
72
+ headers: { Authorization: config_1.CONFIG.apiKey },
73
+ })
74
+ ]);
75
+ if (!docResponse.ok) {
76
+ throw new Error(`Error fetching document: ${docResponse.status} ${docResponse.statusText}`);
77
+ }
78
+ if (!pagesResponse.ok) {
79
+ throw new Error(`Error fetching pages: ${pagesResponse.status} ${pagesResponse.statusText}`);
80
+ }
81
+ const docData = await docResponse.json();
82
+ const pagesData = await pagesResponse.json();
83
+ const doc = docData; // Document data is flat
84
+ // Extract all pages while preserving hierarchy for display
85
+ const pages = [];
86
+ const hierarchicalPages = pagesData; // Keep original structure for hierarchy display
87
+ // Extract flat list of pages for searching and navigation
88
+ pagesData.forEach((pageGroup) => {
89
+ pages.push(...extractAllPages(pageGroup));
90
+ });
91
+ if (pages.length === 0) {
92
+ return {
93
+ content: [{
94
+ type: "text",
95
+ text: `📄 **Document: ${doc.name}** (doc_id: ${doc_id})
96
+ 📎 Document URL: ${(0, utils_1.generateDocumentUrl)(doc_id)}
97
+
98
+ ⚠️ This document exists but has no pages yet.
99
+
100
+ **Next steps:**
101
+ - Use \`writeDocument\` with parent_type="doc" and parent_id="${doc_id}" to add the first page
102
+ - Example: writeDocument(parent_type="doc", parent_id="${doc_id}", page_name="Introduction", content="Your content here")`
103
+ }],
104
+ };
105
+ }
106
+ // Determine which page to read
107
+ let targetPage = null;
108
+ if (page) {
109
+ // Look for page by ID first, then by name
110
+ targetPage = pages.find((p) => p.id === page || p.name === page);
111
+ if (!targetPage) {
112
+ return {
113
+ content: [{
114
+ type: "text",
115
+ text: `Page "${page}" not found in document "${doc.name}". Available pages: ${pages.map((p) => `"${p.name}" (${p.id})`).join(', ')}`
116
+ }],
117
+ };
118
+ }
119
+ }
120
+ else {
121
+ // Default to first page
122
+ targetPage = pages[0];
123
+ }
124
+ // Get the specific page content
125
+ const pageResponse = await fetch(`https://api.clickup.com/api/v3/workspaces/${config_1.CONFIG.teamId}/docs/${doc_id}/pages/${targetPage.id}`, {
126
+ headers: { Authorization: config_1.CONFIG.apiKey },
127
+ });
128
+ if (!pageResponse.ok) {
129
+ throw new Error(`Error fetching page content: ${pageResponse.status} ${pageResponse.statusText}`);
130
+ }
131
+ const pageData = await pageResponse.json();
132
+ const pageContent = pageData; // Page data is flat
133
+ // Build the response
134
+ const result = [];
135
+ // Document header with metadata
136
+ result.push(`doc_id: ${doc.id}`);
137
+ result.push(`Document Title: ${doc.name}`);
138
+ result.push(`Document URL: ${(0, utils_1.generateDocumentUrl)(doc_id)}`);
139
+ result.push(`Current page_id: ${targetPage.id}`);
140
+ result.push(`Current Page Title: ${pageContent.name}`);
141
+ result.push(`Current Page URL: ${(0, utils_1.generateDocumentUrl)(doc_id, targetPage.id)}`);
142
+ // Page structure overview with hierarchy
143
+ result.push('Page Structure:');
144
+ hierarchicalPages.forEach((pageGroup) => {
145
+ result.push(...displayPageHierarchy(pageGroup, targetPage.id));
146
+ });
147
+ // Current page content
148
+ if (pageContent.content && pageContent.content.trim()) {
149
+ result.push(`Page Content:`);
150
+ return {
151
+ content: [
152
+ { type: "text", text: result.join('\n') },
153
+ { type: "text", text: pageContent.content },
154
+ ],
155
+ };
156
+ }
157
+ else {
158
+ result.push('*This page is empty.*');
159
+ result.push('');
160
+ result.push('**💡 To add content to this page:**');
161
+ result.push(`Use \`writeDocument\` with page_id="${targetPage.id}" and your content.`);
162
+ result.push(`Example: writeDocument(page_id="${targetPage.id}", content="Your content here")`);
163
+ return {
164
+ content: [
165
+ { type: "text", text: result.join('\n') },
166
+ ],
167
+ };
168
+ }
169
+ }
170
+ catch (error) {
171
+ console.error('Error reading document:', error);
172
+ return {
173
+ content: [
174
+ {
175
+ type: "text",
176
+ text: `Error reading document ${doc_id}: ${error instanceof Error ? error.message : 'Unknown error'}`,
177
+ },
178
+ ],
179
+ };
180
+ }
181
+ });
182
+ // Dynamically construct the searchDocuments description
183
+ const searchDocumentsDescriptionBase = [
184
+ "Search documents by name and space with fuzzy matching and support for multiple search terms (OR logic).",
185
+ "This can be a great knowledge based for general project information. Consider searching for documents and for tasks at the same time.",
186
+ "Can filter by specific space_ids. If no search terms provided, returns most recently updated documents."
187
+ ];
188
+ if (config_1.CONFIG.primaryLanguageHint && config_1.CONFIG.primaryLanguageHint.toLowerCase() !== 'en') {
189
+ searchDocumentsDescriptionBase.push(`For optimal results, as your ClickUp documents may be primarily in '${config_1.CONFIG.primaryLanguageHint}', consider providing search terms in English and '${config_1.CONFIG.primaryLanguageHint}'.`);
190
+ }
191
+ searchDocumentsDescriptionBase.push("Always reference documents by their URLs when discussing search results or suggesting actions.");
192
+ searchDocumentsDescriptionBase.push("You'll get document overview with space context - use readDocument to get full content.");
193
+ server.tool("searchDocuments", searchDocumentsDescriptionBase.join("\n"), {
194
+ terms: zod_1.z
195
+ .array(zod_1.z.string())
196
+ .optional()
197
+ .describe("Array of search terms to match against document names and spaces. If not provided, returns most recent documents."),
198
+ space_ids: zod_1.z
199
+ .array(zod_1.z.string())
200
+ .optional()
201
+ .describe("Filter documents to specific space IDs")
202
+ }, {
203
+ readOnlyHint: true
204
+ }, async ({ terms, space_ids }) => {
205
+ try {
206
+ // Get the document search index
207
+ const searchIndex = await (0, utils_1.getDocumentSearchIndex)(space_ids);
208
+ if (!searchIndex) {
209
+ return {
210
+ content: [
211
+ {
212
+ type: "text",
213
+ text: "Unable to create document search index.",
214
+ },
215
+ ],
216
+ };
217
+ }
218
+ let results;
219
+ if (terms && terms.length > 0) {
220
+ // Perform multi-term search
221
+ results = await (0, utils_1.performMultiTermSearch)(searchIndex, terms);
222
+ }
223
+ else {
224
+ // Return all documents sorted by creation date (most recent first)
225
+ const allResults = searchIndex._docs || [];
226
+ results = allResults
227
+ .sort((a, b) => new Date(b.date_created || 0).getTime() - new Date(a.date_created || 0).getTime());
228
+ }
229
+ // Limit results to prevent overwhelming the LLM
230
+ const limitedResults = results.slice(0, 50);
231
+ if (limitedResults.length === 0) {
232
+ const searchTermsText = terms && terms.length > 0 ? ` for terms: ${terms.join(", ")}` : "";
233
+ const spaceFilterText = space_ids && space_ids.length > 0 ? ` in spaces: ${space_ids.join(", ")}` : "";
234
+ return {
235
+ content: [
236
+ {
237
+ type: "text",
238
+ text: [
239
+ `No documents found${searchTermsText}${spaceFilterText}.`,
240
+ `The content of documents is not searched, so ask the user for more details if needed.`,
241
+ ].join("\n"),
242
+ },
243
+ ],
244
+ };
245
+ }
246
+ // Format results
247
+ const responseLines = [
248
+ `Found ${limitedResults.length} document${limitedResults.length === 1 ? "" : "s"}:`
249
+ ];
250
+ limitedResults.forEach((doc, index) => {
251
+ const docUrl = (0, utils_1.generateDocumentUrl)(doc.id);
252
+ const createdDate = doc.date_created ? new Date(+doc.date_created).toLocaleDateString() : 'Unknown';
253
+ const meta = [`doc_id ${doc.id}`];
254
+ // Show parent information
255
+ if (doc.parent_info) {
256
+ meta.push(doc.parent_info);
257
+ }
258
+ meta.push(`Created: ${createdDate}`);
259
+ responseLines.push(`- ${doc.name} (${meta.join(', ')}) ${docUrl}`);
260
+ });
261
+ responseLines.push("", "Use `readDocument` with a document ID to view full content and page structure.");
262
+ return {
263
+ content: [
264
+ {
265
+ type: "text",
266
+ text: responseLines.join("\n"),
267
+ },
268
+ ],
269
+ };
270
+ }
271
+ catch (error) {
272
+ console.error('Error searching documents:', error);
273
+ return {
274
+ content: [
275
+ {
276
+ type: "text",
277
+ text: `Error searching documents: ${error instanceof Error ? error.message : 'Unknown error'}`,
278
+ },
279
+ ],
280
+ };
281
+ }
282
+ });
283
+ }
284
+ function registerDocumentToolsWrite(server) {
285
+ server.tool("writeDocument", [
286
+ "Universal tool for document and page operations with smart document creation.",
287
+ "Create new documents by using parent_type 'space' or 'list'.",
288
+ "Create pages in existing documents by using parent_type 'doc'.",
289
+ "Create sub-pages by using parent_type 'page'.",
290
+ "Update existing pages by providing page_id.",
291
+ "Always reference documents by their URLs when creating or updating."
292
+ ].join("\n"), {
293
+ page_id: zod_1.z
294
+ .string()
295
+ .optional()
296
+ .describe("Optional page ID to update (if provided, updates existing page; if not, creates new page/document)"),
297
+ parent_type: zod_1.z
298
+ .enum(["space", "list", "doc", "page"])
299
+ .optional()
300
+ .describe("Type of parent when creating new content (required if page_id not provided)"),
301
+ parent_id: zod_1.z
302
+ .string()
303
+ .optional()
304
+ .describe("ID of parent (space, list, document, or page) when creating new content (required if page_id not provided)"),
305
+ page_name: zod_1.z
306
+ .string()
307
+ .optional()
308
+ .describe("Name for the page (required for new pages, optional for updates to rename)"),
309
+ content: zod_1.z
310
+ .string()
311
+ .optional()
312
+ .describe("Page content in markdown format (optional for updates)"),
313
+ append: zod_1.z
314
+ .boolean()
315
+ .optional()
316
+ .describe("Whether to append content to existing page content (default: false - replaces content)")
317
+ }, {
318
+ readOnlyHint: false
319
+ }, async ({ page_id, parent_type, parent_id, page_name, content, append = false }) => {
320
+ try {
321
+ // Validate input parameters
322
+ if (!page_id && (!parent_type || !parent_id)) {
323
+ return {
324
+ content: [{
325
+ type: "text",
326
+ text: "Error: Either page_id (for updates) or both parent_type and parent_id (for creation) must be provided."
327
+ }],
328
+ };
329
+ }
330
+ if (!page_id && !page_name) {
331
+ return {
332
+ content: [{
333
+ type: "text",
334
+ text: "Error: page_name is required when creating new pages."
335
+ }],
336
+ };
337
+ }
338
+ // Case 1: Update existing page
339
+ if (page_id) {
340
+ const requestBody = {};
341
+ if (page_name) {
342
+ requestBody.name = page_name;
343
+ }
344
+ if (content !== undefined) {
345
+ requestBody.content = content;
346
+ // Use native append mode from ClickUp API
347
+ requestBody.content_edit_mode = append ? 'append' : 'replace';
348
+ }
349
+ const response = await fetch(`https://api.clickup.com/api/v3/docs/pages/${page_id}`, {
350
+ method: 'PUT',
351
+ headers: {
352
+ Authorization: config_1.CONFIG.apiKey,
353
+ 'Content-Type': 'application/json',
354
+ },
355
+ body: JSON.stringify(requestBody),
356
+ });
357
+ if (!response.ok) {
358
+ throw new Error(`Error updating page: ${response.status} ${response.statusText}`);
359
+ }
360
+ const data = await response.json();
361
+ const updatedPage = data.page;
362
+ return {
363
+ content: [{
364
+ type: "text",
365
+ text: `✅ Successfully updated page "${updatedPage.name}" (${page_id})\n\nPage URL: ${(0, utils_1.generateDocumentUrl)(updatedPage.doc_id, page_id)}`
366
+ }],
367
+ };
368
+ }
369
+ // Case 2: Create new content
370
+ let docId;
371
+ if (parent_type === "space" || parent_type === "list") {
372
+ // Create new document first, then create first page manually
373
+ const docRequestBody = {
374
+ name: page_name, // Document name matches first page name
375
+ create_page: false, // Don't auto-create page, we'll create it manually for better control
376
+ parent: {
377
+ id: parent_id,
378
+ type: parent_type === "space" ? 4 : parent_type === "list" ? 6 : 7 // 4=Space, 6=List, 7=Workspace
379
+ }
380
+ };
381
+ const docResponse = await fetch(`https://api.clickup.com/api/v3/workspaces/${config_1.CONFIG.teamId}/docs`, {
382
+ method: 'POST',
383
+ headers: {
384
+ Authorization: config_1.CONFIG.apiKey,
385
+ 'Content-Type': 'application/json',
386
+ },
387
+ body: JSON.stringify(docRequestBody),
388
+ });
389
+ if (!docResponse.ok) {
390
+ throw new Error(`Error creating document: ${docResponse.status} ${docResponse.statusText}`);
391
+ }
392
+ const docData = await docResponse.json();
393
+ docId = docData.id;
394
+ // Create the first page manually for better control
395
+ const pageRequestBody = {
396
+ name: page_name,
397
+ content: content || '',
398
+ };
399
+ const pageResponse = await fetch(`https://api.clickup.com/api/v3/workspaces/${config_1.CONFIG.teamId}/docs/${docId}/pages`, {
400
+ method: 'POST',
401
+ headers: {
402
+ Authorization: config_1.CONFIG.apiKey,
403
+ 'Content-Type': 'application/json',
404
+ },
405
+ body: JSON.stringify(pageRequestBody),
406
+ });
407
+ if (!pageResponse.ok) {
408
+ throw new Error(`Error creating first page: ${pageResponse.status} ${pageResponse.statusText}`);
409
+ }
410
+ const pageData = await pageResponse.json();
411
+ const firstPage = pageData.page || pageData;
412
+ return {
413
+ content: [{
414
+ type: "text",
415
+ text: `✅ Successfully created new document "${page_name}" with first page\n\nDocument URL: ${(0, utils_1.generateDocumentUrl)(docId)}\nFirst Page URL: ${(0, utils_1.generateDocumentUrl)(docId, firstPage.id)}`
416
+ }],
417
+ };
418
+ }
419
+ else if (parent_type === "doc") {
420
+ // Create new page in existing document
421
+ docId = parent_id;
422
+ const pageRequestBody = {
423
+ name: page_name,
424
+ content: content || '',
425
+ };
426
+ const response = await fetch(`https://api.clickup.com/api/v3/workspaces/${config_1.CONFIG.teamId}/docs/${docId}/pages`, {
427
+ method: 'POST',
428
+ headers: {
429
+ Authorization: config_1.CONFIG.apiKey,
430
+ 'Content-Type': 'application/json',
431
+ },
432
+ body: JSON.stringify(pageRequestBody),
433
+ });
434
+ if (!response.ok) {
435
+ throw new Error(`Error creating page: ${response.status} ${response.statusText}`);
436
+ }
437
+ const data = await response.json();
438
+ const newPage = data.page;
439
+ return {
440
+ content: [{
441
+ type: "text",
442
+ text: `✅ Successfully created page "${newPage.name}" (${newPage.id}) in document\n\nPage URL: ${(0, utils_1.generateDocumentUrl)(docId, newPage.id)}`
443
+ }],
444
+ };
445
+ }
446
+ else if (parent_type === "page") {
447
+ // Create sub-page under existing page
448
+ const pageRequestBody = {
449
+ name: page_name,
450
+ content: content || '',
451
+ parent_page_id: parent_id
452
+ };
453
+ // For sub-pages, we need to get the doc_id from the parent page first
454
+ // This is a limitation we'll need to handle - for now, use a generic endpoint
455
+ const response = await fetch(`https://api.clickup.com/api/v3/docs/pages`, {
456
+ method: 'POST',
457
+ headers: {
458
+ Authorization: config_1.CONFIG.apiKey,
459
+ 'Content-Type': 'application/json',
460
+ },
461
+ body: JSON.stringify(pageRequestBody),
462
+ });
463
+ if (!response.ok) {
464
+ throw new Error(`Error creating sub-page: ${response.status} ${response.statusText}`);
465
+ }
466
+ const data = await response.json();
467
+ const newPage = data.page;
468
+ return {
469
+ content: [{
470
+ type: "text",
471
+ text: `✅ Successfully created sub-page "${newPage.name}" (${newPage.id}) under parent page ${parent_id}\n\nPage URL: ${(0, utils_1.generateDocumentUrl)(newPage.doc_id, newPage.id)}`
472
+ }],
473
+ };
474
+ }
475
+ return {
476
+ content: [{ type: "text", text: "Error: Invalid parent_type specified." }],
477
+ };
478
+ }
479
+ catch (error) {
480
+ console.error('Error in writeDocument:', error);
481
+ return {
482
+ content: [
483
+ {
484
+ type: "text",
485
+ text: `Error in document/page operation: ${error instanceof Error ? error.message : 'Unknown error'}`,
486
+ },
487
+ ],
488
+ };
489
+ }
490
+ });
491
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"list-tools.d.ts","sourceRoot":"","sources":["../../src/tools/list-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAMpE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,QA+GtD;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,QAyEvD"}
1
+ {"version":3,"file":"list-tools.d.ts","sourceRoot":"","sources":["../../src/tools/list-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKpE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,QAkHtD;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,QA4EvD"}
@@ -14,6 +14,8 @@ function registerListToolsRead(server) {
14
14
  "Share the clickable list URL when suggesting list-related actions."
15
15
  ].join("\n"), {
16
16
  list_id: zod_1.z.string().min(1).describe("The list ID to get information for")
17
+ }, {
18
+ readOnlyHint: true
17
19
  }, async ({ list_id }) => {
18
20
  try {
19
21
  // Get list details including statuses (try to get markdown content)
@@ -115,6 +117,8 @@ function registerListToolsWrite(server) {
115
117
  ].join("\n"), {
116
118
  list_id: zod_1.z.string().min(1).describe("The list ID to update"),
117
119
  append_description: zod_1.z.string().min(1).describe("Markdown content to APPEND to existing list description (preserves existing content for safety)")
120
+ }, {
121
+ readOnlyHint: false
118
122
  }, async ({ list_id, append_description }) => {
119
123
  try {
120
124
  // Get current list info including description (try to get markdown content)
@@ -1 +1 @@
1
- {"version":3,"file":"search-tools.d.ts","sourceRoot":"","sources":["../../src/tools/search-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AASlE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,QA8MnE"}
1
+ {"version":3,"file":"search-tools.d.ts","sourceRoot":"","sources":["../../src/tools/search-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AASlE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,QA2LnE"}
@@ -9,7 +9,7 @@ const MAX_SEARCH_RESULTS = 50;
9
9
  function registerSearchTools(server, userData) {
10
10
  // Dynamically construct the searchTasks description
11
11
  const searchTasksDescriptionBase = [
12
- "Searches tasks (sometimes called Tickets or Cards) by name, content, assignees, and ID (case insensitive) with fuzzy matching and support for multiple search terms (OR logic).",
12
+ "Searches tasks (sometimes called Tickets or Cards) by name, content, assignees, and ID with fuzzy matching and support for multiple search terms (OR logic).",
13
13
  "Can filter by multiple list_ids, space_ids, todo status, or tasks assigned to the current user. If no search terms provided, returns most recently updated tasks.",
14
14
  "Can also be used to find tasks for the current user by providing the assigned_to_me flag."
15
15
  ];
@@ -44,6 +44,8 @@ function registerSearchTools(server, userData) {
44
44
  .boolean()
45
45
  .optional()
46
46
  .describe(`Filter for tasks assigned to the current user (${userData.user.username} (${userData.user.id}))`),
47
+ }, {
48
+ readOnlyHint: true
47
49
  }, async ({ terms, list_ids, space_ids, only_todo, status, assigned_to_me }) => {
48
50
  // Get current user ID if filtering by assigned_to_me
49
51
  const assignees = assigned_to_me ? [userData.user.id] : [];
@@ -94,37 +96,15 @@ function registerSearchTools(server, userData) {
94
96
  content: await Promise.all(resultTasks.map((task) => (0, task_tools_1.generateTaskMetadata)(task, timeEntries))),
95
97
  };
96
98
  }
97
- // Filter valid search terms
98
- const validTerms = terms.filter(term => term && term.trim().length > 0);
99
- if (validTerms.length === 0) {
100
- return {
101
- content: [
102
- {
103
- type: "text",
104
- text: "No valid search terms provided.",
105
- },
106
- ],
107
- };
108
- }
99
+ // Create a results map to track unique tasks with scores
109
100
  const uniqueResults = new Map();
110
- // Search with each term
111
- validTerms.forEach(term => {
112
- const results = searchIndex.search(term.toLowerCase());
113
- results.forEach(result => {
114
- if (result.item && typeof result.item.id === 'string') {
115
- const currentScore = result.score ?? 1;
116
- const existing = uniqueResults.get(result.item.id);
117
- if (!existing || currentScore < existing.score) {
118
- uniqueResults.set(result.item.id, {
119
- item: result.item,
120
- score: currentScore
121
- });
122
- }
123
- }
124
- });
101
+ // Perform multi-term search with aggressive boosting
102
+ const searchResults = await (0, utils_1.performMultiTermSearch)(searchIndex, terms);
103
+ searchResults.forEach(task => {
104
+ uniqueResults.set(task.id, { item: task, score: 0.1 }); // Give search results a good score
125
105
  });
126
106
  // Task ID Fallback Logic
127
- const potentialTaskIds = validTerms.filter(utils_1.isTaskId);
107
+ const potentialTaskIds = terms.filter(utils_1.isTaskId);
128
108
  const foundTaskIdsByFuse = new Set(Array.from(uniqueResults.keys()).map(id => id.toLowerCase()));
129
109
  const taskIdsToFetchDirectly = potentialTaskIds.filter(id => {
130
110
  const lowerId = id.toLowerCase();
@@ -1 +1 @@
1
- {"version":3,"file":"space-tools.d.ts","sourceRoot":"","sources":["../../src/tools/space-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAMpE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,QAqNnD"}
1
+ {"version":3,"file":"space-tools.d.ts","sourceRoot":"","sources":["../../src/tools/space-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKpE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,QAoNnD"}