@mhalder/qdrant-mcp-server 2.0.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/CONTRIBUTING.md +14 -2
  3. package/README.md +9 -8
  4. package/build/index.js +34 -832
  5. package/build/index.js.map +1 -1
  6. package/build/prompts/register.d.ts +10 -0
  7. package/build/prompts/register.d.ts.map +1 -0
  8. package/build/prompts/register.js +50 -0
  9. package/build/prompts/register.js.map +1 -0
  10. package/build/resources/index.d.ts +10 -0
  11. package/build/resources/index.d.ts.map +1 -0
  12. package/build/resources/index.js +60 -0
  13. package/build/resources/index.js.map +1 -0
  14. package/build/tools/code.d.ts +10 -0
  15. package/build/tools/code.d.ts.map +1 -0
  16. package/build/tools/code.js +122 -0
  17. package/build/tools/code.js.map +1 -0
  18. package/build/tools/collection.d.ts +12 -0
  19. package/build/tools/collection.d.ts.map +1 -0
  20. package/build/tools/collection.js +59 -0
  21. package/build/tools/collection.js.map +1 -0
  22. package/build/tools/document.d.ts +12 -0
  23. package/build/tools/document.d.ts.map +1 -0
  24. package/build/tools/document.js +84 -0
  25. package/build/tools/document.js.map +1 -0
  26. package/build/tools/index.d.ts +18 -0
  27. package/build/tools/index.d.ts.map +1 -0
  28. package/build/tools/index.js +30 -0
  29. package/build/tools/index.js.map +1 -0
  30. package/build/tools/schemas.d.ts +75 -0
  31. package/build/tools/schemas.d.ts.map +1 -0
  32. package/build/tools/schemas.js +114 -0
  33. package/build/tools/schemas.js.map +1 -0
  34. package/build/tools/search.d.ts +12 -0
  35. package/build/tools/search.d.ts.map +1 -0
  36. package/build/tools/search.js +79 -0
  37. package/build/tools/search.js.map +1 -0
  38. package/package.json +1 -1
  39. package/src/index.ts +38 -984
  40. package/src/prompts/register.ts +71 -0
  41. package/src/resources/index.ts +79 -0
  42. package/src/tools/code.ts +184 -0
  43. package/src/tools/collection.ts +100 -0
  44. package/src/tools/document.ts +113 -0
  45. package/src/tools/index.ts +48 -0
  46. package/src/tools/schemas.ts +130 -0
  47. package/src/tools/search.ts +122 -0
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Prompt registration module for McpServer
3
+ */
4
+
5
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { z } from "zod";
7
+ import { renderTemplate, validateArguments } from "./template.js";
8
+ import type { PromptArgument, PromptsConfig } from "./types.js";
9
+
10
+ /**
11
+ * Build a Zod schema object for prompt arguments
12
+ */
13
+ function buildArgsSchema(args: PromptArgument[]): Record<string, z.ZodTypeAny> {
14
+ const schema: Record<string, z.ZodTypeAny> = {};
15
+
16
+ for (const arg of args) {
17
+ const fieldSchema = z.string().describe(arg.description);
18
+ schema[arg.name] = arg.required ? fieldSchema : fieldSchema.optional();
19
+ }
20
+
21
+ return schema;
22
+ }
23
+
24
+ /**
25
+ * Register all prompts from configuration on the server
26
+ */
27
+ export function registerAllPrompts(
28
+ server: McpServer,
29
+ config: PromptsConfig | null,
30
+ ): void {
31
+ if (!config) {
32
+ return; // No prompts = no prompts capability
33
+ }
34
+
35
+ for (const prompt of config.prompts) {
36
+ const argsSchema = buildArgsSchema(prompt.arguments);
37
+
38
+ server.registerPrompt(
39
+ prompt.name,
40
+ {
41
+ title: prompt.name,
42
+ description: prompt.description,
43
+ argsSchema,
44
+ },
45
+ (args) => {
46
+ // Validate arguments
47
+ const argsRecord = (args || {}) as Record<string, string>;
48
+ validateArguments(argsRecord, prompt.arguments);
49
+
50
+ // Render template
51
+ const rendered = renderTemplate(
52
+ prompt.template,
53
+ argsRecord,
54
+ prompt.arguments,
55
+ );
56
+
57
+ return {
58
+ messages: [
59
+ {
60
+ role: "user",
61
+ content: {
62
+ type: "text",
63
+ text: rendered.text,
64
+ },
65
+ },
66
+ ],
67
+ };
68
+ },
69
+ );
70
+ }
71
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Resource registration module
3
+ */
4
+
5
+ import {
6
+ McpServer,
7
+ ResourceTemplate,
8
+ } from "@modelcontextprotocol/sdk/server/mcp.js";
9
+ import type { QdrantManager } from "../qdrant/client.js";
10
+
11
+ /**
12
+ * Register all MCP resources on the server
13
+ */
14
+ export function registerAllResources(
15
+ server: McpServer,
16
+ qdrant: QdrantManager,
17
+ ): void {
18
+ // Static resource: list all collections
19
+ server.registerResource(
20
+ "collections",
21
+ "qdrant://collections",
22
+ {
23
+ title: "All Collections",
24
+ description: "List of all vector collections in Qdrant",
25
+ mimeType: "application/json",
26
+ },
27
+ async (uri) => {
28
+ const collections = await qdrant.listCollections();
29
+ return {
30
+ contents: [
31
+ {
32
+ uri: uri.href,
33
+ mimeType: "application/json",
34
+ text: JSON.stringify(collections, null, 2),
35
+ },
36
+ ],
37
+ };
38
+ },
39
+ );
40
+
41
+ // Dynamic resource: individual collection info
42
+ server.registerResource(
43
+ "collection-info",
44
+ new ResourceTemplate("qdrant://collection/{name}", {
45
+ list: async () => {
46
+ const collections = await qdrant.listCollections();
47
+ return {
48
+ resources: collections.map((name) => ({
49
+ uri: `qdrant://collection/${name}`,
50
+ name: `Collection: ${name}`,
51
+ description: `Details and statistics for collection "${name}"`,
52
+ mimeType: "application/json",
53
+ })),
54
+ };
55
+ },
56
+ }),
57
+ {
58
+ title: "Collection Details",
59
+ description: "Detailed information about a specific collection",
60
+ mimeType: "application/json",
61
+ },
62
+ async (uri, params) => {
63
+ const name = params.name;
64
+ if (typeof name !== "string" || !name) {
65
+ throw new Error("Invalid collection name parameter");
66
+ }
67
+ const info = await qdrant.getCollectionInfo(name);
68
+ return {
69
+ contents: [
70
+ {
71
+ uri: uri.href,
72
+ mimeType: "application/json",
73
+ text: JSON.stringify(info, null, 2),
74
+ },
75
+ ],
76
+ };
77
+ },
78
+ );
79
+ }
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Code indexing tools registration
3
+ */
4
+
5
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import type { CodeIndexer } from "../code/indexer.js";
7
+ import * as schemas from "./schemas.js";
8
+
9
+ export interface CodeToolDependencies {
10
+ codeIndexer: CodeIndexer;
11
+ }
12
+
13
+ export function registerCodeTools(
14
+ server: McpServer,
15
+ deps: CodeToolDependencies,
16
+ ): void {
17
+ const { codeIndexer } = deps;
18
+
19
+ // index_codebase
20
+ server.registerTool(
21
+ "index_codebase",
22
+ {
23
+ title: "Index Codebase",
24
+ description:
25
+ "Index a codebase for semantic code search. Automatically discovers files, chunks code intelligently using AST-aware parsing, and stores in vector database. Respects .gitignore and other ignore files.",
26
+ inputSchema: schemas.IndexCodebaseSchema,
27
+ },
28
+ async ({ path, forceReindex, extensions, ignorePatterns }) => {
29
+ const stats = await codeIndexer.indexCodebase(
30
+ path,
31
+ { forceReindex, extensions, ignorePatterns },
32
+ (progress) => {
33
+ // Progress callback - could send progress updates via SSE in future
34
+ console.error(
35
+ `[${progress.phase}] ${progress.percentage}% - ${progress.message}`,
36
+ );
37
+ },
38
+ );
39
+
40
+ let statusMessage = `Indexed ${stats.filesIndexed}/${stats.filesScanned} files (${stats.chunksCreated} chunks) in ${(stats.durationMs / 1000).toFixed(1)}s`;
41
+
42
+ if (stats.status === "partial") {
43
+ statusMessage += `\n\nWarnings:\n${stats.errors?.join("\n")}`;
44
+ } else if (stats.status === "failed") {
45
+ statusMessage = `Indexing failed:\n${stats.errors?.join("\n")}`;
46
+ }
47
+
48
+ return {
49
+ content: [{ type: "text", text: statusMessage }],
50
+ isError: stats.status === "failed",
51
+ };
52
+ },
53
+ );
54
+
55
+ // search_code
56
+ server.registerTool(
57
+ "search_code",
58
+ {
59
+ title: "Search Code",
60
+ description:
61
+ "Search indexed codebase using natural language queries. Returns semantically relevant code chunks with file paths and line numbers.",
62
+ inputSchema: schemas.SearchCodeSchema,
63
+ },
64
+ async ({ path, query, limit, fileTypes, pathPattern }) => {
65
+ const results = await codeIndexer.searchCode(path, query, {
66
+ limit,
67
+ fileTypes,
68
+ pathPattern,
69
+ });
70
+
71
+ if (results.length === 0) {
72
+ return {
73
+ content: [
74
+ { type: "text", text: `No results found for query: "${query}"` },
75
+ ],
76
+ };
77
+ }
78
+
79
+ // Format results with file references
80
+ const formattedResults = results
81
+ .map(
82
+ (r, idx) =>
83
+ `\n--- Result ${idx + 1} (score: ${r.score.toFixed(3)}) ---\n` +
84
+ `File: ${r.filePath}:${r.startLine}-${r.endLine}\n` +
85
+ `Language: ${r.language}\n\n` +
86
+ `${r.content}\n`,
87
+ )
88
+ .join("\n");
89
+
90
+ return {
91
+ content: [
92
+ {
93
+ type: "text",
94
+ text: `Found ${results.length} result(s):\n${formattedResults}`,
95
+ },
96
+ ],
97
+ };
98
+ },
99
+ );
100
+
101
+ // reindex_changes
102
+ server.registerTool(
103
+ "reindex_changes",
104
+ {
105
+ title: "Reindex Changes",
106
+ description:
107
+ "Incrementally re-index only changed files. Detects added, modified, and deleted files since last index. Requires previous indexing with index_codebase.",
108
+ inputSchema: schemas.ReindexChangesSchema,
109
+ },
110
+ async ({ path }) => {
111
+ const stats = await codeIndexer.reindexChanges(path, (progress) => {
112
+ console.error(
113
+ `[${progress.phase}] ${progress.percentage}% - ${progress.message}`,
114
+ );
115
+ });
116
+
117
+ let message = `Incremental re-index complete:\n`;
118
+ message += `- Files added: ${stats.filesAdded}\n`;
119
+ message += `- Files modified: ${stats.filesModified}\n`;
120
+ message += `- Files deleted: ${stats.filesDeleted}\n`;
121
+ message += `- Chunks added: ${stats.chunksAdded}\n`;
122
+ message += `- Duration: ${(stats.durationMs / 1000).toFixed(1)}s`;
123
+
124
+ if (
125
+ stats.filesAdded === 0 &&
126
+ stats.filesModified === 0 &&
127
+ stats.filesDeleted === 0
128
+ ) {
129
+ message = `No changes detected. Codebase is up to date.`;
130
+ }
131
+
132
+ return {
133
+ content: [{ type: "text", text: message }],
134
+ };
135
+ },
136
+ );
137
+
138
+ // get_index_status
139
+ server.registerTool(
140
+ "get_index_status",
141
+ {
142
+ title: "Get Index Status",
143
+ description: "Get indexing status and statistics for a codebase.",
144
+ inputSchema: schemas.GetIndexStatusSchema,
145
+ },
146
+ async ({ path }) => {
147
+ const status = await codeIndexer.getIndexStatus(path);
148
+
149
+ if (!status.isIndexed) {
150
+ return {
151
+ content: [
152
+ {
153
+ type: "text",
154
+ text: `Codebase at "${path}" is not indexed. Use index_codebase to index it first.`,
155
+ },
156
+ ],
157
+ };
158
+ }
159
+
160
+ return {
161
+ content: [{ type: "text", text: JSON.stringify(status, null, 2) }],
162
+ };
163
+ },
164
+ );
165
+
166
+ // clear_index
167
+ server.registerTool(
168
+ "clear_index",
169
+ {
170
+ title: "Clear Index",
171
+ description:
172
+ "Delete all indexed data for a codebase. This is irreversible and will remove the entire collection.",
173
+ inputSchema: schemas.ClearIndexSchema,
174
+ },
175
+ async ({ path }) => {
176
+ await codeIndexer.clearIndex(path);
177
+ return {
178
+ content: [
179
+ { type: "text", text: `Index cleared for codebase at "${path}".` },
180
+ ],
181
+ };
182
+ },
183
+ );
184
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Collection management tools registration
3
+ */
4
+
5
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import type { EmbeddingProvider } from "../embeddings/base.js";
7
+ import type { QdrantManager } from "../qdrant/client.js";
8
+ import * as schemas from "./schemas.js";
9
+
10
+ export interface CollectionToolDependencies {
11
+ qdrant: QdrantManager;
12
+ embeddings: EmbeddingProvider;
13
+ }
14
+
15
+ export function registerCollectionTools(
16
+ server: McpServer,
17
+ deps: CollectionToolDependencies,
18
+ ): void {
19
+ const { qdrant, embeddings } = deps;
20
+
21
+ // create_collection
22
+ server.registerTool(
23
+ "create_collection",
24
+ {
25
+ title: "Create Collection",
26
+ description:
27
+ "Create a new vector collection in Qdrant. The collection will be configured with the embedding provider's dimensions automatically. Set enableHybrid to true to enable hybrid search combining semantic and keyword search.",
28
+ inputSchema: schemas.CreateCollectionSchema,
29
+ },
30
+ async ({ name, distance, enableHybrid }) => {
31
+ const vectorSize = embeddings.getDimensions();
32
+ await qdrant.createCollection(
33
+ name,
34
+ vectorSize,
35
+ distance,
36
+ enableHybrid || false,
37
+ );
38
+
39
+ let message = `Collection "${name}" created successfully with ${vectorSize} dimensions and ${distance || "Cosine"} distance metric.`;
40
+ if (enableHybrid) {
41
+ message += " Hybrid search is enabled for this collection.";
42
+ }
43
+
44
+ return {
45
+ content: [{ type: "text", text: message }],
46
+ };
47
+ },
48
+ );
49
+
50
+ // list_collections
51
+ server.registerTool(
52
+ "list_collections",
53
+ {
54
+ title: "List Collections",
55
+ description: "List all available collections in Qdrant.",
56
+ inputSchema: {},
57
+ },
58
+ async () => {
59
+ const collections = await qdrant.listCollections();
60
+ return {
61
+ content: [{ type: "text", text: JSON.stringify(collections, null, 2) }],
62
+ };
63
+ },
64
+ );
65
+
66
+ // get_collection_info
67
+ server.registerTool(
68
+ "get_collection_info",
69
+ {
70
+ title: "Get Collection Info",
71
+ description:
72
+ "Get detailed information about a collection including vector size, point count, and distance metric.",
73
+ inputSchema: schemas.GetCollectionInfoSchema,
74
+ },
75
+ async ({ name }) => {
76
+ const info = await qdrant.getCollectionInfo(name);
77
+ return {
78
+ content: [{ type: "text", text: JSON.stringify(info, null, 2) }],
79
+ };
80
+ },
81
+ );
82
+
83
+ // delete_collection
84
+ server.registerTool(
85
+ "delete_collection",
86
+ {
87
+ title: "Delete Collection",
88
+ description: "Delete a collection and all its documents.",
89
+ inputSchema: schemas.DeleteCollectionSchema,
90
+ },
91
+ async ({ name }) => {
92
+ await qdrant.deleteCollection(name);
93
+ return {
94
+ content: [
95
+ { type: "text", text: `Collection "${name}" deleted successfully.` },
96
+ ],
97
+ };
98
+ },
99
+ );
100
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Document operation tools registration
3
+ */
4
+
5
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import type { EmbeddingProvider } from "../embeddings/base.js";
7
+ import { BM25SparseVectorGenerator } from "../embeddings/sparse.js";
8
+ import type { QdrantManager } from "../qdrant/client.js";
9
+ import * as schemas from "./schemas.js";
10
+
11
+ export interface DocumentToolDependencies {
12
+ qdrant: QdrantManager;
13
+ embeddings: EmbeddingProvider;
14
+ }
15
+
16
+ export function registerDocumentTools(
17
+ server: McpServer,
18
+ deps: DocumentToolDependencies,
19
+ ): void {
20
+ const { qdrant, embeddings } = deps;
21
+
22
+ // add_documents
23
+ server.registerTool(
24
+ "add_documents",
25
+ {
26
+ title: "Add Documents",
27
+ description:
28
+ "Add documents to a collection. Documents will be automatically embedded using the configured embedding provider.",
29
+ inputSchema: schemas.AddDocumentsSchema,
30
+ },
31
+ async ({ collection, documents }) => {
32
+ // Check if collection exists and get info
33
+ const exists = await qdrant.collectionExists(collection);
34
+ if (!exists) {
35
+ return {
36
+ content: [
37
+ {
38
+ type: "text",
39
+ text: `Error: Collection "${collection}" does not exist. Create it first using create_collection.`,
40
+ },
41
+ ],
42
+ isError: true,
43
+ };
44
+ }
45
+
46
+ const collectionInfo = await qdrant.getCollectionInfo(collection);
47
+
48
+ // Generate embeddings for all documents
49
+ const texts = documents.map((doc) => doc.text);
50
+ const embeddingResults = await embeddings.embedBatch(texts);
51
+
52
+ // If hybrid search is enabled, generate sparse vectors and use appropriate method
53
+ if (collectionInfo.hybridEnabled) {
54
+ const sparseGenerator = new BM25SparseVectorGenerator();
55
+
56
+ // Prepare points with both dense and sparse vectors
57
+ const points = documents.map((doc, index) => ({
58
+ id: doc.id,
59
+ vector: embeddingResults[index].embedding,
60
+ sparseVector: sparseGenerator.generate(doc.text),
61
+ payload: {
62
+ text: doc.text,
63
+ ...doc.metadata,
64
+ },
65
+ }));
66
+
67
+ await qdrant.addPointsWithSparse(collection, points);
68
+ } else {
69
+ // Standard dense-only vectors
70
+ const points = documents.map((doc, index) => ({
71
+ id: doc.id,
72
+ vector: embeddingResults[index].embedding,
73
+ payload: {
74
+ text: doc.text,
75
+ ...doc.metadata,
76
+ },
77
+ }));
78
+
79
+ await qdrant.addPoints(collection, points);
80
+ }
81
+
82
+ return {
83
+ content: [
84
+ {
85
+ type: "text",
86
+ text: `Successfully added ${documents.length} document(s) to collection "${collection}".`,
87
+ },
88
+ ],
89
+ };
90
+ },
91
+ );
92
+
93
+ // delete_documents
94
+ server.registerTool(
95
+ "delete_documents",
96
+ {
97
+ title: "Delete Documents",
98
+ description: "Delete specific documents from a collection by their IDs.",
99
+ inputSchema: schemas.DeleteDocumentsSchema,
100
+ },
101
+ async ({ collection, ids }) => {
102
+ await qdrant.deletePoints(collection, ids);
103
+ return {
104
+ content: [
105
+ {
106
+ type: "text",
107
+ text: `Successfully deleted ${ids.length} document(s) from collection "${collection}".`,
108
+ },
109
+ ],
110
+ };
111
+ },
112
+ );
113
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Tool registration orchestrator
3
+ */
4
+
5
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import type { CodeIndexer } from "../code/indexer.js";
7
+ import type { EmbeddingProvider } from "../embeddings/base.js";
8
+ import type { QdrantManager } from "../qdrant/client.js";
9
+ import { registerCodeTools } from "./code.js";
10
+ import { registerCollectionTools } from "./collection.js";
11
+ import { registerDocumentTools } from "./document.js";
12
+ import { registerSearchTools } from "./search.js";
13
+
14
+ export interface ToolDependencies {
15
+ qdrant: QdrantManager;
16
+ embeddings: EmbeddingProvider;
17
+ codeIndexer: CodeIndexer;
18
+ }
19
+
20
+ /**
21
+ * Register all MCP tools on the server
22
+ */
23
+ export function registerAllTools(
24
+ server: McpServer,
25
+ deps: ToolDependencies,
26
+ ): void {
27
+ registerCollectionTools(server, {
28
+ qdrant: deps.qdrant,
29
+ embeddings: deps.embeddings,
30
+ });
31
+
32
+ registerDocumentTools(server, {
33
+ qdrant: deps.qdrant,
34
+ embeddings: deps.embeddings,
35
+ });
36
+
37
+ registerSearchTools(server, {
38
+ qdrant: deps.qdrant,
39
+ embeddings: deps.embeddings,
40
+ });
41
+
42
+ registerCodeTools(server, {
43
+ codeIndexer: deps.codeIndexer,
44
+ });
45
+ }
46
+
47
+ // Re-export schemas for external use
48
+ export * from "./schemas.js";