@mhalder/qdrant-mcp-server 3.3.2 → 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.
- package/.github/workflows/ci.yml +0 -2
- package/.github/workflows/claude-code-review.yml +1 -1
- package/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/biome.json +3 -2
- package/build/code/chunker/tree-sitter-chunker.d.ts.map +1 -1
- package/build/code/chunker/tree-sitter-chunker.js +2 -12
- package/build/code/chunker/tree-sitter-chunker.js.map +1 -1
- package/build/code/indexer.d.ts.map +1 -1
- package/build/code/indexer.js +12 -18
- package/build/code/indexer.js.map +1 -1
- package/build/code/scanner.js +1 -1
- package/build/code/scanner.js.map +1 -1
- package/build/embeddings/cohere.d.ts +1 -1
- package/build/embeddings/cohere.d.ts.map +1 -1
- package/build/embeddings/cohere.js +2 -2
- package/build/embeddings/cohere.js.map +1 -1
- package/build/embeddings/cohere.test.js +1 -5
- package/build/embeddings/cohere.test.js.map +1 -1
- package/build/embeddings/factory.d.ts +1 -1
- package/build/embeddings/factory.d.ts.map +1 -1
- package/build/embeddings/factory.js +7 -9
- package/build/embeddings/factory.js.map +1 -1
- package/build/embeddings/factory.test.js +3 -3
- package/build/embeddings/factory.test.js.map +1 -1
- package/build/embeddings/ollama.d.ts +1 -1
- package/build/embeddings/ollama.d.ts.map +1 -1
- package/build/embeddings/ollama.js +6 -8
- package/build/embeddings/ollama.js.map +1 -1
- package/build/embeddings/ollama.test.js +2 -6
- package/build/embeddings/ollama.test.js.map +1 -1
- package/build/embeddings/openai.d.ts +1 -1
- package/build/embeddings/openai.d.ts.map +1 -1
- package/build/embeddings/openai.js +4 -7
- package/build/embeddings/openai.js.map +1 -1
- package/build/embeddings/openai.test.js +3 -12
- package/build/embeddings/openai.test.js.map +1 -1
- package/build/embeddings/sparse.test.js +12 -2
- package/build/embeddings/sparse.test.js.map +1 -1
- package/build/embeddings/voyage.d.ts +1 -1
- package/build/embeddings/voyage.d.ts.map +1 -1
- package/build/embeddings/voyage.js +2 -3
- package/build/embeddings/voyage.js.map +1 -1
- package/build/embeddings/voyage.test.js +2 -6
- package/build/embeddings/voyage.test.js.map +1 -1
- package/build/git/chunker.d.ts.map +1 -1
- package/build/git/chunker.js +2 -2
- package/build/git/chunker.js.map +1 -1
- package/build/git/chunker.test.js +1 -1
- package/build/git/chunker.test.js.map +1 -1
- package/build/git/extractor.d.ts.map +1 -1
- package/build/git/extractor.integration.test.js +9 -5
- package/build/git/extractor.integration.test.js.map +1 -1
- package/build/git/extractor.js +1 -1
- package/build/git/extractor.js.map +1 -1
- package/build/git/extractor.test.js +2 -2
- package/build/git/extractor.test.js.map +1 -1
- package/build/git/index.d.ts +4 -4
- package/build/git/index.d.ts.map +1 -1
- package/build/git/index.js +3 -3
- package/build/git/index.js.map +1 -1
- package/build/git/indexer.d.ts.map +1 -1
- package/build/git/indexer.js +9 -21
- package/build/git/indexer.js.map +1 -1
- package/build/git/indexer.test.js +4 -8
- package/build/git/indexer.test.js.map +1 -1
- package/build/git/sync/synchronizer.d.ts.map +1 -1
- package/build/git/sync/synchronizer.js.map +1 -1
- package/build/git/sync/synchronizer.test.js +4 -2
- package/build/git/sync/synchronizer.test.js.map +1 -1
- package/build/index.js +5 -9
- package/build/index.js.map +1 -1
- package/build/index.test.js +3 -3
- package/build/index.test.js.map +1 -1
- package/build/logger.d.ts.map +1 -1
- package/build/logger.js +1 -9
- package/build/logger.js.map +1 -1
- package/build/prompts/register.d.ts.map +1 -1
- package/build/prompts/register.js.map +1 -1
- package/build/qdrant/client.d.ts.map +1 -1
- package/build/qdrant/client.js.map +1 -1
- package/build/qdrant/client.test.js +10 -34
- package/build/qdrant/client.test.js.map +1 -1
- package/build/resources/index.d.ts +1 -1
- package/build/resources/index.d.ts.map +1 -1
- package/build/resources/index.js +1 -1
- package/build/resources/index.js.map +1 -1
- package/build/tools/code.d.ts.map +1 -1
- package/build/tools/code.js +3 -9
- package/build/tools/code.js.map +1 -1
- package/build/tools/collection.d.ts.map +1 -1
- package/build/tools/collection.js +1 -3
- package/build/tools/collection.js.map +1 -1
- package/build/tools/document.d.ts.map +1 -1
- package/build/tools/document.js +1 -1
- package/build/tools/document.js.map +1 -1
- package/build/tools/federated.d.ts.map +1 -1
- package/build/tools/federated.js +15 -6
- package/build/tools/federated.js.map +1 -1
- package/build/tools/federated.test.js +18 -22
- package/build/tools/federated.test.js.map +1 -1
- package/build/tools/git-history.d.ts.map +1 -1
- package/build/tools/git-history.js +3 -7
- package/build/tools/git-history.js.map +1 -1
- package/build/tools/index.d.ts.map +1 -1
- package/build/tools/index.js.map +1 -1
- package/build/tools/logging.d.ts.map +1 -1
- package/build/tools/logging.js +1 -3
- package/build/tools/logging.js.map +1 -1
- package/build/tools/logging.test.js +1 -1
- package/build/tools/logging.test.js.map +1 -1
- package/build/tools/schemas.d.ts.map +1 -1
- package/build/tools/schemas.js +17 -64
- package/build/tools/schemas.js.map +1 -1
- package/build/tools/search.d.ts.map +1 -1
- package/build/tools/search.js +1 -1
- package/build/tools/search.js.map +1 -1
- package/commitlint.config.js +12 -23
- package/package.json +1 -1
- package/scripts/verify-providers.js +12 -32
- package/src/code/chunker/tree-sitter-chunker.ts +9 -35
- package/src/code/indexer.ts +45 -107
- package/src/code/scanner.ts +1 -1
- package/src/embeddings/cohere.test.ts +17 -45
- package/src/embeddings/cohere.ts +10 -17
- package/src/embeddings/factory.test.ts +18 -18
- package/src/embeddings/factory.ts +18 -25
- package/src/embeddings/ollama.test.ts +38 -67
- package/src/embeddings/ollama.ts +15 -27
- package/src/embeddings/openai.test.ts +17 -53
- package/src/embeddings/openai.ts +11 -22
- package/src/embeddings/sparse.test.ts +12 -2
- package/src/embeddings/voyage.test.ts +39 -80
- package/src/embeddings/voyage.ts +9 -13
- package/src/git/chunker.test.ts +1 -1
- package/src/git/chunker.ts +6 -22
- package/src/git/extractor.integration.test.ts +12 -16
- package/src/git/extractor.test.ts +21 -35
- package/src/git/extractor.ts +14 -36
- package/src/git/index.ts +9 -10
- package/src/git/indexer.test.ts +29 -57
- package/src/git/indexer.ts +38 -86
- package/src/git/sync/synchronizer.test.ts +6 -9
- package/src/git/sync/synchronizer.ts +2 -5
- package/src/index.test.ts +7 -9
- package/src/index.ts +34 -80
- package/src/logger.ts +3 -14
- package/src/prompts/register.ts +3 -10
- package/src/qdrant/client.test.ts +63 -169
- package/src/qdrant/client.ts +19 -45
- package/src/resources/index.ts +4 -10
- package/src/tools/code.ts +43 -66
- package/src/tools/collection.ts +19 -38
- package/src/tools/document.ts +10 -19
- package/src/tools/federated.test.ts +34 -57
- package/src/tools/federated.ts +88 -108
- package/src/tools/git-history.ts +32 -60
- package/src/tools/index.ts +1 -4
- package/src/tools/logging.test.ts +10 -10
- package/src/tools/logging.ts +8 -18
- package/src/tools/schemas.ts +23 -78
- package/src/tools/search.ts +77 -94
- package/tests/code/chunker/tree-sitter-chunker.test.ts +6 -19
- package/tests/code/indexer.test.ts +100 -192
- package/tests/code/integration.test.ts +61 -117
- package/tests/code/scanner.test.ts +12 -39
- package/tests/code/sync/snapshot.test.ts +4 -14
- package/tests/code/sync/synchronizer.test.ts +10 -40
|
@@ -39,9 +39,7 @@ describe("QdrantManager", () => {
|
|
|
39
39
|
// Reset mocks and restore default implementations
|
|
40
40
|
mockClient.createCollection.mockReset().mockResolvedValue({});
|
|
41
41
|
mockClient.getCollection.mockReset().mockResolvedValue({});
|
|
42
|
-
mockClient.getCollections
|
|
43
|
-
.mockReset()
|
|
44
|
-
.mockResolvedValue({ collections: [] });
|
|
42
|
+
mockClient.getCollections.mockReset().mockResolvedValue({ collections: [] });
|
|
45
43
|
mockClient.deleteCollection.mockReset().mockResolvedValue({});
|
|
46
44
|
mockClient.upsert.mockReset().mockResolvedValue({});
|
|
47
45
|
mockClient.search.mockReset().mockResolvedValue([]);
|
|
@@ -76,50 +74,41 @@ describe("QdrantManager", () => {
|
|
|
76
74
|
it("should create a collection with default distance metric", async () => {
|
|
77
75
|
await manager.createCollection("test-collection", 1536);
|
|
78
76
|
|
|
79
|
-
expect(mockClient.createCollection).toHaveBeenCalledWith(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
size: 1536,
|
|
84
|
-
distance: "Cosine",
|
|
85
|
-
},
|
|
77
|
+
expect(mockClient.createCollection).toHaveBeenCalledWith("test-collection", {
|
|
78
|
+
vectors: {
|
|
79
|
+
size: 1536,
|
|
80
|
+
distance: "Cosine",
|
|
86
81
|
},
|
|
87
|
-
);
|
|
82
|
+
});
|
|
88
83
|
});
|
|
89
84
|
|
|
90
85
|
it("should create a collection with custom distance metric", async () => {
|
|
91
86
|
await manager.createCollection("test-collection", 1536, "Euclid");
|
|
92
87
|
|
|
93
|
-
expect(mockClient.createCollection).toHaveBeenCalledWith(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
size: 1536,
|
|
98
|
-
distance: "Euclid",
|
|
99
|
-
},
|
|
88
|
+
expect(mockClient.createCollection).toHaveBeenCalledWith("test-collection", {
|
|
89
|
+
vectors: {
|
|
90
|
+
size: 1536,
|
|
91
|
+
distance: "Euclid",
|
|
100
92
|
},
|
|
101
|
-
);
|
|
93
|
+
});
|
|
102
94
|
});
|
|
103
95
|
|
|
104
96
|
it("should create a hybrid collection with sparse vectors enabled", async () => {
|
|
105
97
|
await manager.createCollection("test-collection", 1536, "Cosine", true);
|
|
106
98
|
|
|
107
|
-
expect(mockClient.createCollection).toHaveBeenCalledWith(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
size: 1536,
|
|
113
|
-
distance: "Cosine",
|
|
114
|
-
},
|
|
99
|
+
expect(mockClient.createCollection).toHaveBeenCalledWith("test-collection", {
|
|
100
|
+
vectors: {
|
|
101
|
+
dense: {
|
|
102
|
+
size: 1536,
|
|
103
|
+
distance: "Cosine",
|
|
115
104
|
},
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
105
|
+
},
|
|
106
|
+
sparse_vectors: {
|
|
107
|
+
text: {
|
|
108
|
+
modifier: "idf",
|
|
120
109
|
},
|
|
121
110
|
},
|
|
122
|
-
);
|
|
111
|
+
});
|
|
123
112
|
});
|
|
124
113
|
});
|
|
125
114
|
|
|
@@ -145,20 +134,12 @@ describe("QdrantManager", () => {
|
|
|
145
134
|
describe("listCollections", () => {
|
|
146
135
|
it("should return list of collection names", async () => {
|
|
147
136
|
mockClient.getCollections.mockResolvedValue({
|
|
148
|
-
collections: [
|
|
149
|
-
{ name: "collection1" },
|
|
150
|
-
{ name: "collection2" },
|
|
151
|
-
{ name: "collection3" },
|
|
152
|
-
],
|
|
137
|
+
collections: [{ name: "collection1" }, { name: "collection2" }, { name: "collection3" }],
|
|
153
138
|
});
|
|
154
139
|
|
|
155
140
|
const collections = await manager.listCollections();
|
|
156
141
|
|
|
157
|
-
expect(collections).toEqual([
|
|
158
|
-
"collection1",
|
|
159
|
-
"collection2",
|
|
160
|
-
"collection3",
|
|
161
|
-
]);
|
|
142
|
+
expect(collections).toEqual(["collection1", "collection2", "collection3"]);
|
|
162
143
|
});
|
|
163
144
|
|
|
164
145
|
it("should return empty array when no collections exist", async () => {
|
|
@@ -253,9 +234,7 @@ describe("QdrantManager", () => {
|
|
|
253
234
|
it("should delete a collection", async () => {
|
|
254
235
|
await manager.deleteCollection("test-collection");
|
|
255
236
|
|
|
256
|
-
expect(mockClient.deleteCollection).toHaveBeenCalledWith(
|
|
257
|
-
"test-collection",
|
|
258
|
-
);
|
|
237
|
+
expect(mockClient.deleteCollection).toHaveBeenCalledWith("test-collection");
|
|
259
238
|
});
|
|
260
239
|
});
|
|
261
240
|
|
|
@@ -304,7 +283,7 @@ describe("QdrantManager", () => {
|
|
|
304
283
|
const normalizedId = calls[0][1].points[0].id;
|
|
305
284
|
// Check that it's a valid UUID format
|
|
306
285
|
expect(normalizedId).toMatch(
|
|
307
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
286
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
308
287
|
);
|
|
309
288
|
// Ensure it's not the original ID
|
|
310
289
|
expect(normalizedId).not.toBe("my-custom-id");
|
|
@@ -312,9 +291,7 @@ describe("QdrantManager", () => {
|
|
|
312
291
|
|
|
313
292
|
it("should preserve UUID format IDs without modification", async () => {
|
|
314
293
|
const uuidId = "123e4567-e89b-12d3-a456-426614174000";
|
|
315
|
-
const points = [
|
|
316
|
-
{ id: uuidId, vector: [0.1, 0.2, 0.3], payload: { text: "test" } },
|
|
317
|
-
];
|
|
294
|
+
const points = [{ id: uuidId, vector: [0.1, 0.2, 0.3], payload: { text: "test" } }];
|
|
318
295
|
|
|
319
296
|
await manager.addPoints("test-collection", points);
|
|
320
297
|
|
|
@@ -342,10 +319,8 @@ describe("QdrantManager", () => {
|
|
|
342
319
|
},
|
|
343
320
|
});
|
|
344
321
|
|
|
345
|
-
await expect(
|
|
346
|
-
|
|
347
|
-
).rejects.toThrow(
|
|
348
|
-
'Failed to add points to collection "test-collection": Vector dimension mismatch',
|
|
322
|
+
await expect(manager.addPoints("test-collection", points)).rejects.toThrow(
|
|
323
|
+
'Failed to add points to collection "test-collection": Vector dimension mismatch'
|
|
349
324
|
);
|
|
350
325
|
});
|
|
351
326
|
|
|
@@ -354,10 +329,8 @@ describe("QdrantManager", () => {
|
|
|
354
329
|
|
|
355
330
|
mockClient.upsert.mockRejectedValue(new Error("Network error"));
|
|
356
331
|
|
|
357
|
-
await expect(
|
|
358
|
-
|
|
359
|
-
).rejects.toThrow(
|
|
360
|
-
'Failed to add points to collection "test-collection": Network error',
|
|
332
|
+
await expect(manager.addPoints("test-collection", points)).rejects.toThrow(
|
|
333
|
+
'Failed to add points to collection "test-collection": Network error'
|
|
361
334
|
);
|
|
362
335
|
});
|
|
363
336
|
|
|
@@ -366,10 +339,8 @@ describe("QdrantManager", () => {
|
|
|
366
339
|
|
|
367
340
|
mockClient.upsert.mockRejectedValue("Unknown error");
|
|
368
341
|
|
|
369
|
-
await expect(
|
|
370
|
-
|
|
371
|
-
).rejects.toThrow(
|
|
372
|
-
'Failed to add points to collection "test-collection": Unknown error',
|
|
342
|
+
await expect(manager.addPoints("test-collection", points)).rejects.toThrow(
|
|
343
|
+
'Failed to add points to collection "test-collection": Unknown error'
|
|
373
344
|
);
|
|
374
345
|
});
|
|
375
346
|
});
|
|
@@ -397,11 +368,7 @@ describe("QdrantManager", () => {
|
|
|
397
368
|
{ id: 2, score: 0.85, payload: { text: "result2" } },
|
|
398
369
|
]);
|
|
399
370
|
|
|
400
|
-
const results = await manager.search(
|
|
401
|
-
"test-collection",
|
|
402
|
-
[0.1, 0.2, 0.3],
|
|
403
|
-
5,
|
|
404
|
-
);
|
|
371
|
+
const results = await manager.search("test-collection", [0.1, 0.2, 0.3], 5);
|
|
405
372
|
|
|
406
373
|
expect(results).toEqual([
|
|
407
374
|
{ id: 1, score: 0.95, payload: { text: "result1" } },
|
|
@@ -500,9 +467,7 @@ describe("QdrantManager", () => {
|
|
|
500
467
|
});
|
|
501
468
|
|
|
502
469
|
it("should handle null payload in results", async () => {
|
|
503
|
-
mockClient.search.mockResolvedValue([
|
|
504
|
-
{ id: 1, score: 0.95, payload: null },
|
|
505
|
-
]);
|
|
470
|
+
mockClient.search.mockResolvedValue([{ id: 1, score: 0.95, payload: null }]);
|
|
506
471
|
|
|
507
472
|
const results = await manager.search("test-collection", [0.1, 0.2, 0.3]);
|
|
508
473
|
|
|
@@ -531,19 +496,11 @@ describe("QdrantManager", () => {
|
|
|
531
496
|
},
|
|
532
497
|
});
|
|
533
498
|
|
|
534
|
-
mockClient.search.mockResolvedValue([
|
|
535
|
-
{ id: 1, score: 0.95, payload: { text: "result1" } },
|
|
536
|
-
]);
|
|
499
|
+
mockClient.search.mockResolvedValue([{ id: 1, score: 0.95, payload: { text: "result1" } }]);
|
|
537
500
|
|
|
538
|
-
const results = await manager.search(
|
|
539
|
-
"hybrid-collection",
|
|
540
|
-
[0.1, 0.2, 0.3],
|
|
541
|
-
5,
|
|
542
|
-
);
|
|
501
|
+
const results = await manager.search("hybrid-collection", [0.1, 0.2, 0.3], 5);
|
|
543
502
|
|
|
544
|
-
expect(results).toEqual([
|
|
545
|
-
{ id: 1, score: 0.95, payload: { text: "result1" } },
|
|
546
|
-
]);
|
|
503
|
+
expect(results).toEqual([{ id: 1, score: 0.95, payload: { text: "result1" } }]);
|
|
547
504
|
expect(mockClient.search).toHaveBeenCalledWith("hybrid-collection", {
|
|
548
505
|
vector: { name: "dense", vector: [0.1, 0.2, 0.3] },
|
|
549
506
|
limit: 5,
|
|
@@ -566,19 +523,11 @@ describe("QdrantManager", () => {
|
|
|
566
523
|
},
|
|
567
524
|
});
|
|
568
525
|
|
|
569
|
-
mockClient.search.mockResolvedValue([
|
|
570
|
-
{ id: 1, score: 0.95, payload: { text: "result1" } },
|
|
571
|
-
]);
|
|
526
|
+
mockClient.search.mockResolvedValue([{ id: 1, score: 0.95, payload: { text: "result1" } }]);
|
|
572
527
|
|
|
573
|
-
const results = await manager.search(
|
|
574
|
-
"standard-collection",
|
|
575
|
-
[0.1, 0.2, 0.3],
|
|
576
|
-
5,
|
|
577
|
-
);
|
|
528
|
+
const results = await manager.search("standard-collection", [0.1, 0.2, 0.3], 5);
|
|
578
529
|
|
|
579
|
-
expect(results).toEqual([
|
|
580
|
-
{ id: 1, score: 0.95, payload: { text: "result1" } },
|
|
581
|
-
]);
|
|
530
|
+
expect(results).toEqual([{ id: 1, score: 0.95, payload: { text: "result1" } }]);
|
|
582
531
|
expect(mockClient.search).toHaveBeenCalledWith("standard-collection", {
|
|
583
532
|
vector: [0.1, 0.2, 0.3],
|
|
584
533
|
limit: 5,
|
|
@@ -589,9 +538,7 @@ describe("QdrantManager", () => {
|
|
|
589
538
|
|
|
590
539
|
describe("getPoint", () => {
|
|
591
540
|
it("should retrieve a point by id", async () => {
|
|
592
|
-
mockClient.retrieve.mockResolvedValue([
|
|
593
|
-
{ id: 1, payload: { text: "test" } },
|
|
594
|
-
]);
|
|
541
|
+
mockClient.retrieve.mockResolvedValue([{ id: 1, payload: { text: "test" } }]);
|
|
595
542
|
|
|
596
543
|
const point = await manager.getPoint("test-collection", 1);
|
|
597
544
|
|
|
@@ -691,11 +638,7 @@ describe("QdrantManager", () => {
|
|
|
691
638
|
],
|
|
692
639
|
});
|
|
693
640
|
|
|
694
|
-
const results = await manager.hybridSearch(
|
|
695
|
-
"test-collection",
|
|
696
|
-
denseVector,
|
|
697
|
-
sparseVector,
|
|
698
|
-
);
|
|
641
|
+
const results = await manager.hybridSearch("test-collection", denseVector, sparseVector);
|
|
699
642
|
|
|
700
643
|
expect(results).toEqual([
|
|
701
644
|
{ id: 1, score: 0.95, payload: { text: "result1" } },
|
|
@@ -731,12 +674,7 @@ describe("QdrantManager", () => {
|
|
|
731
674
|
|
|
732
675
|
mockClient.query.mockResolvedValue({ points: [] });
|
|
733
676
|
|
|
734
|
-
await manager.hybridSearch(
|
|
735
|
-
"test-collection",
|
|
736
|
-
denseVector,
|
|
737
|
-
sparseVector,
|
|
738
|
-
10,
|
|
739
|
-
);
|
|
677
|
+
await manager.hybridSearch("test-collection", denseVector, sparseVector, 10);
|
|
740
678
|
|
|
741
679
|
expect(mockClient.query).toHaveBeenCalledWith(
|
|
742
680
|
"test-collection",
|
|
@@ -745,7 +683,7 @@ describe("QdrantManager", () => {
|
|
|
745
683
|
expect.objectContaining({ limit: 40 }), // 10 * 4
|
|
746
684
|
]),
|
|
747
685
|
limit: 10,
|
|
748
|
-
})
|
|
686
|
+
})
|
|
749
687
|
);
|
|
750
688
|
});
|
|
751
689
|
|
|
@@ -756,13 +694,7 @@ describe("QdrantManager", () => {
|
|
|
756
694
|
|
|
757
695
|
mockClient.query.mockResolvedValue({ points: [] });
|
|
758
696
|
|
|
759
|
-
await manager.hybridSearch(
|
|
760
|
-
"test-collection",
|
|
761
|
-
denseVector,
|
|
762
|
-
sparseVector,
|
|
763
|
-
5,
|
|
764
|
-
filter,
|
|
765
|
-
);
|
|
697
|
+
await manager.hybridSearch("test-collection", denseVector, sparseVector, 5, filter);
|
|
766
698
|
|
|
767
699
|
expect(mockClient.query).toHaveBeenCalledWith("test-collection", {
|
|
768
700
|
prefetch: [
|
|
@@ -804,13 +736,7 @@ describe("QdrantManager", () => {
|
|
|
804
736
|
|
|
805
737
|
mockClient.query.mockResolvedValue({ points: [] });
|
|
806
738
|
|
|
807
|
-
await manager.hybridSearch(
|
|
808
|
-
"test-collection",
|
|
809
|
-
denseVector,
|
|
810
|
-
sparseVector,
|
|
811
|
-
5,
|
|
812
|
-
filter,
|
|
813
|
-
);
|
|
739
|
+
await manager.hybridSearch("test-collection", denseVector, sparseVector, 5, filter);
|
|
814
740
|
|
|
815
741
|
const call = mockClient.query.mock.calls[0][1];
|
|
816
742
|
expect(call.prefetch[0].filter).toEqual(filter);
|
|
@@ -826,13 +752,7 @@ describe("QdrantManager", () => {
|
|
|
826
752
|
|
|
827
753
|
mockClient.query.mockResolvedValue({ points: [] });
|
|
828
754
|
|
|
829
|
-
await manager.hybridSearch(
|
|
830
|
-
"test-collection",
|
|
831
|
-
denseVector,
|
|
832
|
-
sparseVector,
|
|
833
|
-
5,
|
|
834
|
-
filter,
|
|
835
|
-
);
|
|
755
|
+
await manager.hybridSearch("test-collection", denseVector, sparseVector, 5, filter);
|
|
836
756
|
|
|
837
757
|
const call = mockClient.query.mock.calls[0][1];
|
|
838
758
|
expect(call.prefetch[0].filter).toEqual(filter);
|
|
@@ -848,13 +768,7 @@ describe("QdrantManager", () => {
|
|
|
848
768
|
|
|
849
769
|
mockClient.query.mockResolvedValue({ points: [] });
|
|
850
770
|
|
|
851
|
-
await manager.hybridSearch(
|
|
852
|
-
"test-collection",
|
|
853
|
-
denseVector,
|
|
854
|
-
sparseVector,
|
|
855
|
-
5,
|
|
856
|
-
filter,
|
|
857
|
-
);
|
|
771
|
+
await manager.hybridSearch("test-collection", denseVector, sparseVector, 5, filter);
|
|
858
772
|
|
|
859
773
|
const call = mockClient.query.mock.calls[0][1];
|
|
860
774
|
expect(call.prefetch[0].filter).toEqual(filter);
|
|
@@ -867,13 +781,7 @@ describe("QdrantManager", () => {
|
|
|
867
781
|
|
|
868
782
|
mockClient.query.mockResolvedValue({ points: [] });
|
|
869
783
|
|
|
870
|
-
await manager.hybridSearch(
|
|
871
|
-
"test-collection",
|
|
872
|
-
denseVector,
|
|
873
|
-
sparseVector,
|
|
874
|
-
5,
|
|
875
|
-
{},
|
|
876
|
-
);
|
|
784
|
+
await manager.hybridSearch("test-collection", denseVector, sparseVector, 5, {});
|
|
877
785
|
|
|
878
786
|
const call = mockClient.query.mock.calls[0][1];
|
|
879
787
|
expect(call.prefetch[0].filter).toBeUndefined();
|
|
@@ -888,11 +796,7 @@ describe("QdrantManager", () => {
|
|
|
888
796
|
points: [{ id: 1, score: 0.95, payload: null }],
|
|
889
797
|
});
|
|
890
798
|
|
|
891
|
-
const results = await manager.hybridSearch(
|
|
892
|
-
"test-collection",
|
|
893
|
-
denseVector,
|
|
894
|
-
sparseVector,
|
|
895
|
-
);
|
|
799
|
+
const results = await manager.hybridSearch("test-collection", denseVector, sparseVector);
|
|
896
800
|
|
|
897
801
|
expect(results).toEqual([{ id: 1, score: 0.95, payload: undefined }]);
|
|
898
802
|
});
|
|
@@ -910,9 +814,9 @@ describe("QdrantManager", () => {
|
|
|
910
814
|
});
|
|
911
815
|
|
|
912
816
|
await expect(
|
|
913
|
-
manager.hybridSearch("test-collection", denseVector, sparseVector)
|
|
817
|
+
manager.hybridSearch("test-collection", denseVector, sparseVector)
|
|
914
818
|
).rejects.toThrow(
|
|
915
|
-
'Hybrid search failed on collection "test-collection": Named vector not found'
|
|
819
|
+
'Hybrid search failed on collection "test-collection": Named vector not found'
|
|
916
820
|
);
|
|
917
821
|
});
|
|
918
822
|
|
|
@@ -923,10 +827,8 @@ describe("QdrantManager", () => {
|
|
|
923
827
|
mockClient.query.mockRejectedValue(new Error("Network timeout"));
|
|
924
828
|
|
|
925
829
|
await expect(
|
|
926
|
-
manager.hybridSearch("test-collection", denseVector, sparseVector)
|
|
927
|
-
).rejects.toThrow(
|
|
928
|
-
'Hybrid search failed on collection "test-collection": Network timeout',
|
|
929
|
-
);
|
|
830
|
+
manager.hybridSearch("test-collection", denseVector, sparseVector)
|
|
831
|
+
).rejects.toThrow('Hybrid search failed on collection "test-collection": Network timeout');
|
|
930
832
|
});
|
|
931
833
|
|
|
932
834
|
it("should throw error with String(error) fallback", async () => {
|
|
@@ -936,10 +838,8 @@ describe("QdrantManager", () => {
|
|
|
936
838
|
mockClient.query.mockRejectedValue("Unknown error");
|
|
937
839
|
|
|
938
840
|
await expect(
|
|
939
|
-
manager.hybridSearch("test-collection", denseVector, sparseVector)
|
|
940
|
-
).rejects.toThrow(
|
|
941
|
-
'Hybrid search failed on collection "test-collection": Unknown error',
|
|
942
|
-
);
|
|
841
|
+
manager.hybridSearch("test-collection", denseVector, sparseVector)
|
|
842
|
+
).rejects.toThrow('Hybrid search failed on collection "test-collection": Unknown error');
|
|
943
843
|
});
|
|
944
844
|
});
|
|
945
845
|
|
|
@@ -1030,7 +930,7 @@ describe("QdrantManager", () => {
|
|
|
1030
930
|
const normalizedId = calls[0][1].points[0].id;
|
|
1031
931
|
// Check that it's a valid UUID format
|
|
1032
932
|
expect(normalizedId).toMatch(
|
|
1033
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
933
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
1034
934
|
);
|
|
1035
935
|
expect(normalizedId).not.toBe("my-doc-id");
|
|
1036
936
|
});
|
|
@@ -1080,10 +980,8 @@ describe("QdrantManager", () => {
|
|
|
1080
980
|
},
|
|
1081
981
|
});
|
|
1082
982
|
|
|
1083
|
-
await expect(
|
|
1084
|
-
|
|
1085
|
-
).rejects.toThrow(
|
|
1086
|
-
'Failed to add points with sparse vectors to collection "test-collection": Sparse vector not configured',
|
|
983
|
+
await expect(manager.addPointsWithSparse("test-collection", points)).rejects.toThrow(
|
|
984
|
+
'Failed to add points with sparse vectors to collection "test-collection": Sparse vector not configured'
|
|
1087
985
|
);
|
|
1088
986
|
});
|
|
1089
987
|
|
|
@@ -1098,10 +996,8 @@ describe("QdrantManager", () => {
|
|
|
1098
996
|
|
|
1099
997
|
mockClient.upsert.mockRejectedValue(new Error("Connection refused"));
|
|
1100
998
|
|
|
1101
|
-
await expect(
|
|
1102
|
-
|
|
1103
|
-
).rejects.toThrow(
|
|
1104
|
-
'Failed to add points with sparse vectors to collection "test-collection": Connection refused',
|
|
999
|
+
await expect(manager.addPointsWithSparse("test-collection", points)).rejects.toThrow(
|
|
1000
|
+
'Failed to add points with sparse vectors to collection "test-collection": Connection refused'
|
|
1105
1001
|
);
|
|
1106
1002
|
});
|
|
1107
1003
|
|
|
@@ -1116,10 +1012,8 @@ describe("QdrantManager", () => {
|
|
|
1116
1012
|
|
|
1117
1013
|
mockClient.upsert.mockRejectedValue("Unexpected error");
|
|
1118
1014
|
|
|
1119
|
-
await expect(
|
|
1120
|
-
|
|
1121
|
-
).rejects.toThrow(
|
|
1122
|
-
'Failed to add points with sparse vectors to collection "test-collection": Unexpected error',
|
|
1015
|
+
await expect(manager.addPointsWithSparse("test-collection", points)).rejects.toThrow(
|
|
1016
|
+
'Failed to add points with sparse vectors to collection "test-collection": Unexpected error'
|
|
1123
1017
|
);
|
|
1124
1018
|
});
|
|
1125
1019
|
});
|
package/src/qdrant/client.ts
CHANGED
|
@@ -39,8 +39,7 @@ export class QdrantManager {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// Check if already a valid UUID (8-4-4-4-12 format)
|
|
42
|
-
const uuidRegex =
|
|
43
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
42
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
44
43
|
if (uuidRegex.test(id)) {
|
|
45
44
|
return id;
|
|
46
45
|
}
|
|
@@ -54,12 +53,9 @@ export class QdrantManager {
|
|
|
54
53
|
name: string,
|
|
55
54
|
vectorSize: number,
|
|
56
55
|
distance: "Cosine" | "Euclid" | "Dot" = "Cosine",
|
|
57
|
-
enableSparse: boolean = false
|
|
56
|
+
enableSparse: boolean = false
|
|
58
57
|
): Promise<void> {
|
|
59
|
-
this.log.debug(
|
|
60
|
-
{ collection: name, vectorSize, distance, enableSparse },
|
|
61
|
-
"createCollection",
|
|
62
|
-
);
|
|
58
|
+
this.log.debug({ collection: name, vectorSize, distance, enableSparse }, "createCollection");
|
|
63
59
|
const config: any = {};
|
|
64
60
|
|
|
65
61
|
// When hybrid search is enabled, use named vectors
|
|
@@ -147,12 +143,9 @@ export class QdrantManager {
|
|
|
147
143
|
id: string | number;
|
|
148
144
|
vector: number[];
|
|
149
145
|
payload?: Record<string, any>;
|
|
150
|
-
}
|
|
146
|
+
}>
|
|
151
147
|
): Promise<void> {
|
|
152
|
-
this.log.debug(
|
|
153
|
-
{ collection: collectionName, count: points.length },
|
|
154
|
-
"addPoints",
|
|
155
|
-
);
|
|
148
|
+
this.log.debug({ collection: collectionName, count: points.length }, "addPoints");
|
|
156
149
|
try {
|
|
157
150
|
// Normalize all IDs to ensure string IDs are in UUID format
|
|
158
151
|
const normalizedPoints = points.map((point) => ({
|
|
@@ -165,11 +158,8 @@ export class QdrantManager {
|
|
|
165
158
|
points: normalizedPoints,
|
|
166
159
|
});
|
|
167
160
|
} catch (error: any) {
|
|
168
|
-
const errorMessage =
|
|
169
|
-
|
|
170
|
-
throw new Error(
|
|
171
|
-
`Failed to add points to collection "${collectionName}": ${errorMessage}`,
|
|
172
|
-
);
|
|
161
|
+
const errorMessage = error?.data?.status?.error || error?.message || String(error);
|
|
162
|
+
throw new Error(`Failed to add points to collection "${collectionName}": ${errorMessage}`);
|
|
173
163
|
}
|
|
174
164
|
}
|
|
175
165
|
|
|
@@ -177,7 +167,7 @@ export class QdrantManager {
|
|
|
177
167
|
collectionName: string,
|
|
178
168
|
vector: number[],
|
|
179
169
|
limit: number = 5,
|
|
180
|
-
filter?: Record<string, any
|
|
170
|
+
filter?: Record<string, any>
|
|
181
171
|
): Promise<SearchResult[]> {
|
|
182
172
|
this.log.debug({ collection: collectionName, limit }, "search");
|
|
183
173
|
// Convert simple key-value filter to Qdrant filter format
|
|
@@ -218,7 +208,7 @@ export class QdrantManager {
|
|
|
218
208
|
|
|
219
209
|
async getPoint(
|
|
220
210
|
collectionName: string,
|
|
221
|
-
id: string | number
|
|
211
|
+
id: string | number
|
|
222
212
|
): Promise<{ id: string | number; payload?: Record<string, any> } | null> {
|
|
223
213
|
try {
|
|
224
214
|
const normalizedId = this.normalizeId(id);
|
|
@@ -239,14 +229,8 @@ export class QdrantManager {
|
|
|
239
229
|
}
|
|
240
230
|
}
|
|
241
231
|
|
|
242
|
-
async deletePoints(
|
|
243
|
-
collectionName:
|
|
244
|
-
ids: (string | number)[],
|
|
245
|
-
): Promise<void> {
|
|
246
|
-
this.log.debug(
|
|
247
|
-
{ collection: collectionName, count: ids.length },
|
|
248
|
-
"deletePoints",
|
|
249
|
-
);
|
|
232
|
+
async deletePoints(collectionName: string, ids: (string | number)[]): Promise<void> {
|
|
233
|
+
this.log.debug({ collection: collectionName, count: ids.length }, "deletePoints");
|
|
250
234
|
// Normalize IDs to ensure string IDs are in UUID format
|
|
251
235
|
const normalizedIds = ids.map((id) => this.normalizeId(id));
|
|
252
236
|
|
|
@@ -260,10 +244,7 @@ export class QdrantManager {
|
|
|
260
244
|
* Deletes points matching a filter condition.
|
|
261
245
|
* Useful for deleting all chunks associated with a specific file path.
|
|
262
246
|
*/
|
|
263
|
-
async deletePointsByFilter(
|
|
264
|
-
collectionName: string,
|
|
265
|
-
filter: Record<string, any>,
|
|
266
|
-
): Promise<void> {
|
|
247
|
+
async deletePointsByFilter(collectionName: string, filter: Record<string, any>): Promise<void> {
|
|
267
248
|
this.log.debug({ collection: collectionName }, "deletePointsByFilter");
|
|
268
249
|
await this.client.delete(collectionName, {
|
|
269
250
|
wait: true,
|
|
@@ -281,7 +262,7 @@ export class QdrantManager {
|
|
|
281
262
|
sparseVector: SparseVector,
|
|
282
263
|
limit: number = 5,
|
|
283
264
|
filter?: Record<string, any>,
|
|
284
|
-
_semanticWeight: number = 0.7
|
|
265
|
+
_semanticWeight: number = 0.7
|
|
285
266
|
): Promise<SearchResult[]> {
|
|
286
267
|
this.log.debug({ collection: collectionName, limit }, "hybridSearch");
|
|
287
268
|
// Convert simple key-value filter to Qdrant filter format
|
|
@@ -332,11 +313,8 @@ export class QdrantManager {
|
|
|
332
313
|
payload: result.payload || undefined,
|
|
333
314
|
}));
|
|
334
315
|
} catch (error: any) {
|
|
335
|
-
const errorMessage =
|
|
336
|
-
|
|
337
|
-
throw new Error(
|
|
338
|
-
`Hybrid search failed on collection "${collectionName}": ${errorMessage}`,
|
|
339
|
-
);
|
|
316
|
+
const errorMessage = error?.data?.status?.error || error?.message || String(error);
|
|
317
|
+
throw new Error(`Hybrid search failed on collection "${collectionName}": ${errorMessage}`);
|
|
340
318
|
}
|
|
341
319
|
}
|
|
342
320
|
|
|
@@ -350,12 +328,9 @@ export class QdrantManager {
|
|
|
350
328
|
vector: number[];
|
|
351
329
|
sparseVector: SparseVector;
|
|
352
330
|
payload?: Record<string, any>;
|
|
353
|
-
}
|
|
331
|
+
}>
|
|
354
332
|
): Promise<void> {
|
|
355
|
-
this.log.debug(
|
|
356
|
-
{ collection: collectionName, count: points.length },
|
|
357
|
-
"addPointsWithSparse",
|
|
358
|
-
);
|
|
333
|
+
this.log.debug({ collection: collectionName, count: points.length }, "addPointsWithSparse");
|
|
359
334
|
try {
|
|
360
335
|
// Normalize all IDs to ensure string IDs are in UUID format
|
|
361
336
|
const normalizedPoints = points.map((point) => ({
|
|
@@ -372,10 +347,9 @@ export class QdrantManager {
|
|
|
372
347
|
points: normalizedPoints,
|
|
373
348
|
});
|
|
374
349
|
} catch (error: any) {
|
|
375
|
-
const errorMessage =
|
|
376
|
-
error?.data?.status?.error || error?.message || String(error);
|
|
350
|
+
const errorMessage = error?.data?.status?.error || error?.message || String(error);
|
|
377
351
|
throw new Error(
|
|
378
|
-
`Failed to add points with sparse vectors to collection "${collectionName}": ${errorMessage}
|
|
352
|
+
`Failed to add points with sparse vectors to collection "${collectionName}": ${errorMessage}`
|
|
379
353
|
);
|
|
380
354
|
}
|
|
381
355
|
}
|
package/src/resources/index.ts
CHANGED
|
@@ -2,19 +2,13 @@
|
|
|
2
2
|
* Resource registration module
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
McpServer,
|
|
7
|
-
ResourceTemplate,
|
|
8
|
-
} from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { type McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
6
|
import type { QdrantManager } from "../qdrant/client.js";
|
|
10
7
|
|
|
11
8
|
/**
|
|
12
9
|
* Register all MCP resources on the server
|
|
13
10
|
*/
|
|
14
|
-
export function registerAllResources(
|
|
15
|
-
server: McpServer,
|
|
16
|
-
qdrant: QdrantManager,
|
|
17
|
-
): void {
|
|
11
|
+
export function registerAllResources(server: McpServer, qdrant: QdrantManager): void {
|
|
18
12
|
// Static resource: list all collections
|
|
19
13
|
server.registerResource(
|
|
20
14
|
"collections",
|
|
@@ -35,7 +29,7 @@ export function registerAllResources(
|
|
|
35
29
|
},
|
|
36
30
|
],
|
|
37
31
|
};
|
|
38
|
-
}
|
|
32
|
+
}
|
|
39
33
|
);
|
|
40
34
|
|
|
41
35
|
// Dynamic resource: individual collection info
|
|
@@ -74,6 +68,6 @@ export function registerAllResources(
|
|
|
74
68
|
},
|
|
75
69
|
],
|
|
76
70
|
};
|
|
77
|
-
}
|
|
71
|
+
}
|
|
78
72
|
);
|
|
79
73
|
}
|