@mhalder/qdrant-mcp-server 1.5.0 → 2.0.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/.github/workflows/ci.yml +5 -16
- package/.github/workflows/claude-code-review.yml +6 -5
- package/.nvmrc +1 -0
- package/.releaserc.json +8 -1
- package/CHANGELOG.md +40 -0
- package/README.md +106 -74
- package/build/embeddings/cohere.test.js +8 -6
- package/build/embeddings/cohere.test.js.map +1 -1
- package/build/embeddings/openai.test.js +12 -8
- package/build/embeddings/openai.test.js.map +1 -1
- package/build/index.js +22 -7
- package/build/index.js.map +1 -1
- package/build/qdrant/client.d.ts +1 -1
- package/build/qdrant/client.d.ts.map +1 -1
- package/build/qdrant/client.js +2 -2
- package/build/qdrant/client.js.map +1 -1
- package/build/qdrant/client.test.js +80 -24
- package/build/qdrant/client.test.js.map +1 -1
- package/compose.yaml +57 -0
- package/examples/README.md +5 -3
- package/package.json +24 -20
- package/src/embeddings/cohere.test.ts +9 -8
- package/src/embeddings/openai.test.ts +13 -10
- package/src/index.ts +134 -51
- package/src/qdrant/client.test.ts +220 -79
- package/src/qdrant/client.ts +27 -16
- package/tsconfig.json +5 -4
- package/vitest.config.ts +1 -0
- package/docker-compose.yml +0 -22
package/src/index.ts
CHANGED
|
@@ -29,26 +29,37 @@ import { CodeIndexer } from "./code/indexer.js";
|
|
|
29
29
|
import type { CodeConfig } from "./code/types.js";
|
|
30
30
|
import { EmbeddingProviderFactory } from "./embeddings/factory.js";
|
|
31
31
|
import { BM25SparseVectorGenerator } from "./embeddings/sparse.js";
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
getPrompt,
|
|
34
|
+
listPrompts,
|
|
35
|
+
loadPromptsConfig,
|
|
36
|
+
type PromptsConfig,
|
|
37
|
+
} from "./prompts/index.js";
|
|
33
38
|
import { renderTemplate, validateArguments } from "./prompts/template.js";
|
|
34
39
|
import { QdrantManager } from "./qdrant/client.js";
|
|
35
40
|
|
|
36
41
|
// Read package.json for version
|
|
37
42
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
38
|
-
const pkg = JSON.parse(
|
|
43
|
+
const pkg = JSON.parse(
|
|
44
|
+
readFileSync(join(__dirname, "../package.json"), "utf-8"),
|
|
45
|
+
);
|
|
39
46
|
|
|
40
47
|
// Validate environment variables
|
|
41
48
|
const QDRANT_URL = process.env.QDRANT_URL || "http://localhost:6333";
|
|
42
|
-
const
|
|
49
|
+
const QDRANT_API_KEY = process.env.QDRANT_API_KEY;
|
|
50
|
+
const EMBEDDING_PROVIDER = (
|
|
51
|
+
process.env.EMBEDDING_PROVIDER || "ollama"
|
|
52
|
+
).toLowerCase();
|
|
43
53
|
const TRANSPORT_MODE = (process.env.TRANSPORT_MODE || "stdio").toLowerCase();
|
|
44
54
|
const HTTP_PORT = parseInt(process.env.HTTP_PORT || "3000", 10);
|
|
45
|
-
const PROMPTS_CONFIG_FILE =
|
|
55
|
+
const PROMPTS_CONFIG_FILE =
|
|
56
|
+
process.env.PROMPTS_CONFIG_FILE || join(__dirname, "../prompts.json");
|
|
46
57
|
|
|
47
58
|
// Validate HTTP_PORT when HTTP mode is selected
|
|
48
59
|
if (TRANSPORT_MODE === "http") {
|
|
49
60
|
if (Number.isNaN(HTTP_PORT) || HTTP_PORT < 1 || HTTP_PORT > 65535) {
|
|
50
61
|
console.error(
|
|
51
|
-
`Error: Invalid HTTP_PORT "${process.env.HTTP_PORT}". Must be a number between 1 and 65535
|
|
62
|
+
`Error: Invalid HTTP_PORT "${process.env.HTTP_PORT}". Must be a number between 1 and 65535.`,
|
|
52
63
|
);
|
|
53
64
|
process.exit(1);
|
|
54
65
|
}
|
|
@@ -74,13 +85,15 @@ if (EMBEDDING_PROVIDER !== "ollama") {
|
|
|
74
85
|
break;
|
|
75
86
|
default:
|
|
76
87
|
console.error(
|
|
77
|
-
`Error: Unknown embedding provider "${EMBEDDING_PROVIDER}". Supported providers: openai, cohere, voyage, ollama
|
|
88
|
+
`Error: Unknown embedding provider "${EMBEDDING_PROVIDER}". Supported providers: openai, cohere, voyage, ollama.`,
|
|
78
89
|
);
|
|
79
90
|
process.exit(1);
|
|
80
91
|
}
|
|
81
92
|
|
|
82
93
|
if (!apiKey) {
|
|
83
|
-
console.error(
|
|
94
|
+
console.error(
|
|
95
|
+
`Error: ${requiredKeyName} is required for ${EMBEDDING_PROVIDER} provider.`,
|
|
96
|
+
);
|
|
84
97
|
process.exit(1);
|
|
85
98
|
}
|
|
86
99
|
}
|
|
@@ -89,7 +102,8 @@ if (EMBEDDING_PROVIDER !== "ollama") {
|
|
|
89
102
|
async function checkOllamaAvailability() {
|
|
90
103
|
if (EMBEDDING_PROVIDER === "ollama") {
|
|
91
104
|
const baseUrl = process.env.EMBEDDING_BASE_URL || "http://localhost:11434";
|
|
92
|
-
const isLocalhost =
|
|
105
|
+
const isLocalhost =
|
|
106
|
+
baseUrl.includes("localhost") || baseUrl.includes("127.0.0.1");
|
|
93
107
|
|
|
94
108
|
try {
|
|
95
109
|
const response = await fetch(`${baseUrl}/api/version`);
|
|
@@ -102,7 +116,7 @@ async function checkOllamaAvailability() {
|
|
|
102
116
|
const { models } = await tagsResponse.json();
|
|
103
117
|
const modelName = process.env.EMBEDDING_MODEL || "nomic-embed-text";
|
|
104
118
|
const modelExists = models.some(
|
|
105
|
-
(m: any) => m.name === modelName || m.name.startsWith(`${modelName}:`)
|
|
119
|
+
(m: any) => m.name === modelName || m.name.startsWith(`${modelName}:`),
|
|
106
120
|
);
|
|
107
121
|
|
|
108
122
|
if (!modelExists) {
|
|
@@ -111,6 +125,7 @@ async function checkOllamaAvailability() {
|
|
|
111
125
|
if (isLocalhost) {
|
|
112
126
|
errorMessage +=
|
|
113
127
|
`Pull it with:\n` +
|
|
128
|
+
` - Using Podman: podman exec ollama ollama pull ${modelName}\n` +
|
|
114
129
|
` - Using Docker: docker exec ollama ollama pull ${modelName}\n` +
|
|
115
130
|
` - Or locally: ollama pull ${modelName}`;
|
|
116
131
|
} else {
|
|
@@ -132,6 +147,7 @@ async function checkOllamaAvailability() {
|
|
|
132
147
|
if (isLocalhost) {
|
|
133
148
|
helpText =
|
|
134
149
|
`Please start Ollama:\n` +
|
|
150
|
+
` - Using Podman: podman compose up -d\n` +
|
|
135
151
|
` - Using Docker: docker compose up -d\n` +
|
|
136
152
|
` - Or install locally: curl -fsSL https://ollama.ai/install.sh | sh\n` +
|
|
137
153
|
`\nThen pull the embedding model:\n` +
|
|
@@ -151,18 +167,30 @@ async function checkOllamaAvailability() {
|
|
|
151
167
|
}
|
|
152
168
|
|
|
153
169
|
// Initialize clients
|
|
154
|
-
const qdrant = new QdrantManager(QDRANT_URL);
|
|
170
|
+
const qdrant = new QdrantManager(QDRANT_URL, QDRANT_API_KEY);
|
|
155
171
|
const embeddings = EmbeddingProviderFactory.createFromEnv();
|
|
156
172
|
|
|
157
173
|
// Initialize code indexer
|
|
158
174
|
const codeConfig: CodeConfig = {
|
|
159
|
-
chunkSize: parseInt(
|
|
160
|
-
|
|
175
|
+
chunkSize: parseInt(
|
|
176
|
+
process.env.CODE_CHUNK_SIZE || String(DEFAULT_CHUNK_SIZE),
|
|
177
|
+
10,
|
|
178
|
+
),
|
|
179
|
+
chunkOverlap: parseInt(
|
|
180
|
+
process.env.CODE_CHUNK_OVERLAP || String(DEFAULT_CHUNK_OVERLAP),
|
|
181
|
+
10,
|
|
182
|
+
),
|
|
161
183
|
enableASTChunking: process.env.CODE_ENABLE_AST !== "false",
|
|
162
184
|
supportedExtensions: DEFAULT_CODE_EXTENSIONS,
|
|
163
185
|
ignorePatterns: DEFAULT_IGNORE_PATTERNS,
|
|
164
|
-
batchSize: parseInt(
|
|
165
|
-
|
|
186
|
+
batchSize: parseInt(
|
|
187
|
+
process.env.CODE_BATCH_SIZE || String(DEFAULT_BATCH_SIZE),
|
|
188
|
+
10,
|
|
189
|
+
),
|
|
190
|
+
defaultSearchLimit: parseInt(
|
|
191
|
+
process.env.CODE_SEARCH_LIMIT || String(DEFAULT_SEARCH_LIMIT),
|
|
192
|
+
10,
|
|
193
|
+
),
|
|
166
194
|
enableHybridSearch: process.env.CODE_ENABLE_HYBRID === "true",
|
|
167
195
|
};
|
|
168
196
|
|
|
@@ -173,9 +201,14 @@ let promptsConfig: PromptsConfig | null = null;
|
|
|
173
201
|
if (existsSync(PROMPTS_CONFIG_FILE)) {
|
|
174
202
|
try {
|
|
175
203
|
promptsConfig = loadPromptsConfig(PROMPTS_CONFIG_FILE);
|
|
176
|
-
console.error(
|
|
204
|
+
console.error(
|
|
205
|
+
`Loaded ${promptsConfig.prompts.length} prompts from ${PROMPTS_CONFIG_FILE}`,
|
|
206
|
+
);
|
|
177
207
|
} catch (error) {
|
|
178
|
-
console.error(
|
|
208
|
+
console.error(
|
|
209
|
+
`Failed to load prompts configuration from ${PROMPTS_CONFIG_FILE}:`,
|
|
210
|
+
error,
|
|
211
|
+
);
|
|
179
212
|
process.exit(1);
|
|
180
213
|
}
|
|
181
214
|
}
|
|
@@ -204,7 +237,7 @@ function createServer() {
|
|
|
204
237
|
},
|
|
205
238
|
{
|
|
206
239
|
capabilities,
|
|
207
|
-
}
|
|
240
|
+
},
|
|
208
241
|
);
|
|
209
242
|
}
|
|
210
243
|
|
|
@@ -235,7 +268,8 @@ function registerHandlers(server: Server) {
|
|
|
235
268
|
},
|
|
236
269
|
enableHybrid: {
|
|
237
270
|
type: "boolean",
|
|
238
|
-
description:
|
|
271
|
+
description:
|
|
272
|
+
"Enable hybrid search with sparse vectors (default: false)",
|
|
239
273
|
},
|
|
240
274
|
},
|
|
241
275
|
required: ["name"],
|
|
@@ -268,7 +302,8 @@ function registerHandlers(server: Server) {
|
|
|
268
302
|
},
|
|
269
303
|
metadata: {
|
|
270
304
|
type: "object",
|
|
271
|
-
description:
|
|
305
|
+
description:
|
|
306
|
+
"Optional metadata to store with the document",
|
|
272
307
|
},
|
|
273
308
|
},
|
|
274
309
|
required: ["id", "text"],
|
|
@@ -344,7 +379,8 @@ function registerHandlers(server: Server) {
|
|
|
344
379
|
},
|
|
345
380
|
{
|
|
346
381
|
name: "delete_documents",
|
|
347
|
-
description:
|
|
382
|
+
description:
|
|
383
|
+
"Delete specific documents from a collection by their IDs.",
|
|
348
384
|
inputSchema: {
|
|
349
385
|
type: "object",
|
|
350
386
|
properties: {
|
|
@@ -399,21 +435,25 @@ function registerHandlers(server: Server) {
|
|
|
399
435
|
properties: {
|
|
400
436
|
path: {
|
|
401
437
|
type: "string",
|
|
402
|
-
description:
|
|
438
|
+
description:
|
|
439
|
+
"Absolute or relative path to codebase root directory",
|
|
403
440
|
},
|
|
404
441
|
forceReindex: {
|
|
405
442
|
type: "boolean",
|
|
406
|
-
description:
|
|
443
|
+
description:
|
|
444
|
+
"Force full re-index even if already indexed (default: false)",
|
|
407
445
|
},
|
|
408
446
|
extensions: {
|
|
409
447
|
type: "array",
|
|
410
448
|
items: { type: "string" },
|
|
411
|
-
description:
|
|
449
|
+
description:
|
|
450
|
+
"Custom file extensions to index (e.g., ['.proto', '.graphql'])",
|
|
412
451
|
},
|
|
413
452
|
ignorePatterns: {
|
|
414
453
|
type: "array",
|
|
415
454
|
items: { type: "string" },
|
|
416
|
-
description:
|
|
455
|
+
description:
|
|
456
|
+
"Additional patterns to ignore (e.g., ['**/test/**', '**/*.test.ts'])",
|
|
417
457
|
},
|
|
418
458
|
},
|
|
419
459
|
required: ["path"],
|
|
@@ -432,7 +472,8 @@ function registerHandlers(server: Server) {
|
|
|
432
472
|
},
|
|
433
473
|
query: {
|
|
434
474
|
type: "string",
|
|
435
|
-
description:
|
|
475
|
+
description:
|
|
476
|
+
"Natural language search query (e.g., 'authentication logic')",
|
|
436
477
|
},
|
|
437
478
|
limit: {
|
|
438
479
|
type: "number",
|
|
@@ -445,7 +486,8 @@ function registerHandlers(server: Server) {
|
|
|
445
486
|
},
|
|
446
487
|
pathPattern: {
|
|
447
488
|
type: "string",
|
|
448
|
-
description:
|
|
489
|
+
description:
|
|
490
|
+
"Filter by path glob pattern (e.g., 'src/services/**')",
|
|
449
491
|
},
|
|
450
492
|
},
|
|
451
493
|
required: ["path", "query"],
|
|
@@ -506,9 +548,15 @@ function registerHandlers(server: Server) {
|
|
|
506
548
|
try {
|
|
507
549
|
switch (name) {
|
|
508
550
|
case "create_collection": {
|
|
509
|
-
const { name, distance, enableHybrid } =
|
|
551
|
+
const { name, distance, enableHybrid } =
|
|
552
|
+
CreateCollectionSchema.parse(args);
|
|
510
553
|
const vectorSize = embeddings.getDimensions();
|
|
511
|
-
await qdrant.createCollection(
|
|
554
|
+
await qdrant.createCollection(
|
|
555
|
+
name,
|
|
556
|
+
vectorSize,
|
|
557
|
+
distance,
|
|
558
|
+
enableHybrid || false,
|
|
559
|
+
);
|
|
512
560
|
|
|
513
561
|
let message = `Collection "${name}" created successfully with ${vectorSize} dimensions and ${distance || "Cosine"} distance metric.`;
|
|
514
562
|
if (enableHybrid) {
|
|
@@ -589,7 +637,8 @@ function registerHandlers(server: Server) {
|
|
|
589
637
|
}
|
|
590
638
|
|
|
591
639
|
case "semantic_search": {
|
|
592
|
-
const { collection, query, limit, filter } =
|
|
640
|
+
const { collection, query, limit, filter } =
|
|
641
|
+
SemanticSearchSchema.parse(args);
|
|
593
642
|
|
|
594
643
|
// Check if collection exists
|
|
595
644
|
const exists = await qdrant.collectionExists(collection);
|
|
@@ -609,7 +658,12 @@ function registerHandlers(server: Server) {
|
|
|
609
658
|
const { embedding } = await embeddings.embed(query);
|
|
610
659
|
|
|
611
660
|
// Search
|
|
612
|
-
const results = await qdrant.search(
|
|
661
|
+
const results = await qdrant.search(
|
|
662
|
+
collection,
|
|
663
|
+
embedding,
|
|
664
|
+
limit || 5,
|
|
665
|
+
filter,
|
|
666
|
+
);
|
|
613
667
|
|
|
614
668
|
return {
|
|
615
669
|
content: [
|
|
@@ -673,7 +727,8 @@ function registerHandlers(server: Server) {
|
|
|
673
727
|
}
|
|
674
728
|
|
|
675
729
|
case "hybrid_search": {
|
|
676
|
-
const { collection, query, limit, filter } =
|
|
730
|
+
const { collection, query, limit, filter } =
|
|
731
|
+
HybridSearchSchema.parse(args);
|
|
677
732
|
|
|
678
733
|
// Check if collection exists
|
|
679
734
|
const exists = await qdrant.collectionExists(collection);
|
|
@@ -716,7 +771,7 @@ function registerHandlers(server: Server) {
|
|
|
716
771
|
embedding,
|
|
717
772
|
sparseVector,
|
|
718
773
|
limit || 5,
|
|
719
|
-
filter
|
|
774
|
+
filter,
|
|
720
775
|
);
|
|
721
776
|
|
|
722
777
|
return {
|
|
@@ -745,8 +800,10 @@ function registerHandlers(server: Server) {
|
|
|
745
800
|
{ forceReindex, extensions, ignorePatterns },
|
|
746
801
|
(progress) => {
|
|
747
802
|
// Progress callback - could send progress updates via SSE in future
|
|
748
|
-
console.error(
|
|
749
|
-
|
|
803
|
+
console.error(
|
|
804
|
+
`[${progress.phase}] ${progress.percentage}% - ${progress.message}`,
|
|
805
|
+
);
|
|
806
|
+
},
|
|
750
807
|
);
|
|
751
808
|
|
|
752
809
|
let statusMessage = `Indexed ${stats.filesIndexed}/${stats.filesScanned} files (${stats.chunksCreated} chunks) in ${(stats.durationMs / 1000).toFixed(1)}s`;
|
|
@@ -777,7 +834,8 @@ function registerHandlers(server: Server) {
|
|
|
777
834
|
pathPattern: z.string().optional(),
|
|
778
835
|
});
|
|
779
836
|
|
|
780
|
-
const { path, query, limit, fileTypes, pathPattern } =
|
|
837
|
+
const { path, query, limit, fileTypes, pathPattern } =
|
|
838
|
+
SearchCodeSchema.parse(args);
|
|
781
839
|
|
|
782
840
|
const results = await codeIndexer.searchCode(path, query, {
|
|
783
841
|
limit,
|
|
@@ -803,7 +861,7 @@ function registerHandlers(server: Server) {
|
|
|
803
861
|
`\n--- Result ${idx + 1} (score: ${r.score.toFixed(3)}) ---\n` +
|
|
804
862
|
`File: ${r.filePath}:${r.startLine}-${r.endLine}\n` +
|
|
805
863
|
`Language: ${r.language}\n\n` +
|
|
806
|
-
`${r.content}\n
|
|
864
|
+
`${r.content}\n`,
|
|
807
865
|
)
|
|
808
866
|
.join("\n");
|
|
809
867
|
|
|
@@ -854,7 +912,9 @@ function registerHandlers(server: Server) {
|
|
|
854
912
|
const { path } = ReindexChangesSchema.parse(args);
|
|
855
913
|
|
|
856
914
|
const stats = await codeIndexer.reindexChanges(path, (progress) => {
|
|
857
|
-
console.error(
|
|
915
|
+
console.error(
|
|
916
|
+
`[${progress.phase}] ${progress.percentage}% - ${progress.message}`,
|
|
917
|
+
);
|
|
858
918
|
});
|
|
859
919
|
|
|
860
920
|
let message = `Incremental re-index complete:\n`;
|
|
@@ -864,7 +924,11 @@ function registerHandlers(server: Server) {
|
|
|
864
924
|
message += `- Chunks added: ${stats.chunksAdded}\n`;
|
|
865
925
|
message += `- Duration: ${(stats.durationMs / 1000).toFixed(1)}s`;
|
|
866
926
|
|
|
867
|
-
if (
|
|
927
|
+
if (
|
|
928
|
+
stats.filesAdded === 0 &&
|
|
929
|
+
stats.filesModified === 0 &&
|
|
930
|
+
stats.filesDeleted === 0
|
|
931
|
+
) {
|
|
868
932
|
message = `No changes detected. Codebase is up to date.`;
|
|
869
933
|
}
|
|
870
934
|
|
|
@@ -909,7 +973,8 @@ function registerHandlers(server: Server) {
|
|
|
909
973
|
}
|
|
910
974
|
} catch (error: any) {
|
|
911
975
|
// Enhanced error details for debugging
|
|
912
|
-
const errorDetails =
|
|
976
|
+
const errorDetails =
|
|
977
|
+
error instanceof Error ? error.message : JSON.stringify(error, null, 2);
|
|
913
978
|
|
|
914
979
|
console.error("Tool execution error:", {
|
|
915
980
|
tool: name,
|
|
@@ -1027,7 +1092,11 @@ function registerHandlers(server: Server) {
|
|
|
1027
1092
|
validateArguments(args || {}, prompt.arguments);
|
|
1028
1093
|
|
|
1029
1094
|
// Render template
|
|
1030
|
-
const rendered = renderTemplate(
|
|
1095
|
+
const rendered = renderTemplate(
|
|
1096
|
+
prompt.template,
|
|
1097
|
+
args || {},
|
|
1098
|
+
prompt.arguments,
|
|
1099
|
+
);
|
|
1031
1100
|
|
|
1032
1101
|
return {
|
|
1033
1102
|
messages: [
|
|
@@ -1042,7 +1111,7 @@ function registerHandlers(server: Server) {
|
|
|
1042
1111
|
};
|
|
1043
1112
|
} catch (error) {
|
|
1044
1113
|
throw new Error(
|
|
1045
|
-
`Failed to render prompt "${name}": ${error instanceof Error ? error.message : String(error)}
|
|
1114
|
+
`Failed to render prompt "${name}": ${error instanceof Error ? error.message : String(error)}`,
|
|
1046
1115
|
);
|
|
1047
1116
|
}
|
|
1048
1117
|
});
|
|
@@ -1070,13 +1139,15 @@ const AddDocumentsSchema = z.object({
|
|
|
1070
1139
|
documents: z
|
|
1071
1140
|
.array(
|
|
1072
1141
|
z.object({
|
|
1073
|
-
id: z
|
|
1142
|
+
id: z
|
|
1143
|
+
.union([z.string(), z.number()])
|
|
1144
|
+
.describe("Unique identifier for the document"),
|
|
1074
1145
|
text: z.string().describe("Text content to embed and store"),
|
|
1075
1146
|
metadata: z
|
|
1076
1147
|
.record(z.any())
|
|
1077
1148
|
.optional()
|
|
1078
1149
|
.describe("Optional metadata to store with the document"),
|
|
1079
|
-
})
|
|
1150
|
+
}),
|
|
1080
1151
|
)
|
|
1081
1152
|
.describe("Array of documents to add"),
|
|
1082
1153
|
});
|
|
@@ -1084,7 +1155,10 @@ const AddDocumentsSchema = z.object({
|
|
|
1084
1155
|
const SemanticSearchSchema = z.object({
|
|
1085
1156
|
collection: z.string().describe("Name of the collection to search"),
|
|
1086
1157
|
query: z.string().describe("Search query text"),
|
|
1087
|
-
limit: z
|
|
1158
|
+
limit: z
|
|
1159
|
+
.number()
|
|
1160
|
+
.optional()
|
|
1161
|
+
.describe("Maximum number of results (default: 5)"),
|
|
1088
1162
|
filter: z.record(z.any()).optional().describe("Optional metadata filter"),
|
|
1089
1163
|
});
|
|
1090
1164
|
|
|
@@ -1098,13 +1172,18 @@ const GetCollectionInfoSchema = z.object({
|
|
|
1098
1172
|
|
|
1099
1173
|
const DeleteDocumentsSchema = z.object({
|
|
1100
1174
|
collection: z.string().describe("Name of the collection"),
|
|
1101
|
-
ids: z
|
|
1175
|
+
ids: z
|
|
1176
|
+
.array(z.union([z.string(), z.number()]))
|
|
1177
|
+
.describe("Array of document IDs to delete"),
|
|
1102
1178
|
});
|
|
1103
1179
|
|
|
1104
1180
|
const HybridSearchSchema = z.object({
|
|
1105
1181
|
collection: z.string().describe("Name of the collection to search"),
|
|
1106
1182
|
query: z.string().describe("Search query text"),
|
|
1107
|
-
limit: z
|
|
1183
|
+
limit: z
|
|
1184
|
+
.number()
|
|
1185
|
+
.optional()
|
|
1186
|
+
.describe("Maximum number of results (default: 5)"),
|
|
1108
1187
|
filter: z.record(z.any()).optional().describe("Optional metadata filter"),
|
|
1109
1188
|
});
|
|
1110
1189
|
|
|
@@ -1147,7 +1226,7 @@ async function startHttpServer() {
|
|
|
1147
1226
|
res: express.Response,
|
|
1148
1227
|
code: number,
|
|
1149
1228
|
message: string,
|
|
1150
|
-
httpStatus: number = 500
|
|
1229
|
+
httpStatus: number = 500,
|
|
1151
1230
|
) => {
|
|
1152
1231
|
if (!res.headersSent) {
|
|
1153
1232
|
res.status(httpStatus).json({
|
|
@@ -1186,7 +1265,7 @@ async function startHttpServer() {
|
|
|
1186
1265
|
const rateLimitMiddleware = async (
|
|
1187
1266
|
req: express.Request,
|
|
1188
1267
|
res: express.Response,
|
|
1189
|
-
next: express.NextFunction
|
|
1268
|
+
next: express.NextFunction,
|
|
1190
1269
|
) => {
|
|
1191
1270
|
const clientIp = req.ip || req.socket.remoteAddress || "unknown";
|
|
1192
1271
|
|
|
@@ -1280,7 +1359,9 @@ async function startHttpServer() {
|
|
|
1280
1359
|
|
|
1281
1360
|
const httpServer = app
|
|
1282
1361
|
.listen(HTTP_PORT, () => {
|
|
1283
|
-
console.error(
|
|
1362
|
+
console.error(
|
|
1363
|
+
`Qdrant MCP server running on http://localhost:${HTTP_PORT}/mcp`,
|
|
1364
|
+
);
|
|
1284
1365
|
})
|
|
1285
1366
|
.on("error", (error) => {
|
|
1286
1367
|
console.error("HTTP server error:", error);
|
|
@@ -1294,7 +1375,9 @@ async function startHttpServer() {
|
|
|
1294
1375
|
if (isShuttingDown) return;
|
|
1295
1376
|
isShuttingDown = true;
|
|
1296
1377
|
|
|
1297
|
-
console.error(
|
|
1378
|
+
console.error(
|
|
1379
|
+
"Shutdown signal received, closing HTTP server gracefully...",
|
|
1380
|
+
);
|
|
1298
1381
|
|
|
1299
1382
|
// Clear the cleanup interval to allow graceful shutdown
|
|
1300
1383
|
clearInterval(cleanupIntervalId);
|
|
@@ -1324,7 +1407,7 @@ async function main() {
|
|
|
1324
1407
|
await startStdioServer();
|
|
1325
1408
|
} else {
|
|
1326
1409
|
console.error(
|
|
1327
|
-
`Error: Invalid TRANSPORT_MODE "${TRANSPORT_MODE}". Supported modes: stdio, http
|
|
1410
|
+
`Error: Invalid TRANSPORT_MODE "${TRANSPORT_MODE}". Supported modes: stdio, http.`,
|
|
1328
1411
|
);
|
|
1329
1412
|
process.exit(1);
|
|
1330
1413
|
}
|