@mhalder/qdrant-mcp-server 2.1.0 → 2.1.2

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 (58) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/CONTRIBUTING.md +14 -2
  3. package/README.md +3 -2
  4. package/build/code/indexer.d.ts +5 -0
  5. package/build/code/indexer.d.ts.map +1 -1
  6. package/build/code/indexer.js +116 -14
  7. package/build/code/indexer.js.map +1 -1
  8. package/build/code/types.d.ts +4 -0
  9. package/build/code/types.d.ts.map +1 -1
  10. package/build/index.js +28 -831
  11. package/build/index.js.map +1 -1
  12. package/build/prompts/register.d.ts +10 -0
  13. package/build/prompts/register.d.ts.map +1 -0
  14. package/build/prompts/register.js +50 -0
  15. package/build/prompts/register.js.map +1 -0
  16. package/build/resources/index.d.ts +10 -0
  17. package/build/resources/index.d.ts.map +1 -0
  18. package/build/resources/index.js +60 -0
  19. package/build/resources/index.js.map +1 -0
  20. package/build/tools/code.d.ts +10 -0
  21. package/build/tools/code.d.ts.map +1 -0
  22. package/build/tools/code.js +132 -0
  23. package/build/tools/code.js.map +1 -0
  24. package/build/tools/collection.d.ts +12 -0
  25. package/build/tools/collection.d.ts.map +1 -0
  26. package/build/tools/collection.js +59 -0
  27. package/build/tools/collection.js.map +1 -0
  28. package/build/tools/document.d.ts +12 -0
  29. package/build/tools/document.d.ts.map +1 -0
  30. package/build/tools/document.js +84 -0
  31. package/build/tools/document.js.map +1 -0
  32. package/build/tools/index.d.ts +18 -0
  33. package/build/tools/index.d.ts.map +1 -0
  34. package/build/tools/index.js +30 -0
  35. package/build/tools/index.js.map +1 -0
  36. package/build/tools/schemas.d.ts +75 -0
  37. package/build/tools/schemas.d.ts.map +1 -0
  38. package/build/tools/schemas.js +114 -0
  39. package/build/tools/schemas.js.map +1 -0
  40. package/build/tools/search.d.ts +12 -0
  41. package/build/tools/search.d.ts.map +1 -0
  42. package/build/tools/search.js +79 -0
  43. package/build/tools/search.js.map +1 -0
  44. package/examples/code-search/README.md +19 -4
  45. package/package.json +1 -1
  46. package/src/code/indexer.ts +186 -38
  47. package/src/code/types.ts +5 -0
  48. package/src/index.ts +26 -983
  49. package/src/prompts/register.ts +71 -0
  50. package/src/resources/index.ts +79 -0
  51. package/src/tools/code.ts +195 -0
  52. package/src/tools/collection.ts +100 -0
  53. package/src/tools/document.ts +113 -0
  54. package/src/tools/index.ts +48 -0
  55. package/src/tools/schemas.ts +130 -0
  56. package/src/tools/search.ts +122 -0
  57. package/tests/code/indexer.test.ts +412 -74
  58. package/tests/code/integration.test.ts +239 -54
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Consolidated Zod schemas for all MCP tools
3
+ *
4
+ * Note: Schemas are exported as plain objects (not wrapped in z.object()) because
5
+ * McpServer.registerTool() expects schemas in this format. The SDK internally
6
+ * converts these to JSON Schema for the MCP protocol. Each property is a Zod
7
+ * field definition that gets composed into the final schema by the SDK.
8
+ */
9
+
10
+ import { z } from "zod";
11
+
12
+ // Collection management schemas
13
+ export const CreateCollectionSchema = {
14
+ name: z.string().describe("Name of the collection"),
15
+ distance: z
16
+ .enum(["Cosine", "Euclid", "Dot"])
17
+ .optional()
18
+ .describe("Distance metric (default: Cosine)"),
19
+ enableHybrid: z
20
+ .boolean()
21
+ .optional()
22
+ .describe("Enable hybrid search with sparse vectors (default: false)"),
23
+ };
24
+
25
+ export const DeleteCollectionSchema = {
26
+ name: z.string().describe("Name of the collection to delete"),
27
+ };
28
+
29
+ export const GetCollectionInfoSchema = {
30
+ name: z.string().describe("Name of the collection"),
31
+ };
32
+
33
+ // Document operation schemas
34
+ export const AddDocumentsSchema = {
35
+ collection: z.string().describe("Name of the collection"),
36
+ documents: z
37
+ .array(
38
+ z.object({
39
+ id: z
40
+ .union([z.string(), z.number()])
41
+ .describe("Unique identifier for the document"),
42
+ text: z.string().describe("Text content to embed and store"),
43
+ metadata: z
44
+ .record(z.any())
45
+ .optional()
46
+ .describe("Optional metadata to store with the document"),
47
+ }),
48
+ )
49
+ .describe("Array of documents to add"),
50
+ };
51
+
52
+ export const DeleteDocumentsSchema = {
53
+ collection: z.string().describe("Name of the collection"),
54
+ ids: z
55
+ .array(z.union([z.string(), z.number()]))
56
+ .describe("Array of document IDs to delete"),
57
+ };
58
+
59
+ // Search schemas
60
+ export const SemanticSearchSchema = {
61
+ collection: z.string().describe("Name of the collection to search"),
62
+ query: z.string().describe("Search query text"),
63
+ limit: z
64
+ .number()
65
+ .optional()
66
+ .describe("Maximum number of results (default: 5)"),
67
+ filter: z.record(z.any()).optional().describe("Optional metadata filter"),
68
+ };
69
+
70
+ export const HybridSearchSchema = {
71
+ collection: z.string().describe("Name of the collection to search"),
72
+ query: z.string().describe("Search query text"),
73
+ limit: z
74
+ .number()
75
+ .optional()
76
+ .describe("Maximum number of results (default: 5)"),
77
+ filter: z.record(z.any()).optional().describe("Optional metadata filter"),
78
+ };
79
+
80
+ // Code indexing schemas
81
+ export const IndexCodebaseSchema = {
82
+ path: z
83
+ .string()
84
+ .describe("Absolute or relative path to codebase root directory"),
85
+ forceReindex: z
86
+ .boolean()
87
+ .optional()
88
+ .describe("Force full re-index even if already indexed (default: false)"),
89
+ extensions: z
90
+ .array(z.string())
91
+ .optional()
92
+ .describe("Custom file extensions to index (e.g., ['.proto', '.graphql'])"),
93
+ ignorePatterns: z
94
+ .array(z.string())
95
+ .optional()
96
+ .describe(
97
+ "Additional patterns to ignore (e.g., ['**/test/**', '**/*.test.ts'])",
98
+ ),
99
+ };
100
+
101
+ export const SearchCodeSchema = {
102
+ path: z.string().describe("Path to codebase (must be indexed first)"),
103
+ query: z
104
+ .string()
105
+ .describe("Natural language search query (e.g., 'authentication logic')"),
106
+ limit: z
107
+ .number()
108
+ .optional()
109
+ .describe("Maximum number of results (default: 5, max: 100)"),
110
+ fileTypes: z
111
+ .array(z.string())
112
+ .optional()
113
+ .describe("Filter by file extensions (e.g., ['.ts', '.py'])"),
114
+ pathPattern: z
115
+ .string()
116
+ .optional()
117
+ .describe("Filter by path glob pattern (e.g., 'src/services/**')"),
118
+ };
119
+
120
+ export const ReindexChangesSchema = {
121
+ path: z.string().describe("Path to codebase"),
122
+ };
123
+
124
+ export const GetIndexStatusSchema = {
125
+ path: z.string().describe("Path to codebase"),
126
+ };
127
+
128
+ export const ClearIndexSchema = {
129
+ path: z.string().describe("Path to codebase"),
130
+ };
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Search 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 SearchToolDependencies {
12
+ qdrant: QdrantManager;
13
+ embeddings: EmbeddingProvider;
14
+ }
15
+
16
+ export function registerSearchTools(
17
+ server: McpServer,
18
+ deps: SearchToolDependencies,
19
+ ): void {
20
+ const { qdrant, embeddings } = deps;
21
+
22
+ // semantic_search
23
+ server.registerTool(
24
+ "semantic_search",
25
+ {
26
+ title: "Semantic Search",
27
+ description:
28
+ "Search for documents using natural language queries. Returns the most semantically similar documents.",
29
+ inputSchema: schemas.SemanticSearchSchema,
30
+ },
31
+ async ({ collection, query, limit, filter }) => {
32
+ // Check if collection exists
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.`,
40
+ },
41
+ ],
42
+ isError: true,
43
+ };
44
+ }
45
+
46
+ // Generate embedding for query
47
+ const { embedding } = await embeddings.embed(query);
48
+
49
+ // Search
50
+ const results = await qdrant.search(
51
+ collection,
52
+ embedding,
53
+ limit || 5,
54
+ filter,
55
+ );
56
+
57
+ return {
58
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
59
+ };
60
+ },
61
+ );
62
+
63
+ // hybrid_search
64
+ server.registerTool(
65
+ "hybrid_search",
66
+ {
67
+ title: "Hybrid Search",
68
+ description:
69
+ "Perform hybrid search combining semantic vector search with keyword search using BM25. This provides better results by combining the strengths of both approaches. The collection must be created with enableHybrid set to true.",
70
+ inputSchema: schemas.HybridSearchSchema,
71
+ },
72
+ async ({ collection, query, limit, filter }) => {
73
+ // Check if collection exists
74
+ const exists = await qdrant.collectionExists(collection);
75
+ if (!exists) {
76
+ return {
77
+ content: [
78
+ {
79
+ type: "text",
80
+ text: `Error: Collection "${collection}" does not exist.`,
81
+ },
82
+ ],
83
+ isError: true,
84
+ };
85
+ }
86
+
87
+ // Check if collection has hybrid search enabled
88
+ const collectionInfo = await qdrant.getCollectionInfo(collection);
89
+ if (!collectionInfo.hybridEnabled) {
90
+ return {
91
+ content: [
92
+ {
93
+ type: "text",
94
+ text: `Error: Collection "${collection}" does not have hybrid search enabled. Create a new collection with enableHybrid set to true.`,
95
+ },
96
+ ],
97
+ isError: true,
98
+ };
99
+ }
100
+
101
+ // Generate dense embedding for query
102
+ const { embedding } = await embeddings.embed(query);
103
+
104
+ // Generate sparse vector for query
105
+ const sparseGenerator = new BM25SparseVectorGenerator();
106
+ const sparseVector = sparseGenerator.generate(query);
107
+
108
+ // Perform hybrid search
109
+ const results = await qdrant.hybridSearch(
110
+ collection,
111
+ embedding,
112
+ sparseVector,
113
+ limit || 5,
114
+ filter,
115
+ );
116
+
117
+ return {
118
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
119
+ };
120
+ },
121
+ );
122
+ }