@mhalder/qdrant-mcp-server 1.1.1 → 1.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/CHANGELOG.md +18 -0
- package/README.md +36 -0
- package/biome.json +34 -0
- package/build/embeddings/sparse.d.ts +40 -0
- package/build/embeddings/sparse.d.ts.map +1 -0
- package/build/embeddings/sparse.js +105 -0
- package/build/embeddings/sparse.js.map +1 -0
- package/build/embeddings/sparse.test.d.ts +2 -0
- package/build/embeddings/sparse.test.d.ts.map +1 -0
- package/build/embeddings/sparse.test.js +69 -0
- package/build/embeddings/sparse.test.js.map +1 -0
- package/build/index.js +333 -32
- package/build/index.js.map +1 -1
- package/build/qdrant/client.d.ts +21 -2
- package/build/qdrant/client.d.ts.map +1 -1
- package/build/qdrant/client.js +131 -17
- package/build/qdrant/client.js.map +1 -1
- package/build/qdrant/client.test.js +429 -21
- package/build/qdrant/client.test.js.map +1 -1
- package/build/transport.test.d.ts +2 -0
- package/build/transport.test.d.ts.map +1 -0
- package/build/transport.test.js +168 -0
- package/build/transport.test.js.map +1 -0
- package/examples/README.md +16 -1
- package/examples/basic/README.md +1 -0
- package/examples/hybrid-search/README.md +236 -0
- package/package.json +3 -1
- package/src/embeddings/sparse.test.ts +87 -0
- package/src/embeddings/sparse.ts +127 -0
- package/src/index.ts +393 -59
- package/src/qdrant/client.test.ts +544 -56
- package/src/qdrant/client.ts +162 -22
- package/src/transport.test.ts +202 -0
- package/vitest.config.ts +3 -3
package/src/qdrant/client.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { QdrantClient } from "@qdrant/js-client-rest";
|
|
3
3
|
|
|
4
4
|
export interface CollectionInfo {
|
|
5
5
|
name: string;
|
|
6
6
|
vectorSize: number;
|
|
7
7
|
pointsCount: number;
|
|
8
|
-
distance:
|
|
8
|
+
distance: "Cosine" | "Euclid" | "Dot";
|
|
9
|
+
hybridEnabled?: boolean;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export interface SearchResult {
|
|
@@ -14,10 +15,15 @@ export interface SearchResult {
|
|
|
14
15
|
payload?: Record<string, any>;
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
export interface SparseVector {
|
|
19
|
+
indices: number[];
|
|
20
|
+
values: number[];
|
|
21
|
+
}
|
|
22
|
+
|
|
17
23
|
export class QdrantManager {
|
|
18
24
|
private client: QdrantClient;
|
|
19
25
|
|
|
20
|
-
constructor(url: string =
|
|
26
|
+
constructor(url: string = "http://localhost:6333") {
|
|
21
27
|
this.client = new QdrantClient({ url });
|
|
22
28
|
}
|
|
23
29
|
|
|
@@ -26,7 +32,7 @@ export class QdrantManager {
|
|
|
26
32
|
* Qdrant requires string IDs to be in UUID format.
|
|
27
33
|
*/
|
|
28
34
|
private normalizeId(id: string | number): string | number {
|
|
29
|
-
if (typeof id ===
|
|
35
|
+
if (typeof id === "number") {
|
|
30
36
|
return id;
|
|
31
37
|
}
|
|
32
38
|
|
|
@@ -37,21 +43,40 @@ export class QdrantManager {
|
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
// Convert arbitrary string to deterministic UUID v5-like format
|
|
40
|
-
const hash = createHash(
|
|
46
|
+
const hash = createHash("sha256").update(id).digest("hex");
|
|
41
47
|
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
async createCollection(
|
|
45
51
|
name: string,
|
|
46
52
|
vectorSize: number,
|
|
47
|
-
distance:
|
|
53
|
+
distance: "Cosine" | "Euclid" | "Dot" = "Cosine",
|
|
54
|
+
enableSparse: boolean = false
|
|
48
55
|
): Promise<void> {
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
const config: any = {};
|
|
57
|
+
|
|
58
|
+
// When hybrid search is enabled, use named vectors
|
|
59
|
+
if (enableSparse) {
|
|
60
|
+
config.vectors = {
|
|
61
|
+
dense: {
|
|
62
|
+
size: vectorSize,
|
|
63
|
+
distance,
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
config.sparse_vectors = {
|
|
67
|
+
text: {
|
|
68
|
+
modifier: "idf",
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
} else {
|
|
72
|
+
// Standard unnamed vector configuration
|
|
73
|
+
config.vectors = {
|
|
51
74
|
size: vectorSize,
|
|
52
75
|
distance,
|
|
53
|
-
}
|
|
54
|
-
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
await this.client.createCollection(name, config);
|
|
55
80
|
}
|
|
56
81
|
|
|
57
82
|
async collectionExists(name: string): Promise<boolean> {
|
|
@@ -74,11 +99,25 @@ export class QdrantManager {
|
|
|
74
99
|
|
|
75
100
|
// Handle both named and unnamed vector configurations
|
|
76
101
|
let size = 0;
|
|
77
|
-
let distance:
|
|
102
|
+
let distance: "Cosine" | "Euclid" | "Dot" = "Cosine";
|
|
103
|
+
let hybridEnabled = false;
|
|
78
104
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
105
|
+
// Check if sparse vectors are configured
|
|
106
|
+
if (info.config.params.sparse_vectors) {
|
|
107
|
+
hybridEnabled = true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (typeof vectorConfig === "object" && vectorConfig !== null) {
|
|
111
|
+
// Check for unnamed vector config (has 'size' directly)
|
|
112
|
+
if ("size" in vectorConfig) {
|
|
113
|
+
size = typeof vectorConfig.size === "number" ? vectorConfig.size : 0;
|
|
114
|
+
distance = vectorConfig.distance as "Cosine" | "Euclid" | "Dot";
|
|
115
|
+
} else if ("dense" in vectorConfig) {
|
|
116
|
+
// Named vector config for hybrid search
|
|
117
|
+
const denseConfig = vectorConfig.dense as any;
|
|
118
|
+
size = typeof denseConfig.size === "number" ? denseConfig.size : 0;
|
|
119
|
+
distance = denseConfig.distance as "Cosine" | "Euclid" | "Dot";
|
|
120
|
+
}
|
|
82
121
|
}
|
|
83
122
|
|
|
84
123
|
return {
|
|
@@ -86,6 +125,7 @@ export class QdrantManager {
|
|
|
86
125
|
vectorSize: size,
|
|
87
126
|
pointsCount: info.points_count || 0,
|
|
88
127
|
distance,
|
|
128
|
+
hybridEnabled,
|
|
89
129
|
};
|
|
90
130
|
}
|
|
91
131
|
|
|
@@ -103,7 +143,7 @@ export class QdrantManager {
|
|
|
103
143
|
): Promise<void> {
|
|
104
144
|
try {
|
|
105
145
|
// Normalize all IDs to ensure string IDs are in UUID format
|
|
106
|
-
const normalizedPoints = points.map(point => ({
|
|
146
|
+
const normalizedPoints = points.map((point) => ({
|
|
107
147
|
...point,
|
|
108
148
|
id: this.normalizeId(point.id),
|
|
109
149
|
}));
|
|
@@ -144,8 +184,11 @@ export class QdrantManager {
|
|
|
144
184
|
}
|
|
145
185
|
}
|
|
146
186
|
|
|
187
|
+
// Check if collection uses named vectors (hybrid mode)
|
|
188
|
+
const collectionInfo = await this.getCollectionInfo(collectionName);
|
|
189
|
+
|
|
147
190
|
const results = await this.client.search(collectionName, {
|
|
148
|
-
vector,
|
|
191
|
+
vector: collectionInfo.hybridEnabled ? { name: "dense", vector } : vector,
|
|
149
192
|
limit,
|
|
150
193
|
filter: qdrantFilter,
|
|
151
194
|
});
|
|
@@ -180,16 +223,113 @@ export class QdrantManager {
|
|
|
180
223
|
}
|
|
181
224
|
}
|
|
182
225
|
|
|
183
|
-
async deletePoints(
|
|
184
|
-
collectionName: string,
|
|
185
|
-
ids: (string | number)[]
|
|
186
|
-
): Promise<void> {
|
|
226
|
+
async deletePoints(collectionName: string, ids: (string | number)[]): Promise<void> {
|
|
187
227
|
// Normalize IDs to ensure string IDs are in UUID format
|
|
188
|
-
const normalizedIds = ids.map(id => this.normalizeId(id));
|
|
228
|
+
const normalizedIds = ids.map((id) => this.normalizeId(id));
|
|
189
229
|
|
|
190
230
|
await this.client.delete(collectionName, {
|
|
191
231
|
wait: true,
|
|
192
232
|
points: normalizedIds,
|
|
193
233
|
});
|
|
194
234
|
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Performs hybrid search combining semantic vector search with sparse vector (keyword) search
|
|
238
|
+
* using Reciprocal Rank Fusion (RRF) to combine results
|
|
239
|
+
*/
|
|
240
|
+
async hybridSearch(
|
|
241
|
+
collectionName: string,
|
|
242
|
+
denseVector: number[],
|
|
243
|
+
sparseVector: SparseVector,
|
|
244
|
+
limit: number = 5,
|
|
245
|
+
filter?: Record<string, any>,
|
|
246
|
+
_semanticWeight: number = 0.7
|
|
247
|
+
): Promise<SearchResult[]> {
|
|
248
|
+
// Convert simple key-value filter to Qdrant filter format
|
|
249
|
+
let qdrantFilter;
|
|
250
|
+
if (filter && Object.keys(filter).length > 0) {
|
|
251
|
+
if (filter.must || filter.should || filter.must_not) {
|
|
252
|
+
qdrantFilter = filter;
|
|
253
|
+
} else {
|
|
254
|
+
qdrantFilter = {
|
|
255
|
+
must: Object.entries(filter).map(([key, value]) => ({
|
|
256
|
+
key,
|
|
257
|
+
match: { value },
|
|
258
|
+
})),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Calculate prefetch limits based on weights
|
|
264
|
+
// We fetch more results than needed to ensure good fusion results
|
|
265
|
+
const prefetchLimit = Math.max(20, limit * 4);
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
const results = await this.client.query(collectionName, {
|
|
269
|
+
prefetch: [
|
|
270
|
+
{
|
|
271
|
+
query: denseVector,
|
|
272
|
+
using: "dense",
|
|
273
|
+
limit: prefetchLimit,
|
|
274
|
+
filter: qdrantFilter,
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
query: sparseVector,
|
|
278
|
+
using: "text",
|
|
279
|
+
limit: prefetchLimit,
|
|
280
|
+
filter: qdrantFilter,
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
query: {
|
|
284
|
+
fusion: "rrf",
|
|
285
|
+
},
|
|
286
|
+
limit: limit,
|
|
287
|
+
with_payload: true,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
return results.points.map((result: any) => ({
|
|
291
|
+
id: result.id,
|
|
292
|
+
score: result.score,
|
|
293
|
+
payload: result.payload || undefined,
|
|
294
|
+
}));
|
|
295
|
+
} catch (error: any) {
|
|
296
|
+
const errorMessage = error?.data?.status?.error || error?.message || String(error);
|
|
297
|
+
throw new Error(`Hybrid search failed on collection "${collectionName}": ${errorMessage}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Adds points with both dense and sparse vectors for hybrid search
|
|
303
|
+
*/
|
|
304
|
+
async addPointsWithSparse(
|
|
305
|
+
collectionName: string,
|
|
306
|
+
points: Array<{
|
|
307
|
+
id: string | number;
|
|
308
|
+
vector: number[];
|
|
309
|
+
sparseVector: SparseVector;
|
|
310
|
+
payload?: Record<string, any>;
|
|
311
|
+
}>
|
|
312
|
+
): Promise<void> {
|
|
313
|
+
try {
|
|
314
|
+
// Normalize all IDs to ensure string IDs are in UUID format
|
|
315
|
+
const normalizedPoints = points.map((point) => ({
|
|
316
|
+
id: this.normalizeId(point.id),
|
|
317
|
+
vector: {
|
|
318
|
+
dense: point.vector,
|
|
319
|
+
text: point.sparseVector,
|
|
320
|
+
},
|
|
321
|
+
payload: point.payload,
|
|
322
|
+
}));
|
|
323
|
+
|
|
324
|
+
await this.client.upsert(collectionName, {
|
|
325
|
+
wait: true,
|
|
326
|
+
points: normalizedPoints,
|
|
327
|
+
});
|
|
328
|
+
} catch (error: any) {
|
|
329
|
+
const errorMessage = error?.data?.status?.error || error?.message || String(error);
|
|
330
|
+
throw new Error(
|
|
331
|
+
`Failed to add points with sparse vectors to collection "${collectionName}": ${errorMessage}`
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
195
335
|
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { Server as HttpServer } from "node:http";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
describe("Transport Configuration", () => {
|
|
5
|
+
let originalEnv: NodeJS.ProcessEnv;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
originalEnv = { ...process.env };
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
process.env = originalEnv;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("HTTP Port Validation", () => {
|
|
16
|
+
it("should accept valid port numbers", () => {
|
|
17
|
+
const validPorts = ["1", "80", "443", "3000", "8080", "65535"];
|
|
18
|
+
|
|
19
|
+
validPorts.forEach((port) => {
|
|
20
|
+
const parsed = parseInt(port, 10);
|
|
21
|
+
expect(parsed).toBeGreaterThanOrEqual(1);
|
|
22
|
+
expect(parsed).toBeLessThanOrEqual(65535);
|
|
23
|
+
expect(Number.isNaN(parsed)).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should reject invalid port numbers", () => {
|
|
28
|
+
const invalidCases = [
|
|
29
|
+
{ port: "0", reason: "port 0 is reserved" },
|
|
30
|
+
{ port: "-1", reason: "negative ports are invalid" },
|
|
31
|
+
{ port: "65536", reason: "exceeds maximum port" },
|
|
32
|
+
{ port: "99999", reason: "exceeds maximum port" },
|
|
33
|
+
{ port: "abc", reason: "non-numeric input" },
|
|
34
|
+
{ port: "", reason: "empty string" },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
invalidCases.forEach(({ port, reason }) => {
|
|
38
|
+
const parsed = parseInt(port, 10);
|
|
39
|
+
const isValid = !Number.isNaN(parsed) && parsed >= 1 && parsed <= 65535;
|
|
40
|
+
expect(isValid, `Failed for ${port}: ${reason}`).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should use default port 3000 when HTTP_PORT is not set", () => {
|
|
45
|
+
const port = parseInt(process.env.HTTP_PORT || "3000", 10);
|
|
46
|
+
expect(port).toBe(3000);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should parse HTTP_PORT from environment", () => {
|
|
50
|
+
process.env.HTTP_PORT = "8080";
|
|
51
|
+
const port = parseInt(process.env.HTTP_PORT || "3000", 10);
|
|
52
|
+
expect(port).toBe(8080);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("Transport Mode Validation", () => {
|
|
57
|
+
it("should accept valid transport modes", () => {
|
|
58
|
+
const validModes = ["stdio", "http", "STDIO", "HTTP"];
|
|
59
|
+
|
|
60
|
+
validModes.forEach((mode) => {
|
|
61
|
+
const normalized = mode.toLowerCase();
|
|
62
|
+
expect(["stdio", "http"]).toContain(normalized);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should reject invalid transport modes", () => {
|
|
67
|
+
const invalidModes = ["tcp", "websocket", "grpc", ""];
|
|
68
|
+
|
|
69
|
+
invalidModes.forEach((mode) => {
|
|
70
|
+
const normalized = mode.toLowerCase();
|
|
71
|
+
expect(["stdio", "http"]).not.toContain(normalized);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should default to stdio when TRANSPORT_MODE is not set", () => {
|
|
76
|
+
const mode = (process.env.TRANSPORT_MODE || "stdio").toLowerCase();
|
|
77
|
+
expect(mode).toBe("stdio");
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("Request Size Limits", () => {
|
|
82
|
+
it("should define request size limit for HTTP transport", () => {
|
|
83
|
+
const limit = "10mb";
|
|
84
|
+
expect(limit).toMatch(/^\d+mb$/);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("should parse size limit correctly", () => {
|
|
88
|
+
const limit = "10mb";
|
|
89
|
+
const sizeInBytes = parseInt(limit, 10) * 1024 * 1024;
|
|
90
|
+
expect(sizeInBytes).toBe(10485760);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("HTTP Server Configuration", () => {
|
|
96
|
+
describe("Graceful Shutdown", () => {
|
|
97
|
+
it("should handle shutdown signals", async () => {
|
|
98
|
+
const mockServer = {
|
|
99
|
+
close: vi.fn((callback) => {
|
|
100
|
+
if (callback) callback();
|
|
101
|
+
}),
|
|
102
|
+
} as unknown as HttpServer;
|
|
103
|
+
|
|
104
|
+
const shutdown = () => {
|
|
105
|
+
mockServer.close(() => {
|
|
106
|
+
// Shutdown callback
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
shutdown();
|
|
111
|
+
expect(mockServer.close).toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should support timeout for forced shutdown", () => {
|
|
115
|
+
vi.useFakeTimers();
|
|
116
|
+
|
|
117
|
+
let forcedShutdown = false;
|
|
118
|
+
const timeout = setTimeout(() => {
|
|
119
|
+
forcedShutdown = true;
|
|
120
|
+
}, 10000);
|
|
121
|
+
|
|
122
|
+
vi.advanceTimersByTime(9999);
|
|
123
|
+
expect(forcedShutdown).toBe(false);
|
|
124
|
+
|
|
125
|
+
vi.advanceTimersByTime(1);
|
|
126
|
+
expect(forcedShutdown).toBe(true);
|
|
127
|
+
|
|
128
|
+
clearTimeout(timeout);
|
|
129
|
+
vi.useRealTimers();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("Error Handling", () => {
|
|
134
|
+
it("should return JSON-RPC 2.0 error format", () => {
|
|
135
|
+
const error = {
|
|
136
|
+
jsonrpc: "2.0",
|
|
137
|
+
error: {
|
|
138
|
+
code: -32603,
|
|
139
|
+
message: "Internal server error",
|
|
140
|
+
},
|
|
141
|
+
id: null,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
expect(error.jsonrpc).toBe("2.0");
|
|
145
|
+
expect(error.error.code).toBe(-32603);
|
|
146
|
+
expect(error.error.message).toBeTruthy();
|
|
147
|
+
expect(error.id).toBeNull();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("should use standard JSON-RPC error codes", () => {
|
|
151
|
+
const errorCodes = {
|
|
152
|
+
parseError: -32700,
|
|
153
|
+
invalidRequest: -32600,
|
|
154
|
+
methodNotFound: -32601,
|
|
155
|
+
invalidParams: -32602,
|
|
156
|
+
internalError: -32603,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
expect(errorCodes.parseError).toBe(-32700);
|
|
160
|
+
expect(errorCodes.invalidRequest).toBe(-32600);
|
|
161
|
+
expect(errorCodes.methodNotFound).toBe(-32601);
|
|
162
|
+
expect(errorCodes.invalidParams).toBe(-32602);
|
|
163
|
+
expect(errorCodes.internalError).toBe(-32603);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe("Transport Lifecycle", () => {
|
|
169
|
+
it("should close transport on response close", () => {
|
|
170
|
+
const mockTransport = {
|
|
171
|
+
close: vi.fn(),
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const mockResponse = {
|
|
175
|
+
on: vi.fn((event, callback) => {
|
|
176
|
+
if (event === "close") {
|
|
177
|
+
callback();
|
|
178
|
+
}
|
|
179
|
+
}),
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
mockResponse.on("close", () => {
|
|
183
|
+
mockTransport.close();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
expect(mockTransport.close).toHaveBeenCalled();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("should handle transport connection", async () => {
|
|
190
|
+
const mockServer = {
|
|
191
|
+
connect: vi.fn().mockResolvedValue(undefined),
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const mockTransport = {
|
|
195
|
+
handleRequest: vi.fn().mockResolvedValue(undefined),
|
|
196
|
+
close: vi.fn(),
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
await mockServer.connect(mockTransport);
|
|
200
|
+
expect(mockServer.connect).toHaveBeenCalledWith(mockTransport);
|
|
201
|
+
});
|
|
202
|
+
});
|
package/vitest.config.ts
CHANGED
|
@@ -14,10 +14,10 @@ export default defineConfig({
|
|
|
14
14
|
"**/*.test.ts",
|
|
15
15
|
"**/*.spec.ts",
|
|
16
16
|
"vitest.config.ts",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
17
|
+
"commitlint.config.js",
|
|
18
|
+
"src/index.ts",
|
|
19
|
+
"scripts/**",
|
|
19
20
|
],
|
|
20
|
-
// Set thresholds for core business logic modules
|
|
21
21
|
thresholds: {
|
|
22
22
|
"src/qdrant/client.ts": {
|
|
23
23
|
lines: 90,
|