@mhalder/qdrant-mcp-server 3.2.0 → 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.
- package/.dagger/.gitattributes +1 -0
- package/.dagger/package.json +6 -0
- package/.dagger/src/index.ts +83 -0
- package/.dagger/tsconfig.json +13 -0
- package/.dagger/yarn.lock +8 -0
- package/.github/workflows/ci.yml +17 -27
- package/.github/workflows/release.yml +16 -19
- package/CHANGELOG.md +13 -0
- package/README.md +11 -9
- package/build/code/chunker/tree-sitter-chunker.d.ts.map +1 -1
- package/build/code/chunker/tree-sitter-chunker.js +15 -3
- package/build/code/chunker/tree-sitter-chunker.js.map +1 -1
- package/build/code/indexer.d.ts +1 -0
- package/build/code/indexer.d.ts.map +1 -1
- package/build/code/indexer.js +24 -4
- package/build/code/indexer.js.map +1 -1
- package/build/embeddings/cohere.d.ts +1 -0
- package/build/embeddings/cohere.d.ts.map +1 -1
- package/build/embeddings/cohere.js +8 -1
- package/build/embeddings/cohere.js.map +1 -1
- package/build/embeddings/cohere.test.js +11 -0
- package/build/embeddings/cohere.test.js.map +1 -1
- package/build/embeddings/factory.d.ts.map +1 -1
- package/build/embeddings/factory.js +2 -0
- package/build/embeddings/factory.js.map +1 -1
- package/build/embeddings/factory.test.js +12 -1
- package/build/embeddings/factory.test.js.map +1 -1
- package/build/embeddings/ollama.d.ts +1 -0
- package/build/embeddings/ollama.d.ts.map +1 -1
- package/build/embeddings/ollama.js +8 -1
- package/build/embeddings/ollama.js.map +1 -1
- package/build/embeddings/ollama.test.js +11 -0
- package/build/embeddings/ollama.test.js.map +1 -1
- package/build/embeddings/openai.d.ts +1 -0
- package/build/embeddings/openai.d.ts.map +1 -1
- package/build/embeddings/openai.js +8 -1
- package/build/embeddings/openai.js.map +1 -1
- package/build/embeddings/openai.test.js +11 -0
- package/build/embeddings/openai.test.js.map +1 -1
- package/build/embeddings/voyage.d.ts +1 -0
- package/build/embeddings/voyage.d.ts.map +1 -1
- package/build/embeddings/voyage.js +8 -1
- package/build/embeddings/voyage.js.map +1 -1
- package/build/embeddings/voyage.test.js +11 -0
- package/build/embeddings/voyage.test.js.map +1 -1
- package/build/git/indexer.d.ts +1 -0
- package/build/git/indexer.d.ts.map +1 -1
- package/build/git/indexer.js +16 -3
- package/build/git/indexer.js.map +1 -1
- package/build/git/indexer.test.js +15 -9
- package/build/git/indexer.test.js.map +1 -1
- package/build/index.js +35 -26
- package/build/index.js.map +1 -1
- package/build/index.test.js +105 -91
- package/build/index.test.js.map +1 -1
- package/build/logger.d.ts +4 -0
- package/build/logger.d.ts.map +1 -0
- package/build/logger.js +24 -0
- package/build/logger.js.map +1 -0
- package/build/qdrant/client.d.ts +1 -0
- package/build/qdrant/client.d.ts.map +1 -1
- package/build/qdrant/client.js +10 -0
- package/build/qdrant/client.js.map +1 -1
- package/build/qdrant/client.test.js +11 -0
- package/build/qdrant/client.test.js.map +1 -1
- package/build/tools/code.d.ts.map +1 -1
- package/build/tools/code.js +44 -13
- package/build/tools/code.js.map +1 -1
- package/build/tools/collection.d.ts.map +1 -1
- package/build/tools/collection.js +15 -8
- package/build/tools/collection.js.map +1 -1
- package/build/tools/document.d.ts.map +1 -1
- package/build/tools/document.js +9 -4
- package/build/tools/document.js.map +1 -1
- package/build/tools/federated.d.ts.map +1 -1
- package/build/tools/federated.js +9 -4
- package/build/tools/federated.js.map +1 -1
- package/build/tools/federated.test.js +11 -0
- 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 +44 -12
- package/build/tools/git-history.js.map +1 -1
- package/build/tools/logging.d.ts +16 -0
- package/build/tools/logging.d.ts.map +1 -0
- package/build/tools/logging.js +68 -0
- package/build/tools/logging.js.map +1 -0
- package/build/tools/logging.test.d.ts +2 -0
- package/build/tools/logging.test.d.ts.map +1 -0
- package/build/tools/logging.test.js +139 -0
- package/build/tools/logging.test.js.map +1 -0
- package/build/tools/schemas.d.ts +32 -19
- package/build/tools/schemas.d.ts.map +1 -1
- package/build/tools/schemas.js +9 -3
- package/build/tools/schemas.js.map +1 -1
- package/build/tools/search.d.ts.map +1 -1
- package/build/tools/search.js +13 -4
- package/build/tools/search.js.map +1 -1
- package/dagger.json +8 -0
- package/mise.toml +2 -0
- package/package.json +14 -13
- package/src/code/chunker/tree-sitter-chunker.ts +41 -9
- package/src/code/indexer.ts +41 -6
- package/src/embeddings/cohere.test.ts +12 -0
- package/src/embeddings/cohere.ts +10 -2
- package/src/embeddings/factory.test.ts +13 -1
- package/src/embeddings/factory.ts +3 -0
- package/src/embeddings/ollama.test.ts +12 -0
- package/src/embeddings/ollama.ts +10 -2
- package/src/embeddings/openai.test.ts +12 -0
- package/src/embeddings/openai.ts +10 -2
- package/src/embeddings/voyage.test.ts +12 -0
- package/src/embeddings/voyage.ts +10 -2
- package/src/git/indexer.test.ts +22 -16
- package/src/git/indexer.ts +30 -4
- package/src/index.test.ts +128 -106
- package/src/index.ts +59 -38
- package/src/logger.ts +33 -0
- package/src/qdrant/client.test.ts +12 -0
- package/src/qdrant/client.ts +22 -0
- package/src/tools/code.ts +107 -62
- package/src/tools/collection.ts +39 -22
- package/src/tools/document.ts +52 -22
- package/src/tools/federated.test.ts +12 -0
- package/src/tools/federated.ts +143 -125
- package/src/tools/git-history.ts +117 -60
- package/src/tools/logging.test.ts +206 -0
- package/src/tools/logging.ts +85 -0
- package/src/tools/schemas.ts +9 -3
- package/src/tools/search.ts +93 -71
- package/tests/code/chunker/tree-sitter-chunker.test.ts +13 -1
- package/tests/code/indexer.test.ts +12 -0
- package/tests/code/integration.test.ts +14 -1
package/src/git/indexer.ts
CHANGED
|
@@ -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(
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
699
|
+
this.log.error({ err: error }, "Failed to store indexing marker");
|
|
674
700
|
}
|
|
675
701
|
}
|
|
676
702
|
|
package/src/index.test.ts
CHANGED
|
@@ -1,190 +1,212 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from
|
|
2
|
-
|
|
3
|
-
vi.mock(
|
|
4
|
-
vi.mock(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
|
|
3
|
+
vi.mock("./qdrant/client.js");
|
|
4
|
+
vi.mock("./embeddings/openai.js");
|
|
5
|
+
vi.mock("./logger.js", () => ({
|
|
6
|
+
default: {
|
|
7
|
+
info: vi.fn(),
|
|
8
|
+
warn: vi.fn(),
|
|
9
|
+
error: vi.fn(),
|
|
10
|
+
debug: vi.fn(),
|
|
11
|
+
fatal: vi.fn(),
|
|
12
|
+
trace: vi.fn(),
|
|
13
|
+
child: vi.fn().mockReturnThis(),
|
|
14
|
+
},
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
describe("MCP Server Tool Schemas", () => {
|
|
18
|
+
describe("CreateCollectionSchema", () => {
|
|
19
|
+
it("should validate correct collection creation input", async () => {
|
|
20
|
+
const { z } = await import("zod");
|
|
10
21
|
|
|
11
22
|
const CreateCollectionSchema = z.object({
|
|
12
23
|
name: z.string(),
|
|
13
|
-
distance: z.enum([
|
|
24
|
+
distance: z.enum(["Cosine", "Euclid", "Dot"]).optional(),
|
|
14
25
|
});
|
|
15
26
|
|
|
16
|
-
const validInput = { name:
|
|
27
|
+
const validInput = { name: "test-collection" };
|
|
17
28
|
expect(() => CreateCollectionSchema.parse(validInput)).not.toThrow();
|
|
18
29
|
|
|
19
|
-
const validInputWithDistance = {
|
|
20
|
-
|
|
30
|
+
const validInputWithDistance = {
|
|
31
|
+
name: "test-collection",
|
|
32
|
+
distance: "Cosine" as const,
|
|
33
|
+
};
|
|
34
|
+
expect(() =>
|
|
35
|
+
CreateCollectionSchema.parse(validInputWithDistance),
|
|
36
|
+
).not.toThrow();
|
|
21
37
|
});
|
|
22
38
|
|
|
23
|
-
it(
|
|
24
|
-
const { z } = await import(
|
|
39
|
+
it("should reject invalid distance metric", async () => {
|
|
40
|
+
const { z } = await import("zod");
|
|
25
41
|
|
|
26
42
|
const CreateCollectionSchema = z.object({
|
|
27
43
|
name: z.string(),
|
|
28
|
-
distance: z.enum([
|
|
44
|
+
distance: z.enum(["Cosine", "Euclid", "Dot"]).optional(),
|
|
29
45
|
});
|
|
30
46
|
|
|
31
|
-
const invalidInput = { name:
|
|
47
|
+
const invalidInput = { name: "test", distance: "Invalid" };
|
|
32
48
|
expect(() => CreateCollectionSchema.parse(invalidInput)).toThrow();
|
|
33
49
|
});
|
|
34
50
|
|
|
35
|
-
it(
|
|
36
|
-
const { z } = await import(
|
|
51
|
+
it("should require name field", async () => {
|
|
52
|
+
const { z } = await import("zod");
|
|
37
53
|
|
|
38
54
|
const CreateCollectionSchema = z.object({
|
|
39
55
|
name: z.string(),
|
|
40
|
-
distance: z.enum([
|
|
56
|
+
distance: z.enum(["Cosine", "Euclid", "Dot"]).optional(),
|
|
41
57
|
});
|
|
42
58
|
|
|
43
|
-
const invalidInput = { distance:
|
|
59
|
+
const invalidInput = { distance: "Cosine" };
|
|
44
60
|
expect(() => CreateCollectionSchema.parse(invalidInput)).toThrow();
|
|
45
61
|
});
|
|
46
62
|
});
|
|
47
63
|
|
|
48
|
-
describe(
|
|
49
|
-
it(
|
|
50
|
-
const { z } = await import(
|
|
64
|
+
describe("AddDocumentsSchema", () => {
|
|
65
|
+
it("should validate correct document addition input", async () => {
|
|
66
|
+
const { z } = await import("zod");
|
|
51
67
|
|
|
52
68
|
const AddDocumentsSchema = z.object({
|
|
53
69
|
collection: z.string(),
|
|
54
|
-
documents: z.array(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
70
|
+
documents: z.array(
|
|
71
|
+
z.object({
|
|
72
|
+
id: z.union([z.string(), z.number()]),
|
|
73
|
+
text: z.string(),
|
|
74
|
+
metadata: z.record(z.string(), z.any()).optional(),
|
|
75
|
+
}),
|
|
76
|
+
),
|
|
59
77
|
});
|
|
60
78
|
|
|
61
79
|
const validInput = {
|
|
62
|
-
collection:
|
|
80
|
+
collection: "test-collection",
|
|
63
81
|
documents: [
|
|
64
|
-
{ id: 1, text:
|
|
65
|
-
{ id:
|
|
82
|
+
{ id: 1, text: "test document" },
|
|
83
|
+
{ id: "doc-2", text: "another document", metadata: { type: "test" } },
|
|
66
84
|
],
|
|
67
85
|
};
|
|
68
86
|
|
|
69
87
|
expect(() => AddDocumentsSchema.parse(validInput)).not.toThrow();
|
|
70
88
|
});
|
|
71
89
|
|
|
72
|
-
it(
|
|
73
|
-
const { z } = await import(
|
|
90
|
+
it("should accept both string and number IDs", async () => {
|
|
91
|
+
const { z } = await import("zod");
|
|
74
92
|
|
|
75
93
|
const AddDocumentsSchema = z.object({
|
|
76
94
|
collection: z.string(),
|
|
77
|
-
documents: z.array(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
95
|
+
documents: z.array(
|
|
96
|
+
z.object({
|
|
97
|
+
id: z.union([z.string(), z.number()]),
|
|
98
|
+
text: z.string(),
|
|
99
|
+
metadata: z.record(z.string(), z.any()).optional(),
|
|
100
|
+
}),
|
|
101
|
+
),
|
|
82
102
|
});
|
|
83
103
|
|
|
84
104
|
const stringIdInput = {
|
|
85
|
-
collection:
|
|
86
|
-
documents: [{ id:
|
|
105
|
+
collection: "test",
|
|
106
|
+
documents: [{ id: "string-id", text: "test" }],
|
|
87
107
|
};
|
|
88
108
|
expect(() => AddDocumentsSchema.parse(stringIdInput)).not.toThrow();
|
|
89
109
|
|
|
90
110
|
const numberIdInput = {
|
|
91
|
-
collection:
|
|
92
|
-
documents: [{ id: 123, text:
|
|
111
|
+
collection: "test",
|
|
112
|
+
documents: [{ id: 123, text: "test" }],
|
|
93
113
|
};
|
|
94
114
|
expect(() => AddDocumentsSchema.parse(numberIdInput)).not.toThrow();
|
|
95
115
|
});
|
|
96
116
|
|
|
97
|
-
it(
|
|
98
|
-
const { z } = await import(
|
|
117
|
+
it("should require text field in documents", async () => {
|
|
118
|
+
const { z } = await import("zod");
|
|
99
119
|
|
|
100
120
|
const AddDocumentsSchema = z.object({
|
|
101
121
|
collection: z.string(),
|
|
102
|
-
documents: z.array(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
122
|
+
documents: z.array(
|
|
123
|
+
z.object({
|
|
124
|
+
id: z.union([z.string(), z.number()]),
|
|
125
|
+
text: z.string(),
|
|
126
|
+
metadata: z.record(z.string(), z.any()).optional(),
|
|
127
|
+
}),
|
|
128
|
+
),
|
|
107
129
|
});
|
|
108
130
|
|
|
109
131
|
const invalidInput = {
|
|
110
|
-
collection:
|
|
132
|
+
collection: "test",
|
|
111
133
|
documents: [{ id: 1, metadata: {} }],
|
|
112
134
|
};
|
|
113
135
|
expect(() => AddDocumentsSchema.parse(invalidInput)).toThrow();
|
|
114
136
|
});
|
|
115
137
|
});
|
|
116
138
|
|
|
117
|
-
describe(
|
|
118
|
-
it(
|
|
119
|
-
const { z } = await import(
|
|
139
|
+
describe("SemanticSearchSchema", () => {
|
|
140
|
+
it("should validate correct search input", async () => {
|
|
141
|
+
const { z } = await import("zod");
|
|
120
142
|
|
|
121
143
|
const SemanticSearchSchema = z.object({
|
|
122
144
|
collection: z.string(),
|
|
123
145
|
query: z.string(),
|
|
124
146
|
limit: z.number().optional(),
|
|
125
|
-
filter: z.record(z.any()).optional(),
|
|
147
|
+
filter: z.record(z.string(), z.any()).optional(),
|
|
126
148
|
});
|
|
127
149
|
|
|
128
150
|
const validInput = {
|
|
129
|
-
collection:
|
|
130
|
-
query:
|
|
151
|
+
collection: "test-collection",
|
|
152
|
+
query: "search query",
|
|
131
153
|
limit: 10,
|
|
132
|
-
filter: { category:
|
|
154
|
+
filter: { category: "test" },
|
|
133
155
|
};
|
|
134
156
|
|
|
135
157
|
expect(() => SemanticSearchSchema.parse(validInput)).not.toThrow();
|
|
136
158
|
});
|
|
137
159
|
|
|
138
|
-
it(
|
|
139
|
-
const { z } = await import(
|
|
160
|
+
it("should work with minimal input", async () => {
|
|
161
|
+
const { z } = await import("zod");
|
|
140
162
|
|
|
141
163
|
const SemanticSearchSchema = z.object({
|
|
142
164
|
collection: z.string(),
|
|
143
165
|
query: z.string(),
|
|
144
166
|
limit: z.number().optional(),
|
|
145
|
-
filter: z.record(z.any()).optional(),
|
|
167
|
+
filter: z.record(z.string(), z.any()).optional(),
|
|
146
168
|
});
|
|
147
169
|
|
|
148
170
|
const minimalInput = {
|
|
149
|
-
collection:
|
|
150
|
-
query:
|
|
171
|
+
collection: "test",
|
|
172
|
+
query: "search",
|
|
151
173
|
};
|
|
152
174
|
|
|
153
175
|
expect(() => SemanticSearchSchema.parse(minimalInput)).not.toThrow();
|
|
154
176
|
});
|
|
155
177
|
|
|
156
|
-
it(
|
|
157
|
-
const { z } = await import(
|
|
178
|
+
it("should require collection and query", async () => {
|
|
179
|
+
const { z } = await import("zod");
|
|
158
180
|
|
|
159
181
|
const SemanticSearchSchema = z.object({
|
|
160
182
|
collection: z.string(),
|
|
161
183
|
query: z.string(),
|
|
162
184
|
limit: z.number().optional(),
|
|
163
|
-
filter: z.record(z.any()).optional(),
|
|
185
|
+
filter: z.record(z.string(), z.any()).optional(),
|
|
164
186
|
});
|
|
165
187
|
|
|
166
|
-
const missingQuery = { collection:
|
|
188
|
+
const missingQuery = { collection: "test", limit: 5 };
|
|
167
189
|
expect(() => SemanticSearchSchema.parse(missingQuery)).toThrow();
|
|
168
190
|
|
|
169
|
-
const missingCollection = { query:
|
|
191
|
+
const missingCollection = { query: "test", limit: 5 };
|
|
170
192
|
expect(() => SemanticSearchSchema.parse(missingCollection)).toThrow();
|
|
171
193
|
});
|
|
172
194
|
});
|
|
173
195
|
|
|
174
|
-
describe(
|
|
175
|
-
it(
|
|
176
|
-
const { z } = await import(
|
|
196
|
+
describe("DeleteCollectionSchema", () => {
|
|
197
|
+
it("should validate correct delete input", async () => {
|
|
198
|
+
const { z } = await import("zod");
|
|
177
199
|
|
|
178
200
|
const DeleteCollectionSchema = z.object({
|
|
179
201
|
name: z.string(),
|
|
180
202
|
});
|
|
181
203
|
|
|
182
|
-
const validInput = { name:
|
|
204
|
+
const validInput = { name: "test-collection" };
|
|
183
205
|
expect(() => DeleteCollectionSchema.parse(validInput)).not.toThrow();
|
|
184
206
|
});
|
|
185
207
|
|
|
186
|
-
it(
|
|
187
|
-
const { z } = await import(
|
|
208
|
+
it("should require name field", async () => {
|
|
209
|
+
const { z } = await import("zod");
|
|
188
210
|
|
|
189
211
|
const DeleteCollectionSchema = z.object({
|
|
190
212
|
name: z.string(),
|
|
@@ -194,22 +216,22 @@ describe('MCP Server Tool Schemas', () => {
|
|
|
194
216
|
});
|
|
195
217
|
});
|
|
196
218
|
|
|
197
|
-
describe(
|
|
198
|
-
it(
|
|
199
|
-
const { z } = await import(
|
|
219
|
+
describe("GetCollectionInfoSchema", () => {
|
|
220
|
+
it("should validate correct input", async () => {
|
|
221
|
+
const { z } = await import("zod");
|
|
200
222
|
|
|
201
223
|
const GetCollectionInfoSchema = z.object({
|
|
202
224
|
name: z.string(),
|
|
203
225
|
});
|
|
204
226
|
|
|
205
|
-
const validInput = { name:
|
|
227
|
+
const validInput = { name: "test-collection" };
|
|
206
228
|
expect(() => GetCollectionInfoSchema.parse(validInput)).not.toThrow();
|
|
207
229
|
});
|
|
208
230
|
});
|
|
209
231
|
|
|
210
|
-
describe(
|
|
211
|
-
it(
|
|
212
|
-
const { z } = await import(
|
|
232
|
+
describe("DeleteDocumentsSchema", () => {
|
|
233
|
+
it("should validate correct delete documents input", async () => {
|
|
234
|
+
const { z } = await import("zod");
|
|
213
235
|
|
|
214
236
|
const DeleteDocumentsSchema = z.object({
|
|
215
237
|
collection: z.string(),
|
|
@@ -217,40 +239,40 @@ describe('MCP Server Tool Schemas', () => {
|
|
|
217
239
|
});
|
|
218
240
|
|
|
219
241
|
const validInput = {
|
|
220
|
-
collection:
|
|
221
|
-
ids: [1,
|
|
242
|
+
collection: "test-collection",
|
|
243
|
+
ids: [1, "doc-2", 3],
|
|
222
244
|
};
|
|
223
245
|
|
|
224
246
|
expect(() => DeleteDocumentsSchema.parse(validInput)).not.toThrow();
|
|
225
247
|
});
|
|
226
248
|
|
|
227
|
-
it(
|
|
228
|
-
const { z } = await import(
|
|
249
|
+
it("should accept string and number IDs", async () => {
|
|
250
|
+
const { z } = await import("zod");
|
|
229
251
|
|
|
230
252
|
const DeleteDocumentsSchema = z.object({
|
|
231
253
|
collection: z.string(),
|
|
232
254
|
ids: z.array(z.union([z.string(), z.number()])),
|
|
233
255
|
});
|
|
234
256
|
|
|
235
|
-
const stringIds = { collection:
|
|
257
|
+
const stringIds = { collection: "test", ids: ["a", "b", "c"] };
|
|
236
258
|
expect(() => DeleteDocumentsSchema.parse(stringIds)).not.toThrow();
|
|
237
259
|
|
|
238
|
-
const numberIds = { collection:
|
|
260
|
+
const numberIds = { collection: "test", ids: [1, 2, 3] };
|
|
239
261
|
expect(() => DeleteDocumentsSchema.parse(numberIds)).not.toThrow();
|
|
240
262
|
|
|
241
|
-
const mixedIds = { collection:
|
|
263
|
+
const mixedIds = { collection: "test", ids: [1, "b", 3] };
|
|
242
264
|
expect(() => DeleteDocumentsSchema.parse(mixedIds)).not.toThrow();
|
|
243
265
|
});
|
|
244
266
|
|
|
245
|
-
it(
|
|
246
|
-
const { z } = await import(
|
|
267
|
+
it("should require both collection and ids", async () => {
|
|
268
|
+
const { z } = await import("zod");
|
|
247
269
|
|
|
248
270
|
const DeleteDocumentsSchema = z.object({
|
|
249
271
|
collection: z.string(),
|
|
250
272
|
ids: z.array(z.union([z.string(), z.number()])),
|
|
251
273
|
});
|
|
252
274
|
|
|
253
|
-
const missingIds = { collection:
|
|
275
|
+
const missingIds = { collection: "test" };
|
|
254
276
|
expect(() => DeleteDocumentsSchema.parse(missingIds)).toThrow();
|
|
255
277
|
|
|
256
278
|
const missingCollection = { ids: [1, 2, 3] };
|
|
@@ -259,25 +281,25 @@ describe('MCP Server Tool Schemas', () => {
|
|
|
259
281
|
});
|
|
260
282
|
});
|
|
261
283
|
|
|
262
|
-
describe(
|
|
263
|
-
it(
|
|
264
|
-
const collectionsUri =
|
|
284
|
+
describe("MCP Server Resource URIs", () => {
|
|
285
|
+
it("should match collections URI pattern", () => {
|
|
286
|
+
const collectionsUri = "qdrant://collections";
|
|
265
287
|
expect(collectionsUri).toMatch(/^qdrant:\/\/collections$/);
|
|
266
288
|
});
|
|
267
289
|
|
|
268
|
-
it(
|
|
269
|
-
const collectionUri =
|
|
290
|
+
it("should match collection detail URI pattern", () => {
|
|
291
|
+
const collectionUri = "qdrant://collection/my-collection";
|
|
270
292
|
const match = collectionUri.match(/^qdrant:\/\/collection\/(.+)$/);
|
|
271
293
|
|
|
272
294
|
expect(match).not.toBeNull();
|
|
273
|
-
expect(match![1]).toBe(
|
|
295
|
+
expect(match![1]).toBe("my-collection");
|
|
274
296
|
});
|
|
275
297
|
|
|
276
|
-
it(
|
|
298
|
+
it("should extract collection name from URI", () => {
|
|
277
299
|
const testCases = [
|
|
278
|
-
{ uri:
|
|
279
|
-
{ uri:
|
|
280
|
-
{ uri:
|
|
300
|
+
{ uri: "qdrant://collection/test", expected: "test" },
|
|
301
|
+
{ uri: "qdrant://collection/my-docs", expected: "my-docs" },
|
|
302
|
+
{ uri: "qdrant://collection/collection-123", expected: "collection-123" },
|
|
281
303
|
];
|
|
282
304
|
|
|
283
305
|
testCases.forEach(({ uri, expected }) => {
|
|
@@ -286,12 +308,12 @@ describe('MCP Server Resource URIs', () => {
|
|
|
286
308
|
});
|
|
287
309
|
});
|
|
288
310
|
|
|
289
|
-
it(
|
|
311
|
+
it("should not match invalid URIs", () => {
|
|
290
312
|
const invalidUris = [
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
313
|
+
"qdrant://invalid",
|
|
314
|
+
"http://collections",
|
|
315
|
+
"qdrant://collection/",
|
|
316
|
+
"qdrant:collections",
|
|
295
317
|
];
|
|
296
318
|
|
|
297
319
|
invalidUris.forEach((uri) => {
|