@mhalder/qdrant-mcp-server 3.3.3 → 3.3.4

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 (168) hide show
  1. package/.github/workflows/ci.yml +0 -2
  2. package/.github/workflows/claude-code-review.yml +1 -1
  3. package/CHANGELOG.md +6 -0
  4. package/README.md +1 -1
  5. package/biome.json +3 -2
  6. package/build/code/chunker/tree-sitter-chunker.d.ts.map +1 -1
  7. package/build/code/chunker/tree-sitter-chunker.js +2 -12
  8. package/build/code/chunker/tree-sitter-chunker.js.map +1 -1
  9. package/build/code/indexer.d.ts.map +1 -1
  10. package/build/code/indexer.js +12 -18
  11. package/build/code/indexer.js.map +1 -1
  12. package/build/code/scanner.js +1 -1
  13. package/build/code/scanner.js.map +1 -1
  14. package/build/embeddings/cohere.d.ts +1 -1
  15. package/build/embeddings/cohere.d.ts.map +1 -1
  16. package/build/embeddings/cohere.js +2 -2
  17. package/build/embeddings/cohere.js.map +1 -1
  18. package/build/embeddings/cohere.test.js +1 -5
  19. package/build/embeddings/cohere.test.js.map +1 -1
  20. package/build/embeddings/factory.d.ts +1 -1
  21. package/build/embeddings/factory.d.ts.map +1 -1
  22. package/build/embeddings/factory.js +7 -9
  23. package/build/embeddings/factory.js.map +1 -1
  24. package/build/embeddings/factory.test.js +3 -3
  25. package/build/embeddings/factory.test.js.map +1 -1
  26. package/build/embeddings/ollama.d.ts +1 -1
  27. package/build/embeddings/ollama.d.ts.map +1 -1
  28. package/build/embeddings/ollama.js +6 -8
  29. package/build/embeddings/ollama.js.map +1 -1
  30. package/build/embeddings/ollama.test.js +2 -6
  31. package/build/embeddings/ollama.test.js.map +1 -1
  32. package/build/embeddings/openai.d.ts +1 -1
  33. package/build/embeddings/openai.d.ts.map +1 -1
  34. package/build/embeddings/openai.js +4 -7
  35. package/build/embeddings/openai.js.map +1 -1
  36. package/build/embeddings/openai.test.js +3 -12
  37. package/build/embeddings/openai.test.js.map +1 -1
  38. package/build/embeddings/sparse.test.js +12 -2
  39. package/build/embeddings/sparse.test.js.map +1 -1
  40. package/build/embeddings/voyage.d.ts +1 -1
  41. package/build/embeddings/voyage.d.ts.map +1 -1
  42. package/build/embeddings/voyage.js +2 -3
  43. package/build/embeddings/voyage.js.map +1 -1
  44. package/build/embeddings/voyage.test.js +2 -6
  45. package/build/embeddings/voyage.test.js.map +1 -1
  46. package/build/git/chunker.d.ts.map +1 -1
  47. package/build/git/chunker.js +2 -2
  48. package/build/git/chunker.js.map +1 -1
  49. package/build/git/chunker.test.js +1 -1
  50. package/build/git/chunker.test.js.map +1 -1
  51. package/build/git/extractor.d.ts.map +1 -1
  52. package/build/git/extractor.integration.test.js +9 -5
  53. package/build/git/extractor.integration.test.js.map +1 -1
  54. package/build/git/extractor.js +1 -1
  55. package/build/git/extractor.js.map +1 -1
  56. package/build/git/extractor.test.js +2 -2
  57. package/build/git/extractor.test.js.map +1 -1
  58. package/build/git/index.d.ts +4 -4
  59. package/build/git/index.d.ts.map +1 -1
  60. package/build/git/index.js +3 -3
  61. package/build/git/index.js.map +1 -1
  62. package/build/git/indexer.d.ts.map +1 -1
  63. package/build/git/indexer.js +9 -21
  64. package/build/git/indexer.js.map +1 -1
  65. package/build/git/indexer.test.js +4 -8
  66. package/build/git/indexer.test.js.map +1 -1
  67. package/build/git/sync/synchronizer.d.ts.map +1 -1
  68. package/build/git/sync/synchronizer.js.map +1 -1
  69. package/build/git/sync/synchronizer.test.js +4 -2
  70. package/build/git/sync/synchronizer.test.js.map +1 -1
  71. package/build/index.js +5 -9
  72. package/build/index.js.map +1 -1
  73. package/build/index.test.js +3 -3
  74. package/build/index.test.js.map +1 -1
  75. package/build/logger.d.ts.map +1 -1
  76. package/build/logger.js +1 -9
  77. package/build/logger.js.map +1 -1
  78. package/build/prompts/register.d.ts.map +1 -1
  79. package/build/prompts/register.js.map +1 -1
  80. package/build/qdrant/client.d.ts.map +1 -1
  81. package/build/qdrant/client.js.map +1 -1
  82. package/build/qdrant/client.test.js +10 -34
  83. package/build/qdrant/client.test.js.map +1 -1
  84. package/build/resources/index.d.ts +1 -1
  85. package/build/resources/index.d.ts.map +1 -1
  86. package/build/resources/index.js +1 -1
  87. package/build/resources/index.js.map +1 -1
  88. package/build/tools/code.d.ts.map +1 -1
  89. package/build/tools/code.js +3 -9
  90. package/build/tools/code.js.map +1 -1
  91. package/build/tools/collection.d.ts.map +1 -1
  92. package/build/tools/collection.js +1 -3
  93. package/build/tools/collection.js.map +1 -1
  94. package/build/tools/document.d.ts.map +1 -1
  95. package/build/tools/document.js +1 -1
  96. package/build/tools/document.js.map +1 -1
  97. package/build/tools/federated.d.ts.map +1 -1
  98. package/build/tools/federated.js +15 -6
  99. package/build/tools/federated.js.map +1 -1
  100. package/build/tools/federated.test.js +18 -22
  101. package/build/tools/federated.test.js.map +1 -1
  102. package/build/tools/git-history.d.ts.map +1 -1
  103. package/build/tools/git-history.js +3 -7
  104. package/build/tools/git-history.js.map +1 -1
  105. package/build/tools/index.d.ts.map +1 -1
  106. package/build/tools/index.js.map +1 -1
  107. package/build/tools/logging.d.ts.map +1 -1
  108. package/build/tools/logging.js +1 -3
  109. package/build/tools/logging.js.map +1 -1
  110. package/build/tools/logging.test.js +1 -1
  111. package/build/tools/logging.test.js.map +1 -1
  112. package/build/tools/schemas.d.ts.map +1 -1
  113. package/build/tools/schemas.js +17 -64
  114. package/build/tools/schemas.js.map +1 -1
  115. package/build/tools/search.d.ts.map +1 -1
  116. package/build/tools/search.js +1 -1
  117. package/build/tools/search.js.map +1 -1
  118. package/commitlint.config.js +12 -23
  119. package/package.json +1 -1
  120. package/scripts/verify-providers.js +12 -32
  121. package/src/code/chunker/tree-sitter-chunker.ts +9 -35
  122. package/src/code/indexer.ts +45 -107
  123. package/src/code/scanner.ts +1 -1
  124. package/src/embeddings/cohere.test.ts +17 -45
  125. package/src/embeddings/cohere.ts +10 -17
  126. package/src/embeddings/factory.test.ts +18 -18
  127. package/src/embeddings/factory.ts +18 -25
  128. package/src/embeddings/ollama.test.ts +38 -67
  129. package/src/embeddings/ollama.ts +15 -27
  130. package/src/embeddings/openai.test.ts +17 -53
  131. package/src/embeddings/openai.ts +11 -22
  132. package/src/embeddings/sparse.test.ts +12 -2
  133. package/src/embeddings/voyage.test.ts +39 -80
  134. package/src/embeddings/voyage.ts +9 -13
  135. package/src/git/chunker.test.ts +1 -1
  136. package/src/git/chunker.ts +6 -22
  137. package/src/git/extractor.integration.test.ts +12 -16
  138. package/src/git/extractor.test.ts +21 -35
  139. package/src/git/extractor.ts +14 -36
  140. package/src/git/index.ts +9 -10
  141. package/src/git/indexer.test.ts +29 -57
  142. package/src/git/indexer.ts +38 -86
  143. package/src/git/sync/synchronizer.test.ts +6 -9
  144. package/src/git/sync/synchronizer.ts +2 -5
  145. package/src/index.test.ts +7 -9
  146. package/src/index.ts +34 -80
  147. package/src/logger.ts +3 -14
  148. package/src/prompts/register.ts +3 -10
  149. package/src/qdrant/client.test.ts +63 -169
  150. package/src/qdrant/client.ts +19 -45
  151. package/src/resources/index.ts +4 -10
  152. package/src/tools/code.ts +43 -66
  153. package/src/tools/collection.ts +19 -38
  154. package/src/tools/document.ts +10 -19
  155. package/src/tools/federated.test.ts +34 -57
  156. package/src/tools/federated.ts +88 -108
  157. package/src/tools/git-history.ts +32 -60
  158. package/src/tools/index.ts +1 -4
  159. package/src/tools/logging.test.ts +10 -10
  160. package/src/tools/logging.ts +8 -18
  161. package/src/tools/schemas.ts +23 -78
  162. package/src/tools/search.ts +77 -94
  163. package/tests/code/chunker/tree-sitter-chunker.test.ts +6 -19
  164. package/tests/code/indexer.test.ts +100 -192
  165. package/tests/code/integration.test.ts +61 -117
  166. package/tests/code/scanner.test.ts +12 -39
  167. package/tests/code/sync/snapshot.test.ts +4 -14
  168. package/tests/code/sync/synchronizer.test.ts +10 -40
@@ -7,11 +7,11 @@
7
7
  */
8
8
 
9
9
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
- import logger from "../logger.js";
11
10
  import type { CodeIndexer } from "../code/indexer.js";
12
11
  import type { CodeSearchResult } from "../code/types.js";
13
12
  import type { GitHistoryIndexer } from "../git/indexer.js";
14
13
  import type { GitSearchResult } from "../git/types.js";
14
+ import logger from "../logger.js";
15
15
  import { withToolLogging } from "./logging.js";
16
16
  import * as schemas from "./schemas.js";
17
17
 
@@ -85,7 +85,7 @@ export interface FederatedSearchResponse {
85
85
  */
86
86
  export function buildCorrelations(
87
87
  codeResults: CodeSearchResult[],
88
- gitResults: GitSearchResult[],
88
+ gitResults: GitSearchResult[]
89
89
  ): CodeCommitCorrelation[] {
90
90
  const correlations: CodeCommitCorrelation[] = [];
91
91
 
@@ -95,9 +95,7 @@ export function buildCorrelations(
95
95
  // Find commits that modified this file
96
96
  for (const gitResult of gitResults) {
97
97
  // Check if any file in the commit matches the code result's file path
98
- const matchesFile = gitResult.files.some((file) =>
99
- pathsMatch(codeResult.filePath, file),
100
- );
98
+ const matchesFile = gitResult.files.some((file) => pathsMatch(codeResult.filePath, file));
101
99
 
102
100
  if (matchesFile) {
103
101
  relatedCommits.push({
@@ -157,9 +155,7 @@ export function pathsMatch(path1: string, path2: string): boolean {
157
155
  /**
158
156
  * Normalize scores to [0, 1] range using min-max normalization
159
157
  */
160
- export function normalizeScores<T extends { score: number }>(
161
- results: T[],
162
- ): T[] {
158
+ export function normalizeScores<T extends { score: number }>(results: T[]): T[] {
163
159
  if (results.length === 0) return [];
164
160
  if (results.length === 1) return results.map((r) => ({ ...r, score: 1 }));
165
161
 
@@ -203,7 +199,7 @@ async function performContextualSearch(
203
199
  codeLimit?: number;
204
200
  gitLimit?: number;
205
201
  correlate?: boolean;
206
- },
202
+ }
207
203
  ): Promise<ContextualSearchResult> {
208
204
  const { path, query, codeLimit = 5, gitLimit = 5, correlate = true } = params;
209
205
 
@@ -214,15 +210,11 @@ async function performContextualSearch(
214
210
  ]);
215
211
 
216
212
  if (codeStatus.status !== "indexed") {
217
- throw new Error(
218
- `Code index not found for "${path}". Run index_codebase first.`,
219
- );
213
+ throw new Error(`Code index not found for "${path}". Run index_codebase first.`);
220
214
  }
221
215
 
222
216
  if (gitStatus.status !== "indexed") {
223
- throw new Error(
224
- `Git history index not found for "${path}". Run index_git_history first.`,
225
- );
217
+ throw new Error(`Git history index not found for "${path}". Run index_git_history first.`);
226
218
  }
227
219
 
228
220
  // Execute searches in parallel
@@ -232,9 +224,7 @@ async function performContextualSearch(
232
224
  ]);
233
225
 
234
226
  // Build correlations if requested
235
- const correlations = correlate
236
- ? buildCorrelations(codeResults, gitResults)
237
- : [];
227
+ const correlations = correlate ? buildCorrelations(codeResults, gitResults) : [];
238
228
 
239
229
  return {
240
230
  codeResults,
@@ -261,7 +251,7 @@ async function performFederatedSearch(
261
251
  query: string;
262
252
  searchType?: "code" | "git" | "both";
263
253
  limit?: number;
264
- },
254
+ }
265
255
  ): Promise<FederatedSearchResponse> {
266
256
  const { paths, query, searchType = "both", limit = 20 } = params;
267
257
 
@@ -306,8 +296,8 @@ async function performFederatedSearch(
306
296
  ...r,
307
297
  resultType: "code" as const,
308
298
  repoPath: path,
309
- })),
310
- ),
299
+ }))
300
+ )
311
301
  );
312
302
  }
313
303
 
@@ -322,8 +312,8 @@ async function performFederatedSearch(
322
312
  ...r,
323
313
  resultType: "git" as const,
324
314
  repoPath: path,
325
- })),
326
- ),
315
+ }))
316
+ )
327
317
  );
328
318
  }
329
319
  }
@@ -333,11 +323,10 @@ async function performFederatedSearch(
333
323
 
334
324
  // Normalize scores per result type to ensure fair comparison
335
325
  const codeResults = allResults.filter(
336
- (r): r is FederatedResult & { resultType: "code" } =>
337
- r.resultType === "code",
326
+ (r): r is FederatedResult & { resultType: "code" } => r.resultType === "code"
338
327
  );
339
328
  const gitResults = allResults.filter(
340
- (r): r is FederatedResult & { resultType: "git" } => r.resultType === "git",
329
+ (r): r is FederatedResult & { resultType: "git" } => r.resultType === "git"
341
330
  );
342
331
 
343
332
  const normalizedCode = normalizeScores(codeResults);
@@ -353,7 +342,7 @@ async function performFederatedSearch(
353
342
  if (!groupedResults.has(key)) {
354
343
  groupedResults.set(key, []);
355
344
  }
356
- groupedResults.get(key)!.push(result);
345
+ groupedResults.get(key)?.push(result);
357
346
  }
358
347
 
359
348
  // Sort each group by score and create rank lookup
@@ -399,10 +388,7 @@ async function performFederatedSearch(
399
388
  /**
400
389
  * Register federated search tools on the MCP server
401
390
  */
402
- export function registerFederatedTools(
403
- server: McpServer,
404
- deps: FederatedToolDependencies,
405
- ): void {
391
+ export function registerFederatedTools(server: McpServer, deps: FederatedToolDependencies): void {
406
392
  const { codeIndexer, gitHistoryIndexer } = deps;
407
393
 
408
394
  // contextual_search
@@ -419,16 +405,15 @@ export function registerFederatedTools(
419
405
  withToolLogging(
420
406
  "contextual_search",
421
407
  async ({ path, query, codeLimit, gitLimit, correlate }) => {
422
- log.info(
423
- { tool: "contextual_search", path, query: query.substring(0, 80) },
424
- "Tool called",
425
- );
408
+ log.info({ tool: "contextual_search", path, query: query.substring(0, 80) }, "Tool called");
426
409
  try {
427
- const result = await performContextualSearch(
428
- codeIndexer,
429
- gitHistoryIndexer,
430
- { path, query, codeLimit, gitLimit, correlate },
431
- );
410
+ const result = await performContextualSearch(codeIndexer, gitHistoryIndexer, {
411
+ path,
412
+ query,
413
+ codeLimit,
414
+ gitLimit,
415
+ correlate,
416
+ });
432
417
 
433
418
  // Format output
434
419
  const sections: string[] = [];
@@ -444,7 +429,7 @@ export function registerFederatedTools(
444
429
  r.language +
445
430
  "\n" +
446
431
  r.content +
447
- "\n```\n",
432
+ "\n```\n"
448
433
  );
449
434
  });
450
435
  }
@@ -456,7 +441,7 @@ export function registerFederatedTools(
456
441
  sections.push(
457
442
  `### ${idx + 1}. ${r.shortHash} - ${r.subject} (score: ${r.score.toFixed(3)})\n` +
458
443
  `Author: ${r.author} | Date: ${r.date} | Type: ${r.commitType}\n` +
459
- `Files: ${r.files.slice(0, 5).join(", ")}${r.files.length > 5 ? ` (+${r.files.length - 5} more)` : ""}\n`,
444
+ `Files: ${r.files.slice(0, 5).join(", ")}${r.files.length > 5 ? ` (+${r.files.length - 5} more)` : ""}\n`
460
445
  );
461
446
  });
462
447
  }
@@ -470,7 +455,7 @@ export function registerFederatedTools(
470
455
  .map((commit) => ` - ${commit.shortHash}: ${commit.subject}`)
471
456
  .join("\n");
472
457
  sections.push(
473
- `**${c.codeResult.filePath}:${c.codeResult.startLine}** modified by:\n${commits}\n`,
458
+ `**${c.codeResult.filePath}:${c.codeResult.startLine}** modified by:\n${commits}\n`
474
459
  );
475
460
  });
476
461
  }
@@ -496,8 +481,8 @@ export function registerFederatedTools(
496
481
  isError: true,
497
482
  };
498
483
  }
499
- },
500
- ),
484
+ }
485
+ )
501
486
  );
502
487
 
503
488
  // federated_search
@@ -511,77 +496,72 @@ export function registerFederatedTools(
511
496
  "Supports code-only, git-only, or combined search modes.",
512
497
  inputSchema: schemas.FederatedSearchSchema,
513
498
  },
514
- withToolLogging(
515
- "federated_search",
516
- async ({ paths, query, searchType, limit }) => {
517
- log.info(
518
- { tool: "federated_search", paths, query: query.substring(0, 80) },
519
- "Tool called",
520
- );
521
- try {
522
- const response = await performFederatedSearch(
523
- codeIndexer,
524
- gitHistoryIndexer,
525
- { paths, query, searchType, limit },
526
- );
527
-
528
- if (response.results.length === 0) {
529
- return {
530
- content: [
531
- {
532
- type: "text",
533
- text: `No results found for query "${query}" across ${paths.length} repository(ies).`,
534
- },
535
- ],
536
- };
537
- }
538
-
539
- // Format results
540
- const sections: string[] = [
541
- `# Federated Search Results\n` +
542
- `Query: "${query}" | Type: ${response.metadata.searchType} | ` +
543
- `Repositories: ${response.metadata.repositoriesSearched.length}\n`,
544
- ];
545
-
546
- response.results.forEach((r, idx) => {
547
- if (r.resultType === "code") {
548
- sections.push(
549
- `## ${idx + 1}. [CODE] ${r.filePath}:${r.startLine}-${r.endLine}\n` +
550
- `Repository: ${r.repoPath} | Language: ${r.language} | Score: ${r.score.toFixed(3)}\n` +
551
- "```" +
552
- r.language +
553
- "\n" +
554
- r.content +
555
- "\n```\n",
556
- );
557
- } else {
558
- sections.push(
559
- `## ${idx + 1}. [GIT] ${r.shortHash} - ${r.subject}\n` +
560
- `Repository: ${r.repoPath} | Author: ${r.author} | Date: ${r.date} | Score: ${r.score.toFixed(3)}\n` +
561
- `Type: ${r.commitType} | Files: ${r.files.slice(0, 3).join(", ")}${r.files.length > 3 ? ` (+${r.files.length - 3} more)` : ""}\n`,
562
- );
563
- }
564
- });
565
-
566
- sections.push(
567
- `\n---\nTotal: ${response.metadata.totalResults} result(s) from ${response.metadata.repositoriesSearched.length} repository(ies).`,
568
- );
499
+ withToolLogging("federated_search", async ({ paths, query, searchType, limit }) => {
500
+ log.info({ tool: "federated_search", paths, query: query.substring(0, 80) }, "Tool called");
501
+ try {
502
+ const response = await performFederatedSearch(codeIndexer, gitHistoryIndexer, {
503
+ paths,
504
+ query,
505
+ searchType,
506
+ limit,
507
+ });
569
508
 
570
- return {
571
- content: [{ type: "text", text: sections.join("\n") }],
572
- };
573
- } catch (error) {
509
+ if (response.results.length === 0) {
574
510
  return {
575
511
  content: [
576
512
  {
577
513
  type: "text",
578
- text: `Error: ${error instanceof Error ? error.message : String(error)}`,
514
+ text: `No results found for query "${query}" across ${paths.length} repository(ies).`,
579
515
  },
580
516
  ],
581
- isError: true,
582
517
  };
583
518
  }
584
- },
585
- ),
519
+
520
+ // Format results
521
+ const sections: string[] = [
522
+ `# Federated Search Results\n` +
523
+ `Query: "${query}" | Type: ${response.metadata.searchType} | ` +
524
+ `Repositories: ${response.metadata.repositoriesSearched.length}\n`,
525
+ ];
526
+
527
+ response.results.forEach((r, idx) => {
528
+ if (r.resultType === "code") {
529
+ sections.push(
530
+ `## ${idx + 1}. [CODE] ${r.filePath}:${r.startLine}-${r.endLine}\n` +
531
+ `Repository: ${r.repoPath} | Language: ${r.language} | Score: ${r.score.toFixed(3)}\n` +
532
+ "```" +
533
+ r.language +
534
+ "\n" +
535
+ r.content +
536
+ "\n```\n"
537
+ );
538
+ } else {
539
+ sections.push(
540
+ `## ${idx + 1}. [GIT] ${r.shortHash} - ${r.subject}\n` +
541
+ `Repository: ${r.repoPath} | Author: ${r.author} | Date: ${r.date} | Score: ${r.score.toFixed(3)}\n` +
542
+ `Type: ${r.commitType} | Files: ${r.files.slice(0, 3).join(", ")}${r.files.length > 3 ? ` (+${r.files.length - 3} more)` : ""}\n`
543
+ );
544
+ }
545
+ });
546
+
547
+ sections.push(
548
+ `\n---\nTotal: ${response.metadata.totalResults} result(s) from ${response.metadata.repositoriesSearched.length} repository(ies).`
549
+ );
550
+
551
+ return {
552
+ content: [{ type: "text", text: sections.join("\n") }],
553
+ };
554
+ } catch (error) {
555
+ return {
556
+ content: [
557
+ {
558
+ type: "text",
559
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
560
+ },
561
+ ],
562
+ isError: true,
563
+ };
564
+ }
565
+ })
586
566
  );
587
567
  }
@@ -3,8 +3,8 @@
3
3
  */
4
4
 
5
5
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
- import logger from "../logger.js";
7
6
  import type { GitHistoryIndexer } from "../git/indexer.js";
7
+ import logger from "../logger.js";
8
8
  import { withToolLogging } from "./logging.js";
9
9
  import * as schemas from "./schemas.js";
10
10
 
@@ -14,10 +14,7 @@ export interface GitHistoryToolDependencies {
14
14
  gitHistoryIndexer: GitHistoryIndexer;
15
15
  }
16
16
 
17
- export function registerGitHistoryTools(
18
- server: McpServer,
19
- deps: GitHistoryToolDependencies,
20
- ): void {
17
+ export function registerGitHistoryTools(server: McpServer, deps: GitHistoryToolDependencies): void {
21
18
  const { gitHistoryIndexer } = deps;
22
19
 
23
20
  // index_git_history
@@ -32,20 +29,14 @@ export function registerGitHistoryTools(
32
29
  withToolLogging(
33
30
  "index_git_history",
34
31
  async ({ path, forceReindex, sinceDate, maxCommits }, extra) => {
35
- log.info(
36
- { tool: "index_git_history", path, forceReindex },
37
- "Tool called",
38
- );
32
+ log.info({ tool: "index_git_history", path, forceReindex }, "Tool called");
39
33
  const progressToken = extra._meta?.progressToken;
40
34
 
41
35
  const stats = await gitHistoryIndexer.indexHistory(
42
36
  path,
43
37
  { forceReindex, sinceDate, maxCommits },
44
38
  (progress) => {
45
- log.debug(
46
- { phase: progress.phase, percentage: progress.percentage },
47
- progress.message,
48
- );
39
+ log.debug({ phase: progress.phase, percentage: progress.percentage }, progress.message);
49
40
  if (progressToken !== undefined) {
50
41
  extra.sendNotification({
51
42
  method: "notifications/progress",
@@ -57,7 +48,7 @@ export function registerGitHistoryTools(
57
48
  },
58
49
  });
59
50
  }
60
- },
51
+ }
61
52
  );
62
53
 
63
54
  let statusMessage = `Indexed ${stats.commitsIndexed}/${stats.commitsScanned} commits (${stats.chunksCreated} chunks) in ${(stats.durationMs / 1000).toFixed(1)}s`;
@@ -72,8 +63,8 @@ export function registerGitHistoryTools(
72
63
  content: [{ type: "text", text: statusMessage }],
73
64
  isError: stats.status === "failed",
74
65
  };
75
- },
76
- ),
66
+ }
67
+ )
77
68
  );
78
69
 
79
70
  // search_git_history
@@ -87,18 +78,10 @@ export function registerGitHistoryTools(
87
78
  },
88
79
  withToolLogging(
89
80
  "search_git_history",
90
- async ({
91
- path,
92
- query,
93
- limit,
94
- commitTypes,
95
- authors,
96
- dateFrom,
97
- dateTo,
98
- }) => {
81
+ async ({ path, query, limit, commitTypes, authors, dateFrom, dateTo }) => {
99
82
  log.info(
100
83
  { tool: "search_git_history", path, query: query.substring(0, 80) },
101
- "Tool called",
84
+ "Tool called"
102
85
  );
103
86
  const results = await gitHistoryIndexer.searchHistory(path, query, {
104
87
  limit,
@@ -110,9 +93,7 @@ export function registerGitHistoryTools(
110
93
 
111
94
  if (results.length === 0) {
112
95
  return {
113
- content: [
114
- { type: "text", text: `No results found for query: "${query}"` },
115
- ],
96
+ content: [{ type: "text", text: `No results found for query: "${query}"` }],
116
97
  };
117
98
  }
118
99
 
@@ -127,7 +108,7 @@ export function registerGitHistoryTools(
127
108
  `Date: ${r.date.split("T")[0]}\n` +
128
109
  `Subject: ${r.subject}\n` +
129
110
  `Files: ${r.files.slice(0, 5).join(", ")}${r.files.length > 5 ? ` (+${r.files.length - 5} more)` : ""}\n\n` +
130
- `${r.content.substring(0, 500)}${r.content.length > 500 ? "..." : ""}\n`,
111
+ `${r.content.substring(0, 500)}${r.content.length > 500 ? "..." : ""}\n`
131
112
  )
132
113
  .join("\n");
133
114
 
@@ -139,8 +120,8 @@ export function registerGitHistoryTools(
139
120
  },
140
121
  ],
141
122
  };
142
- },
143
- ),
123
+ }
124
+ )
144
125
  );
145
126
 
146
127
  // index_new_commits
@@ -156,26 +137,20 @@ export function registerGitHistoryTools(
156
137
  log.info({ tool: "index_new_commits", path }, "Tool called");
157
138
  const progressToken = extra._meta?.progressToken;
158
139
 
159
- const stats = await gitHistoryIndexer.indexNewCommits(
160
- path,
161
- (progress) => {
162
- log.debug(
163
- { phase: progress.phase, percentage: progress.percentage },
164
- progress.message,
165
- );
166
- if (progressToken !== undefined) {
167
- extra.sendNotification({
168
- method: "notifications/progress",
169
- params: {
170
- progressToken,
171
- progress: progress.percentage,
172
- total: 100,
173
- message: `[${progress.phase}] ${progress.message}`,
174
- },
175
- });
176
- }
177
- },
178
- );
140
+ const stats = await gitHistoryIndexer.indexNewCommits(path, (progress) => {
141
+ log.debug({ phase: progress.phase, percentage: progress.percentage }, progress.message);
142
+ if (progressToken !== undefined) {
143
+ extra.sendNotification({
144
+ method: "notifications/progress",
145
+ params: {
146
+ progressToken,
147
+ progress: progress.percentage,
148
+ total: 100,
149
+ message: `[${progress.phase}] ${progress.message}`,
150
+ },
151
+ });
152
+ }
153
+ });
179
154
 
180
155
  let message: string;
181
156
  if (stats.newCommits === 0) {
@@ -189,7 +164,7 @@ export function registerGitHistoryTools(
189
164
  return {
190
165
  content: [{ type: "text", text: message }],
191
166
  };
192
- }),
167
+ })
193
168
  );
194
169
 
195
170
  // get_git_index_status
@@ -197,8 +172,7 @@ export function registerGitHistoryTools(
197
172
  "get_git_index_status",
198
173
  {
199
174
  title: "Get Git Index Status",
200
- description:
201
- "Get the indexing status and statistics for a repository's git history index.",
175
+ description: "Get the indexing status and statistics for a repository's git history index.",
202
176
  inputSchema: schemas.GetGitIndexStatusSchema,
203
177
  },
204
178
  withToolLogging("get_git_index_status", async ({ path }) => {
@@ -240,7 +214,7 @@ export function registerGitHistoryTools(
240
214
  return {
241
215
  content: [{ type: "text", text: JSON.stringify(statusInfo, null, 2) }],
242
216
  };
243
- }),
217
+ })
244
218
  );
245
219
 
246
220
  // clear_git_index
@@ -256,10 +230,8 @@ export function registerGitHistoryTools(
256
230
  log.info({ tool: "clear_git_index", path }, "Tool called");
257
231
  await gitHistoryIndexer.clearIndex(path);
258
232
  return {
259
- content: [
260
- { type: "text", text: `Git history index cleared for "${path}".` },
261
- ],
233
+ content: [{ type: "text", text: `Git history index cleared for "${path}".` }],
262
234
  };
263
- }),
235
+ })
264
236
  );
265
237
  }
@@ -24,10 +24,7 @@ export interface ToolDependencies {
24
24
  /**
25
25
  * Register all MCP tools on the server
26
26
  */
27
- export function registerAllTools(
28
- server: McpServer,
29
- deps: ToolDependencies,
30
- ): void {
27
+ export function registerAllTools(server: McpServer, deps: ToolDependencies): void {
31
28
  registerCollectionTools(server, {
32
29
  qdrant: deps.qdrant,
33
30
  embeddings: deps.embeddings,
@@ -1,4 +1,4 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
2
  import { withToolLogging } from "./logging.js";
3
3
 
4
4
  vi.mock("../logger.js", () => ({
@@ -45,7 +45,7 @@ describe("withToolLogging", () => {
45
45
  tool: "create_collection",
46
46
  durationMs: expect.any(Number),
47
47
  }),
48
- "Tool completed",
48
+ "Tool completed"
49
49
  );
50
50
  });
51
51
 
@@ -65,7 +65,7 @@ describe("withToolLogging", () => {
65
65
  durationMs: expect.any(Number),
66
66
  error: "Error: Collection not found",
67
67
  }),
68
- "Tool failed",
68
+ "Tool failed"
69
69
  );
70
70
  expect(mockLog.info).not.toHaveBeenCalled();
71
71
  });
@@ -83,7 +83,7 @@ describe("withToolLogging", () => {
83
83
  durationMs: expect.any(Number),
84
84
  err: testError,
85
85
  }),
86
- "Tool threw an error",
86
+ "Tool threw an error"
87
87
  );
88
88
  });
89
89
 
@@ -100,7 +100,7 @@ describe("withToolLogging", () => {
100
100
  tool: "semantic_search",
101
101
  durationMs: expect.any(Number),
102
102
  }),
103
- "Tool completed with no results",
103
+ "Tool completed with no results"
104
104
  );
105
105
  expect(mockLog.info).not.toHaveBeenCalled();
106
106
  });
@@ -115,7 +115,7 @@ describe("withToolLogging", () => {
115
115
 
116
116
  expect(mockLog.warn).toHaveBeenCalledWith(
117
117
  expect.objectContaining({ tool: "hybrid_search" }),
118
- "Tool completed with no results",
118
+ "Tool completed with no results"
119
119
  );
120
120
  });
121
121
 
@@ -129,7 +129,7 @@ describe("withToolLogging", () => {
129
129
 
130
130
  expect(mockLog.warn).toHaveBeenCalledWith(
131
131
  expect.objectContaining({ tool: "search_code" }),
132
- "Tool completed with no results",
132
+ "Tool completed with no results"
133
133
  );
134
134
  });
135
135
 
@@ -144,7 +144,7 @@ describe("withToolLogging", () => {
144
144
  // Should log info, not warn, because get_index_status is not a search tool
145
145
  expect(mockLog.info).toHaveBeenCalledWith(
146
146
  expect.objectContaining({ tool: "get_index_status" }),
147
- "Tool completed",
147
+ "Tool completed"
148
148
  );
149
149
  expect(mockLog.warn).not.toHaveBeenCalled();
150
150
  });
@@ -159,7 +159,7 @@ describe("withToolLogging", () => {
159
159
 
160
160
  expect(mockLog.info).toHaveBeenCalledWith(
161
161
  expect.objectContaining({ tool: "search_code" }),
162
- "Tool completed",
162
+ "Tool completed"
163
163
  );
164
164
  expect(mockLog.warn).not.toHaveBeenCalled();
165
165
  });
@@ -200,7 +200,7 @@ describe("withToolLogging", () => {
200
200
 
201
201
  expect(mockLog.warn).toHaveBeenCalledWith(
202
202
  expect.objectContaining({ tool: "search_git_history" }),
203
- "Tool completed with no results",
203
+ "Tool completed with no results"
204
204
  );
205
205
  });
206
206
  });
@@ -29,9 +29,7 @@ function isEmptySearchResult(result: CallToolResult): boolean {
29
29
  if (first.type === "text") {
30
30
  const text = (first as { type: "text"; text: string }).text;
31
31
  return (
32
- text.startsWith("No results found") ||
33
- text === "[]" ||
34
- text.includes("Found 0 result(s)")
32
+ text.startsWith("No results found") || text === "[]" || text.includes("Found 0 result(s)")
35
33
  );
36
34
  }
37
35
  return false;
@@ -44,9 +42,10 @@ function isEmptySearchResult(result: CallToolResult): boolean {
44
42
  * - Logs "Tool completed with no results" at warn level for search tools
45
43
  * - Logs "Tool threw an error" at error level when handler throws (re-throws)
46
44
  */
47
- export function withToolLogging<
48
- T extends (...args: any[]) => Promise<CallToolResult>,
49
- >(toolName: string, handler: T): T {
45
+ export function withToolLogging<T extends (...args: any[]) => Promise<CallToolResult>>(
46
+ toolName: string,
47
+ handler: T
48
+ ): T {
50
49
  const wrapped = async (...args: Parameters<T>): Promise<CallToolResult> => {
51
50
  const startTime = Date.now();
52
51
  try {
@@ -58,15 +57,9 @@ export function withToolLogging<
58
57
  result.content?.[0]?.type === "text"
59
58
  ? (result.content[0] as { type: "text"; text: string }).text
60
59
  : "Unknown error";
61
- log.error(
62
- { tool: toolName, durationMs, error: errorText },
63
- "Tool failed",
64
- );
60
+ log.error({ tool: toolName, durationMs, error: errorText }, "Tool failed");
65
61
  } else if (SEARCH_TOOLS.has(toolName) && isEmptySearchResult(result)) {
66
- log.warn(
67
- { tool: toolName, durationMs },
68
- "Tool completed with no results",
69
- );
62
+ log.warn({ tool: toolName, durationMs }, "Tool completed with no results");
70
63
  } else {
71
64
  log.info({ tool: toolName, durationMs }, "Tool completed");
72
65
  }
@@ -74,10 +67,7 @@ export function withToolLogging<
74
67
  return result;
75
68
  } catch (error) {
76
69
  const durationMs = Date.now() - startTime;
77
- log.error(
78
- { tool: toolName, durationMs, err: error },
79
- "Tool threw an error",
80
- );
70
+ log.error({ tool: toolName, durationMs, err: error }, "Tool threw an error");
81
71
  throw error;
82
72
  }
83
73
  };