@mhalder/qdrant-mcp-server 3.2.1 → 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.
Files changed (124) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +12 -21
  3. package/build/code/chunker/tree-sitter-chunker.d.ts.map +1 -1
  4. package/build/code/chunker/tree-sitter-chunker.js +15 -3
  5. package/build/code/chunker/tree-sitter-chunker.js.map +1 -1
  6. package/build/code/indexer.d.ts +1 -0
  7. package/build/code/indexer.d.ts.map +1 -1
  8. package/build/code/indexer.js +24 -4
  9. package/build/code/indexer.js.map +1 -1
  10. package/build/embeddings/cohere.d.ts +1 -0
  11. package/build/embeddings/cohere.d.ts.map +1 -1
  12. package/build/embeddings/cohere.js +8 -1
  13. package/build/embeddings/cohere.js.map +1 -1
  14. package/build/embeddings/cohere.test.js +11 -0
  15. package/build/embeddings/cohere.test.js.map +1 -1
  16. package/build/embeddings/factory.d.ts.map +1 -1
  17. package/build/embeddings/factory.js +2 -0
  18. package/build/embeddings/factory.js.map +1 -1
  19. package/build/embeddings/factory.test.js +12 -1
  20. package/build/embeddings/factory.test.js.map +1 -1
  21. package/build/embeddings/ollama.d.ts +1 -0
  22. package/build/embeddings/ollama.d.ts.map +1 -1
  23. package/build/embeddings/ollama.js +8 -1
  24. package/build/embeddings/ollama.js.map +1 -1
  25. package/build/embeddings/ollama.test.js +11 -0
  26. package/build/embeddings/ollama.test.js.map +1 -1
  27. package/build/embeddings/openai.d.ts +1 -0
  28. package/build/embeddings/openai.d.ts.map +1 -1
  29. package/build/embeddings/openai.js +8 -1
  30. package/build/embeddings/openai.js.map +1 -1
  31. package/build/embeddings/openai.test.js +11 -0
  32. package/build/embeddings/openai.test.js.map +1 -1
  33. package/build/embeddings/voyage.d.ts +1 -0
  34. package/build/embeddings/voyage.d.ts.map +1 -1
  35. package/build/embeddings/voyage.js +8 -1
  36. package/build/embeddings/voyage.js.map +1 -1
  37. package/build/embeddings/voyage.test.js +11 -0
  38. package/build/embeddings/voyage.test.js.map +1 -1
  39. package/build/git/indexer.d.ts +1 -0
  40. package/build/git/indexer.d.ts.map +1 -1
  41. package/build/git/indexer.js +16 -3
  42. package/build/git/indexer.js.map +1 -1
  43. package/build/git/indexer.test.js +15 -9
  44. package/build/git/indexer.test.js.map +1 -1
  45. package/build/index.js +35 -26
  46. package/build/index.js.map +1 -1
  47. package/build/index.test.js +105 -91
  48. package/build/index.test.js.map +1 -1
  49. package/build/logger.d.ts +4 -0
  50. package/build/logger.d.ts.map +1 -0
  51. package/build/logger.js +24 -0
  52. package/build/logger.js.map +1 -0
  53. package/build/qdrant/client.d.ts +1 -0
  54. package/build/qdrant/client.d.ts.map +1 -1
  55. package/build/qdrant/client.js +10 -0
  56. package/build/qdrant/client.js.map +1 -1
  57. package/build/qdrant/client.test.js +11 -0
  58. package/build/qdrant/client.test.js.map +1 -1
  59. package/build/tools/code.d.ts.map +1 -1
  60. package/build/tools/code.js +44 -13
  61. package/build/tools/code.js.map +1 -1
  62. package/build/tools/collection.d.ts.map +1 -1
  63. package/build/tools/collection.js +15 -8
  64. package/build/tools/collection.js.map +1 -1
  65. package/build/tools/document.d.ts.map +1 -1
  66. package/build/tools/document.js +9 -4
  67. package/build/tools/document.js.map +1 -1
  68. package/build/tools/federated.d.ts.map +1 -1
  69. package/build/tools/federated.js +9 -4
  70. package/build/tools/federated.js.map +1 -1
  71. package/build/tools/federated.test.js +11 -0
  72. package/build/tools/federated.test.js.map +1 -1
  73. package/build/tools/git-history.d.ts.map +1 -1
  74. package/build/tools/git-history.js +44 -12
  75. package/build/tools/git-history.js.map +1 -1
  76. package/build/tools/logging.d.ts +16 -0
  77. package/build/tools/logging.d.ts.map +1 -0
  78. package/build/tools/logging.js +68 -0
  79. package/build/tools/logging.js.map +1 -0
  80. package/build/tools/logging.test.d.ts +2 -0
  81. package/build/tools/logging.test.d.ts.map +1 -0
  82. package/build/tools/logging.test.js +139 -0
  83. package/build/tools/logging.test.js.map +1 -0
  84. package/build/tools/schemas.d.ts +32 -19
  85. package/build/tools/schemas.d.ts.map +1 -1
  86. package/build/tools/schemas.js +9 -3
  87. package/build/tools/schemas.js.map +1 -1
  88. package/build/tools/search.d.ts.map +1 -1
  89. package/build/tools/search.js +13 -4
  90. package/build/tools/search.js.map +1 -1
  91. package/mise.toml +2 -0
  92. package/package.json +14 -13
  93. package/src/code/chunker/tree-sitter-chunker.ts +41 -9
  94. package/src/code/indexer.ts +41 -6
  95. package/src/embeddings/cohere.test.ts +12 -0
  96. package/src/embeddings/cohere.ts +10 -2
  97. package/src/embeddings/factory.test.ts +13 -1
  98. package/src/embeddings/factory.ts +3 -0
  99. package/src/embeddings/ollama.test.ts +12 -0
  100. package/src/embeddings/ollama.ts +10 -2
  101. package/src/embeddings/openai.test.ts +12 -0
  102. package/src/embeddings/openai.ts +10 -2
  103. package/src/embeddings/voyage.test.ts +12 -0
  104. package/src/embeddings/voyage.ts +10 -2
  105. package/src/git/indexer.test.ts +22 -16
  106. package/src/git/indexer.ts +30 -4
  107. package/src/index.test.ts +128 -106
  108. package/src/index.ts +59 -38
  109. package/src/logger.ts +33 -0
  110. package/src/qdrant/client.test.ts +12 -0
  111. package/src/qdrant/client.ts +22 -0
  112. package/src/tools/code.ts +107 -62
  113. package/src/tools/collection.ts +39 -22
  114. package/src/tools/document.ts +52 -22
  115. package/src/tools/federated.test.ts +12 -0
  116. package/src/tools/federated.ts +143 -125
  117. package/src/tools/git-history.ts +117 -60
  118. package/src/tools/logging.test.ts +206 -0
  119. package/src/tools/logging.ts +85 -0
  120. package/src/tools/schemas.ts +9 -3
  121. package/src/tools/search.ts +93 -71
  122. package/tests/code/chunker/tree-sitter-chunker.test.ts +13 -1
  123. package/tests/code/indexer.test.ts +12 -0
  124. package/tests/code/integration.test.ts +14 -1
package/src/index.test.ts CHANGED
@@ -1,190 +1,212 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
-
3
- vi.mock('./qdrant/client.js');
4
- vi.mock('./embeddings/openai.js');
5
-
6
- describe('MCP Server Tool Schemas', () => {
7
- describe('CreateCollectionSchema', () => {
8
- it('should validate correct collection creation input', async () => {
9
- const { z } = await import('zod');
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(['Cosine', 'Euclid', 'Dot']).optional(),
24
+ distance: z.enum(["Cosine", "Euclid", "Dot"]).optional(),
14
25
  });
15
26
 
16
- const validInput = { name: 'test-collection' };
27
+ const validInput = { name: "test-collection" };
17
28
  expect(() => CreateCollectionSchema.parse(validInput)).not.toThrow();
18
29
 
19
- const validInputWithDistance = { name: 'test-collection', distance: 'Cosine' as const };
20
- expect(() => CreateCollectionSchema.parse(validInputWithDistance)).not.toThrow();
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('should reject invalid distance metric', async () => {
24
- const { z } = await import('zod');
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(['Cosine', 'Euclid', 'Dot']).optional(),
44
+ distance: z.enum(["Cosine", "Euclid", "Dot"]).optional(),
29
45
  });
30
46
 
31
- const invalidInput = { name: 'test', distance: 'Invalid' };
47
+ const invalidInput = { name: "test", distance: "Invalid" };
32
48
  expect(() => CreateCollectionSchema.parse(invalidInput)).toThrow();
33
49
  });
34
50
 
35
- it('should require name field', async () => {
36
- const { z } = await import('zod');
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(['Cosine', 'Euclid', 'Dot']).optional(),
56
+ distance: z.enum(["Cosine", "Euclid", "Dot"]).optional(),
41
57
  });
42
58
 
43
- const invalidInput = { distance: 'Cosine' };
59
+ const invalidInput = { distance: "Cosine" };
44
60
  expect(() => CreateCollectionSchema.parse(invalidInput)).toThrow();
45
61
  });
46
62
  });
47
63
 
48
- describe('AddDocumentsSchema', () => {
49
- it('should validate correct document addition input', async () => {
50
- const { z } = await import('zod');
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(z.object({
55
- id: z.union([z.string(), z.number()]),
56
- text: z.string(),
57
- metadata: z.record(z.any()).optional(),
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: 'test-collection',
80
+ collection: "test-collection",
63
81
  documents: [
64
- { id: 1, text: 'test document' },
65
- { id: 'doc-2', text: 'another document', metadata: { type: 'test' } },
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('should accept both string and number IDs', async () => {
73
- const { z } = await import('zod');
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(z.object({
78
- id: z.union([z.string(), z.number()]),
79
- text: z.string(),
80
- metadata: z.record(z.any()).optional(),
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: 'test',
86
- documents: [{ id: 'string-id', text: 'test' }],
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: 'test',
92
- documents: [{ id: 123, text: 'test' }],
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('should require text field in documents', async () => {
98
- const { z } = await import('zod');
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(z.object({
103
- id: z.union([z.string(), z.number()]),
104
- text: z.string(),
105
- metadata: z.record(z.any()).optional(),
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: 'test',
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('SemanticSearchSchema', () => {
118
- it('should validate correct search input', async () => {
119
- const { z } = await import('zod');
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: 'test-collection',
130
- query: 'search query',
151
+ collection: "test-collection",
152
+ query: "search query",
131
153
  limit: 10,
132
- filter: { category: 'test' },
154
+ filter: { category: "test" },
133
155
  };
134
156
 
135
157
  expect(() => SemanticSearchSchema.parse(validInput)).not.toThrow();
136
158
  });
137
159
 
138
- it('should work with minimal input', async () => {
139
- const { z } = await import('zod');
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: 'test',
150
- query: 'search',
171
+ collection: "test",
172
+ query: "search",
151
173
  };
152
174
 
153
175
  expect(() => SemanticSearchSchema.parse(minimalInput)).not.toThrow();
154
176
  });
155
177
 
156
- it('should require collection and query', async () => {
157
- const { z } = await import('zod');
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: 'test', limit: 5 };
188
+ const missingQuery = { collection: "test", limit: 5 };
167
189
  expect(() => SemanticSearchSchema.parse(missingQuery)).toThrow();
168
190
 
169
- const missingCollection = { query: 'test', limit: 5 };
191
+ const missingCollection = { query: "test", limit: 5 };
170
192
  expect(() => SemanticSearchSchema.parse(missingCollection)).toThrow();
171
193
  });
172
194
  });
173
195
 
174
- describe('DeleteCollectionSchema', () => {
175
- it('should validate correct delete input', async () => {
176
- const { z } = await import('zod');
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: 'test-collection' };
204
+ const validInput = { name: "test-collection" };
183
205
  expect(() => DeleteCollectionSchema.parse(validInput)).not.toThrow();
184
206
  });
185
207
 
186
- it('should require name field', async () => {
187
- const { z } = await import('zod');
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('GetCollectionInfoSchema', () => {
198
- it('should validate correct input', async () => {
199
- const { z } = await import('zod');
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: 'test-collection' };
227
+ const validInput = { name: "test-collection" };
206
228
  expect(() => GetCollectionInfoSchema.parse(validInput)).not.toThrow();
207
229
  });
208
230
  });
209
231
 
210
- describe('DeleteDocumentsSchema', () => {
211
- it('should validate correct delete documents input', async () => {
212
- const { z } = await import('zod');
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: 'test-collection',
221
- ids: [1, 'doc-2', 3],
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('should accept string and number IDs', async () => {
228
- const { z } = await import('zod');
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: 'test', ids: ['a', 'b', 'c'] };
257
+ const stringIds = { collection: "test", ids: ["a", "b", "c"] };
236
258
  expect(() => DeleteDocumentsSchema.parse(stringIds)).not.toThrow();
237
259
 
238
- const numberIds = { collection: 'test', ids: [1, 2, 3] };
260
+ const numberIds = { collection: "test", ids: [1, 2, 3] };
239
261
  expect(() => DeleteDocumentsSchema.parse(numberIds)).not.toThrow();
240
262
 
241
- const mixedIds = { collection: 'test', ids: [1, 'b', 3] };
263
+ const mixedIds = { collection: "test", ids: [1, "b", 3] };
242
264
  expect(() => DeleteDocumentsSchema.parse(mixedIds)).not.toThrow();
243
265
  });
244
266
 
245
- it('should require both collection and ids', async () => {
246
- const { z } = await import('zod');
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: 'test' };
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('MCP Server Resource URIs', () => {
263
- it('should match collections URI pattern', () => {
264
- const collectionsUri = 'qdrant://collections';
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('should match collection detail URI pattern', () => {
269
- const collectionUri = 'qdrant://collection/my-collection';
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('my-collection');
295
+ expect(match![1]).toBe("my-collection");
274
296
  });
275
297
 
276
- it('should extract collection name from URI', () => {
298
+ it("should extract collection name from URI", () => {
277
299
  const testCases = [
278
- { uri: 'qdrant://collection/test', expected: 'test' },
279
- { uri: 'qdrant://collection/my-docs', expected: 'my-docs' },
280
- { uri: 'qdrant://collection/collection-123', expected: 'collection-123' },
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('should not match invalid URIs', () => {
311
+ it("should not match invalid URIs", () => {
290
312
  const invalidUris = [
291
- 'qdrant://invalid',
292
- 'http://collections',
293
- 'qdrant://collection/',
294
- 'qdrant:collections',
313
+ "qdrant://invalid",
314
+ "http://collections",
315
+ "qdrant://collection/",
316
+ "qdrant:collections",
295
317
  ];
296
318
 
297
319
  invalidUris.forEach((uri) => {