@mhalder/qdrant-mcp-server 3.2.1 → 3.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 (124) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +12 -21
  3. package/build/code/chunker/tree-sitter-chunker.d.ts.map +1 -1
  4. package/build/code/chunker/tree-sitter-chunker.js +15 -3
  5. package/build/code/chunker/tree-sitter-chunker.js.map +1 -1
  6. package/build/code/indexer.d.ts +1 -0
  7. package/build/code/indexer.d.ts.map +1 -1
  8. package/build/code/indexer.js +24 -4
  9. package/build/code/indexer.js.map +1 -1
  10. package/build/embeddings/cohere.d.ts +1 -0
  11. package/build/embeddings/cohere.d.ts.map +1 -1
  12. package/build/embeddings/cohere.js +8 -1
  13. package/build/embeddings/cohere.js.map +1 -1
  14. package/build/embeddings/cohere.test.js +11 -0
  15. package/build/embeddings/cohere.test.js.map +1 -1
  16. package/build/embeddings/factory.d.ts.map +1 -1
  17. package/build/embeddings/factory.js +2 -0
  18. package/build/embeddings/factory.js.map +1 -1
  19. package/build/embeddings/factory.test.js +12 -1
  20. package/build/embeddings/factory.test.js.map +1 -1
  21. package/build/embeddings/ollama.d.ts +1 -0
  22. package/build/embeddings/ollama.d.ts.map +1 -1
  23. package/build/embeddings/ollama.js +8 -1
  24. package/build/embeddings/ollama.js.map +1 -1
  25. package/build/embeddings/ollama.test.js +11 -0
  26. package/build/embeddings/ollama.test.js.map +1 -1
  27. package/build/embeddings/openai.d.ts +1 -0
  28. package/build/embeddings/openai.d.ts.map +1 -1
  29. package/build/embeddings/openai.js +8 -1
  30. package/build/embeddings/openai.js.map +1 -1
  31. package/build/embeddings/openai.test.js +11 -0
  32. package/build/embeddings/openai.test.js.map +1 -1
  33. package/build/embeddings/voyage.d.ts +1 -0
  34. package/build/embeddings/voyage.d.ts.map +1 -1
  35. package/build/embeddings/voyage.js +8 -1
  36. package/build/embeddings/voyage.js.map +1 -1
  37. package/build/embeddings/voyage.test.js +11 -0
  38. package/build/embeddings/voyage.test.js.map +1 -1
  39. package/build/git/indexer.d.ts +1 -0
  40. package/build/git/indexer.d.ts.map +1 -1
  41. package/build/git/indexer.js +16 -3
  42. package/build/git/indexer.js.map +1 -1
  43. package/build/git/indexer.test.js +15 -9
  44. package/build/git/indexer.test.js.map +1 -1
  45. package/build/index.js +35 -26
  46. package/build/index.js.map +1 -1
  47. package/build/index.test.js +105 -91
  48. package/build/index.test.js.map +1 -1
  49. package/build/logger.d.ts +4 -0
  50. package/build/logger.d.ts.map +1 -0
  51. package/build/logger.js +24 -0
  52. package/build/logger.js.map +1 -0
  53. package/build/qdrant/client.d.ts +1 -0
  54. package/build/qdrant/client.d.ts.map +1 -1
  55. package/build/qdrant/client.js +10 -0
  56. package/build/qdrant/client.js.map +1 -1
  57. package/build/qdrant/client.test.js +11 -0
  58. package/build/qdrant/client.test.js.map +1 -1
  59. package/build/tools/code.d.ts.map +1 -1
  60. package/build/tools/code.js +44 -13
  61. package/build/tools/code.js.map +1 -1
  62. package/build/tools/collection.d.ts.map +1 -1
  63. package/build/tools/collection.js +15 -8
  64. package/build/tools/collection.js.map +1 -1
  65. package/build/tools/document.d.ts.map +1 -1
  66. package/build/tools/document.js +9 -4
  67. package/build/tools/document.js.map +1 -1
  68. package/build/tools/federated.d.ts.map +1 -1
  69. package/build/tools/federated.js +9 -4
  70. package/build/tools/federated.js.map +1 -1
  71. package/build/tools/federated.test.js +11 -0
  72. package/build/tools/federated.test.js.map +1 -1
  73. package/build/tools/git-history.d.ts.map +1 -1
  74. package/build/tools/git-history.js +44 -12
  75. package/build/tools/git-history.js.map +1 -1
  76. package/build/tools/logging.d.ts +16 -0
  77. package/build/tools/logging.d.ts.map +1 -0
  78. package/build/tools/logging.js +68 -0
  79. package/build/tools/logging.js.map +1 -0
  80. package/build/tools/logging.test.d.ts +2 -0
  81. package/build/tools/logging.test.d.ts.map +1 -0
  82. package/build/tools/logging.test.js +139 -0
  83. package/build/tools/logging.test.js.map +1 -0
  84. package/build/tools/schemas.d.ts +32 -19
  85. package/build/tools/schemas.d.ts.map +1 -1
  86. package/build/tools/schemas.js +9 -3
  87. package/build/tools/schemas.js.map +1 -1
  88. package/build/tools/search.d.ts.map +1 -1
  89. package/build/tools/search.js +13 -4
  90. package/build/tools/search.js.map +1 -1
  91. package/mise.toml +2 -0
  92. package/package.json +14 -13
  93. package/src/code/chunker/tree-sitter-chunker.ts +41 -9
  94. package/src/code/indexer.ts +41 -6
  95. package/src/embeddings/cohere.test.ts +12 -0
  96. package/src/embeddings/cohere.ts +10 -2
  97. package/src/embeddings/factory.test.ts +13 -1
  98. package/src/embeddings/factory.ts +3 -0
  99. package/src/embeddings/ollama.test.ts +12 -0
  100. package/src/embeddings/ollama.ts +10 -2
  101. package/src/embeddings/openai.test.ts +12 -0
  102. package/src/embeddings/openai.ts +10 -2
  103. package/src/embeddings/voyage.test.ts +12 -0
  104. package/src/embeddings/voyage.ts +10 -2
  105. package/src/git/indexer.test.ts +22 -16
  106. package/src/git/indexer.ts +30 -4
  107. package/src/index.test.ts +128 -106
  108. package/src/index.ts +59 -38
  109. package/src/logger.ts +33 -0
  110. package/src/qdrant/client.test.ts +12 -0
  111. package/src/qdrant/client.ts +22 -0
  112. package/src/tools/code.ts +107 -62
  113. package/src/tools/collection.ts +39 -22
  114. package/src/tools/document.ts +52 -22
  115. package/src/tools/federated.test.ts +12 -0
  116. package/src/tools/federated.ts +143 -125
  117. package/src/tools/git-history.ts +117 -60
  118. package/src/tools/logging.test.ts +206 -0
  119. package/src/tools/logging.ts +85 -0
  120. package/src/tools/schemas.ts +9 -3
  121. package/src/tools/search.ts +93 -71
  122. package/tests/code/chunker/tree-sitter-chunker.test.ts +13 -1
  123. package/tests/code/indexer.test.ts +12 -0
  124. package/tests/code/integration.test.ts +14 -1
@@ -4,6 +4,18 @@ import { OllamaEmbeddings } from "./ollama.js";
4
4
  // Mock fetch globally
5
5
  global.fetch = vi.fn();
6
6
 
7
+ vi.mock("../logger.js", () => ({
8
+ default: {
9
+ info: vi.fn(),
10
+ warn: vi.fn(),
11
+ error: vi.fn(),
12
+ debug: vi.fn(),
13
+ fatal: vi.fn(),
14
+ trace: vi.fn(),
15
+ child: vi.fn().mockReturnThis(),
16
+ },
17
+ }));
18
+
7
19
  describe("OllamaEmbeddings", () => {
8
20
  let embeddings: OllamaEmbeddings;
9
21
  let mockFetch: any;
@@ -1,4 +1,5 @@
1
1
  import Bottleneck from "bottleneck";
2
+ import logger from "../logger.js";
2
3
  import { EmbeddingProvider, EmbeddingResult, RateLimitConfig } from "./base.js";
3
4
 
4
5
  interface OllamaError {
@@ -11,6 +12,7 @@ interface OllamaEmbedResponse {
11
12
  }
12
13
 
13
14
  export class OllamaEmbeddings implements EmbeddingProvider {
15
+ private log = logger.child({ component: "embeddings", provider: "ollama" });
14
16
  private model: string;
15
17
  private dimensions: number;
16
18
  private limiter: Bottleneck;
@@ -76,8 +78,13 @@ export class OllamaEmbeddings implements EmbeddingProvider {
76
78
  if (isRateLimitError && attempt < this.retryAttempts) {
77
79
  const delayMs = this.retryDelayMs * Math.pow(2, attempt);
78
80
  const waitTimeSeconds = (delayMs / 1000).toFixed(1);
79
- console.error(
80
- `Rate limit reached. Retrying in ${waitTimeSeconds}s (attempt ${attempt + 1}/${this.retryAttempts})...`,
81
+ this.log.warn(
82
+ {
83
+ waitTimeSeconds,
84
+ attempt: attempt + 1,
85
+ maxAttempts: this.retryAttempts,
86
+ },
87
+ "Rate limit reached, retrying",
81
88
  );
82
89
 
83
90
  await new Promise((resolve) => setTimeout(resolve, delayMs));
@@ -169,6 +176,7 @@ export class OllamaEmbeddings implements EmbeddingProvider {
169
176
  }
170
177
 
171
178
  async embedBatch(texts: string[]): Promise<EmbeddingResult[]> {
179
+ this.log.debug({ batchSize: texts.length }, "embedBatch");
172
180
  // Ollama doesn't support batch embeddings natively, so we process in parallel
173
181
  // Process in chunks to avoid overwhelming Ollama and prevent memory issues
174
182
  const CHUNK_SIZE = 50;
@@ -14,6 +14,18 @@ vi.mock("openai", () => ({
14
14
  }),
15
15
  }));
16
16
 
17
+ vi.mock("../logger.js", () => ({
18
+ default: {
19
+ info: vi.fn(),
20
+ warn: vi.fn(),
21
+ error: vi.fn(),
22
+ debug: vi.fn(),
23
+ fatal: vi.fn(),
24
+ trace: vi.fn(),
25
+ child: vi.fn().mockReturnThis(),
26
+ },
27
+ }));
28
+
17
29
  describe("OpenAIEmbeddings", () => {
18
30
  let embeddings: OpenAIEmbeddings;
19
31
 
@@ -1,5 +1,6 @@
1
1
  import OpenAI from "openai";
2
2
  import Bottleneck from "bottleneck";
3
+ import logger from "../logger.js";
3
4
  import {
4
5
  EmbeddingProvider,
5
6
  EmbeddingResult,
@@ -18,6 +19,7 @@ interface OpenAIError {
18
19
  }
19
20
 
20
21
  export class OpenAIEmbeddings implements EmbeddingProvider {
22
+ private log = logger.child({ component: "embeddings", provider: "openai" });
21
23
  private client: OpenAI;
22
24
  private model: string;
23
25
  private dimensions: number;
@@ -95,8 +97,13 @@ export class OpenAIEmbeddings implements EmbeddingProvider {
95
97
  }
96
98
 
97
99
  const waitTimeSeconds = (delayMs / 1000).toFixed(1);
98
- console.error(
99
- `Rate limit reached. Retrying in ${waitTimeSeconds}s (attempt ${attempt + 1}/${this.retryAttempts})...`,
100
+ this.log.warn(
101
+ {
102
+ waitTimeSeconds,
103
+ attempt: attempt + 1,
104
+ maxAttempts: this.retryAttempts,
105
+ },
106
+ "Rate limit reached, retrying",
100
107
  );
101
108
 
102
109
  await new Promise((resolve) => setTimeout(resolve, delayMs));
@@ -132,6 +139,7 @@ export class OpenAIEmbeddings implements EmbeddingProvider {
132
139
  }
133
140
 
134
141
  async embedBatch(texts: string[]): Promise<EmbeddingResult[]> {
142
+ this.log.debug({ batchSize: texts.length }, "embedBatch");
135
143
  return this.limiter.schedule(() =>
136
144
  this.retryWithBackoff(async () => {
137
145
  const response = await this.client.embeddings.create({
@@ -4,6 +4,18 @@ import { VoyageEmbeddings } from "./voyage.js";
4
4
  // Mock fetch globally
5
5
  global.fetch = vi.fn();
6
6
 
7
+ vi.mock("../logger.js", () => ({
8
+ default: {
9
+ info: vi.fn(),
10
+ warn: vi.fn(),
11
+ error: vi.fn(),
12
+ debug: vi.fn(),
13
+ fatal: vi.fn(),
14
+ trace: vi.fn(),
15
+ child: vi.fn().mockReturnThis(),
16
+ },
17
+ }));
18
+
7
19
  describe("VoyageEmbeddings", () => {
8
20
  let embeddings: VoyageEmbeddings;
9
21
  let mockFetch: any;
@@ -1,4 +1,5 @@
1
1
  import Bottleneck from "bottleneck";
2
+ import logger from "../logger.js";
2
3
  import { EmbeddingProvider, EmbeddingResult, RateLimitConfig } from "./base.js";
3
4
 
4
5
  interface VoyageError {
@@ -15,6 +16,7 @@ interface VoyageEmbedResponse {
15
16
  }
16
17
 
17
18
  export class VoyageEmbeddings implements EmbeddingProvider {
19
+ private log = logger.child({ component: "embeddings", provider: "voyage" });
18
20
  private apiKey: string;
19
21
  private model: string;
20
22
  private dimensions: number;
@@ -76,8 +78,13 @@ export class VoyageEmbeddings implements EmbeddingProvider {
76
78
  if (isRateLimitError && attempt < this.retryAttempts) {
77
79
  const delayMs = this.retryDelayMs * Math.pow(2, attempt);
78
80
  const waitTimeSeconds = (delayMs / 1000).toFixed(1);
79
- console.error(
80
- `Rate limit reached. Retrying in ${waitTimeSeconds}s (attempt ${attempt + 1}/${this.retryAttempts})...`,
81
+ this.log.warn(
82
+ {
83
+ waitTimeSeconds,
84
+ attempt: attempt + 1,
85
+ maxAttempts: this.retryAttempts,
86
+ },
87
+ "Rate limit reached, retrying",
81
88
  );
82
89
 
83
90
  await new Promise((resolve) => setTimeout(resolve, delayMs));
@@ -142,6 +149,7 @@ export class VoyageEmbeddings implements EmbeddingProvider {
142
149
  }
143
150
 
144
151
  async embedBatch(texts: string[]): Promise<EmbeddingResult[]> {
152
+ this.log.debug({ batchSize: texts.length }, "embedBatch");
145
153
  return this.limiter.schedule(() =>
146
154
  this.retryWithBackoff(async () => {
147
155
  const response = await this.callApi(texts);
@@ -76,6 +76,18 @@ vi.mock("node:fs", () => ({
76
76
  },
77
77
  }));
78
78
 
79
+ vi.mock("../logger.js", () => ({
80
+ default: {
81
+ info: vi.fn(),
82
+ warn: vi.fn(),
83
+ error: vi.fn(),
84
+ debug: vi.fn(),
85
+ fatal: vi.fn(),
86
+ trace: vi.fn(),
87
+ child: vi.fn().mockReturnThis(),
88
+ },
89
+ }));
90
+
79
91
  describe("GitHistoryIndexer", () => {
80
92
  let indexer: GitHistoryIndexer;
81
93
  let mockQdrant: any;
@@ -622,10 +634,6 @@ describe("GitHistoryIndexer", () => {
622
634
  });
623
635
 
624
636
  it("should handle snapshot save failure gracefully", async () => {
625
- const consoleSpy = vi
626
- .spyOn(console, "error")
627
- .mockImplementation(() => {});
628
-
629
637
  const mockCommits = [
630
638
  {
631
639
  hash: "abc123",
@@ -653,14 +661,12 @@ describe("GitHistoryIndexer", () => {
653
661
 
654
662
  expect(stats.status).toBe("completed");
655
663
  expect(stats.errors?.some((e) => e.includes("Snapshot"))).toBe(true);
656
-
657
- consoleSpy.mockRestore();
658
664
  });
659
665
 
660
666
  it("should handle storeIndexingMarker errors silently", async () => {
661
- const consoleSpy = vi
662
- .spyOn(console, "error")
663
- .mockImplementation(() => {});
667
+ const loggerMod = await import("../logger.js");
668
+ const logError = loggerMod.default.error as ReturnType<typeof vi.fn>;
669
+ logError.mockClear();
664
670
 
665
671
  mockExtractorInstance.validateRepository.mockResolvedValue(true);
666
672
  mockExtractorInstance.getLatestCommitHash.mockResolvedValue("abc123");
@@ -670,9 +676,7 @@ describe("GitHistoryIndexer", () => {
670
676
  const stats = await indexer.indexHistory("/test/repo");
671
677
 
672
678
  expect(stats.status).toBe("completed");
673
- expect(consoleSpy).toHaveBeenCalled();
674
-
675
- consoleSpy.mockRestore();
679
+ expect(logError).toHaveBeenCalled();
676
680
  });
677
681
 
678
682
  it("should use hybrid search for indexing when enabled", async () => {
@@ -1072,7 +1076,9 @@ describe("GitHistoryIndexer", () => {
1072
1076
  mockChunkerInstance.generateChunkId.mockReturnValue("chunk-1");
1073
1077
 
1074
1078
  // All retries fail
1075
- mockEmbeddings.embedBatch.mockRejectedValue(new Error("Persistent error"));
1079
+ mockEmbeddings.embedBatch.mockRejectedValue(
1080
+ new Error("Persistent error"),
1081
+ );
1076
1082
 
1077
1083
  mockQdrant.collectionExists.mockResolvedValue(false);
1078
1084
  mockQdrant.getCollectionInfo.mockResolvedValue({ hybridEnabled: false });
@@ -1081,9 +1087,9 @@ describe("GitHistoryIndexer", () => {
1081
1087
 
1082
1088
  expect(stats.status).toBe("partial");
1083
1089
  expect(stats.errors).toBeDefined();
1084
- expect(
1085
- stats.errors?.some((e) => e.includes("after 3 attempts")),
1086
- ).toBe(true);
1090
+ expect(stats.errors?.some((e) => e.includes("after 3 attempts"))).toBe(
1091
+ true,
1092
+ );
1087
1093
  });
1088
1094
  });
1089
1095
  });
@@ -5,6 +5,7 @@
5
5
  import { createHash } from "node:crypto";
6
6
  import { promises as fs } from "node:fs";
7
7
  import { resolve } from "node:path";
8
+ import logger from "../logger.js";
8
9
  import type { EmbeddingProvider } from "../embeddings/base.js";
9
10
  import { BM25SparseVectorGenerator } from "../embeddings/sparse.js";
10
11
  import type { QdrantManager } from "../qdrant/client.js";
@@ -25,6 +26,8 @@ import type {
25
26
  } from "./types.js";
26
27
 
27
28
  export class GitHistoryIndexer {
29
+ private log = logger.child({ component: "git-indexer" });
30
+
28
31
  constructor(
29
32
  private qdrant: QdrantManager,
30
33
  private embeddings: EmbeddingProvider,
@@ -66,6 +69,11 @@ export class GitHistoryIndexer {
66
69
  const absolutePath = await this.validatePath(path);
67
70
  const collectionName = await this.getCollectionName(absolutePath);
68
71
 
72
+ this.log.info(
73
+ { path: absolutePath, collectionName },
74
+ "Git indexing started",
75
+ );
76
+
69
77
  try {
70
78
  // 1. Validate repository
71
79
  const extractor = new GitExtractor(absolutePath, this.config);
@@ -119,6 +127,7 @@ export class GitHistoryIndexer {
119
127
  });
120
128
 
121
129
  stats.commitsScanned = commits.length;
130
+ this.log.info({ commitsExtracted: commits.length }, "Commits extracted");
122
131
 
123
132
  if (commits.length === 0) {
124
133
  await this.storeIndexingMarker(collectionName, true);
@@ -175,6 +184,10 @@ export class GitHistoryIndexer {
175
184
 
176
185
  // 5. Generate embeddings and store in batches
177
186
  const batchSize = this.config.batchSize;
187
+ this.log.debug(
188
+ { totalChunks: allChunks.length, batchSize },
189
+ "Starting embedding generation",
190
+ );
178
191
  for (let i = 0; i < allChunks.length; i += batchSize) {
179
192
  const batch = allChunks.slice(i, i + batchSize);
180
193
 
@@ -232,7 +245,9 @@ export class GitHistoryIndexer {
232
245
  const sparseGenerator = new BM25SparseVectorGenerator();
233
246
  const hybridPoints = points.map((point, idx) => ({
234
247
  ...point,
235
- sparseVector: sparseGenerator.generate(batch[idx].chunk.content),
248
+ sparseVector: sparseGenerator.generate(
249
+ batch[idx].chunk.content,
250
+ ),
236
251
  }));
237
252
  await this.qdrant.addPointsWithSparse(
238
253
  collectionName,
@@ -245,7 +260,8 @@ export class GitHistoryIndexer {
245
260
  success = true;
246
261
  break;
247
262
  } catch (error) {
248
- lastError = error instanceof Error ? error : new Error(String(error));
263
+ lastError =
264
+ error instanceof Error ? error : new Error(String(error));
249
265
  if (attempt < this.config.batchRetryAttempts) {
250
266
  // Exponential backoff: 1s, 2s, 4s...
251
267
  const delay = Math.pow(2, attempt - 1) * 1000;
@@ -270,7 +286,7 @@ export class GitHistoryIndexer {
270
286
  } catch (error) {
271
287
  const errorMessage =
272
288
  error instanceof Error ? error.message : String(error);
273
- console.error("Failed to save snapshot:", errorMessage);
289
+ this.log.error({ err: error }, "Failed to save snapshot");
274
290
  stats.errors?.push(`Snapshot save failed: ${errorMessage}`);
275
291
  }
276
292
 
@@ -278,6 +294,14 @@ export class GitHistoryIndexer {
278
294
  await this.storeIndexingMarker(collectionName, true);
279
295
 
280
296
  stats.durationMs = Date.now() - startTime;
297
+ this.log.info(
298
+ {
299
+ commitsIndexed: stats.commitsIndexed,
300
+ chunksCreated: stats.chunksCreated,
301
+ durationMs: stats.durationMs,
302
+ },
303
+ "Git indexing complete",
304
+ );
281
305
  return stats;
282
306
  } catch (error) {
283
307
  const errorMessage =
@@ -509,6 +533,7 @@ export class GitHistoryIndexer {
509
533
  });
510
534
 
511
535
  stats.newCommits = newCommits.length;
536
+ this.log.info({ newCommits: newCommits.length }, "New commits found");
512
537
 
513
538
  if (newCommits.length === 0) {
514
539
  stats.durationMs = Date.now() - startTime;
@@ -612,6 +637,7 @@ export class GitHistoryIndexer {
612
637
  * Clear all indexed data for a repository
613
638
  */
614
639
  async clearIndex(path: string): Promise<void> {
640
+ this.log.info({ path }, "Clearing git index");
615
641
  const absolutePath = await this.validatePath(path);
616
642
  const collectionName = await this.getCollectionName(absolutePath);
617
643
  const exists = await this.qdrant.collectionExists(collectionName);
@@ -670,7 +696,7 @@ export class GitHistoryIndexer {
670
696
  ]);
671
697
  }
672
698
  } catch (error) {
673
- console.error("Failed to store indexing marker:", error);
699
+ this.log.error({ err: error }, "Failed to store indexing marker");
674
700
  }
675
701
  }
676
702