@mhalder/qdrant-mcp-server 1.3.1 → 1.5.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 (118) hide show
  1. package/.codecov.yml +16 -0
  2. package/CHANGELOG.md +25 -0
  3. package/README.md +304 -9
  4. package/build/code/chunker/base.d.ts +19 -0
  5. package/build/code/chunker/base.d.ts.map +1 -0
  6. package/build/code/chunker/base.js +5 -0
  7. package/build/code/chunker/base.js.map +1 -0
  8. package/build/code/chunker/character-chunker.d.ts +22 -0
  9. package/build/code/chunker/character-chunker.d.ts.map +1 -0
  10. package/build/code/chunker/character-chunker.js +111 -0
  11. package/build/code/chunker/character-chunker.js.map +1 -0
  12. package/build/code/chunker/tree-sitter-chunker.d.ts +29 -0
  13. package/build/code/chunker/tree-sitter-chunker.d.ts.map +1 -0
  14. package/build/code/chunker/tree-sitter-chunker.js +213 -0
  15. package/build/code/chunker/tree-sitter-chunker.js.map +1 -0
  16. package/build/code/config.d.ts +11 -0
  17. package/build/code/config.d.ts.map +1 -0
  18. package/build/code/config.js +145 -0
  19. package/build/code/config.js.map +1 -0
  20. package/build/code/indexer.d.ts +42 -0
  21. package/build/code/indexer.d.ts.map +1 -0
  22. package/build/code/indexer.js +508 -0
  23. package/build/code/indexer.js.map +1 -0
  24. package/build/code/metadata.d.ts +32 -0
  25. package/build/code/metadata.d.ts.map +1 -0
  26. package/build/code/metadata.js +128 -0
  27. package/build/code/metadata.js.map +1 -0
  28. package/build/code/scanner.d.ts +35 -0
  29. package/build/code/scanner.d.ts.map +1 -0
  30. package/build/code/scanner.js +108 -0
  31. package/build/code/scanner.js.map +1 -0
  32. package/build/code/sync/merkle.d.ts +45 -0
  33. package/build/code/sync/merkle.d.ts.map +1 -0
  34. package/build/code/sync/merkle.js +116 -0
  35. package/build/code/sync/merkle.js.map +1 -0
  36. package/build/code/sync/snapshot.d.ts +41 -0
  37. package/build/code/sync/snapshot.d.ts.map +1 -0
  38. package/build/code/sync/snapshot.js +91 -0
  39. package/build/code/sync/snapshot.js.map +1 -0
  40. package/build/code/sync/synchronizer.d.ts +53 -0
  41. package/build/code/sync/synchronizer.d.ts.map +1 -0
  42. package/build/code/sync/synchronizer.js +132 -0
  43. package/build/code/sync/synchronizer.js.map +1 -0
  44. package/build/code/types.d.ts +98 -0
  45. package/build/code/types.d.ts.map +1 -0
  46. package/build/code/types.js +5 -0
  47. package/build/code/types.js.map +1 -0
  48. package/build/index.js +321 -6
  49. package/build/index.js.map +1 -1
  50. package/build/prompts/index.d.ts +7 -0
  51. package/build/prompts/index.d.ts.map +1 -0
  52. package/build/prompts/index.js +7 -0
  53. package/build/prompts/index.js.map +1 -0
  54. package/build/prompts/index.test.d.ts +2 -0
  55. package/build/prompts/index.test.d.ts.map +1 -0
  56. package/build/prompts/index.test.js +25 -0
  57. package/build/prompts/index.test.js.map +1 -0
  58. package/build/prompts/loader.d.ts +25 -0
  59. package/build/prompts/loader.d.ts.map +1 -0
  60. package/build/prompts/loader.js +81 -0
  61. package/build/prompts/loader.js.map +1 -0
  62. package/build/prompts/loader.test.d.ts +2 -0
  63. package/build/prompts/loader.test.d.ts.map +1 -0
  64. package/build/prompts/loader.test.js +417 -0
  65. package/build/prompts/loader.test.js.map +1 -0
  66. package/build/prompts/template.d.ts +20 -0
  67. package/build/prompts/template.d.ts.map +1 -0
  68. package/build/prompts/template.js +52 -0
  69. package/build/prompts/template.js.map +1 -0
  70. package/build/prompts/template.test.d.ts +2 -0
  71. package/build/prompts/template.test.d.ts.map +1 -0
  72. package/build/prompts/template.test.js +163 -0
  73. package/build/prompts/template.test.js.map +1 -0
  74. package/build/prompts/types.d.ts +34 -0
  75. package/build/prompts/types.d.ts.map +1 -0
  76. package/build/prompts/types.js +5 -0
  77. package/build/prompts/types.js.map +1 -0
  78. package/examples/code-search/README.md +271 -0
  79. package/package.json +13 -1
  80. package/prompts.example.json +96 -0
  81. package/src/code/chunker/base.ts +22 -0
  82. package/src/code/chunker/character-chunker.ts +131 -0
  83. package/src/code/chunker/tree-sitter-chunker.ts +250 -0
  84. package/src/code/config.ts +156 -0
  85. package/src/code/indexer.ts +613 -0
  86. package/src/code/metadata.ts +153 -0
  87. package/src/code/scanner.ts +124 -0
  88. package/src/code/sync/merkle.ts +136 -0
  89. package/src/code/sync/snapshot.ts +110 -0
  90. package/src/code/sync/synchronizer.ts +154 -0
  91. package/src/code/types.ts +117 -0
  92. package/src/index.ts +382 -5
  93. package/src/prompts/index.test.ts +29 -0
  94. package/src/prompts/index.ts +7 -0
  95. package/src/prompts/loader.test.ts +494 -0
  96. package/src/prompts/loader.ts +90 -0
  97. package/src/prompts/template.test.ts +212 -0
  98. package/src/prompts/template.ts +69 -0
  99. package/src/prompts/types.ts +37 -0
  100. package/tests/code/chunker/character-chunker.test.ts +141 -0
  101. package/tests/code/chunker/tree-sitter-chunker.test.ts +275 -0
  102. package/tests/code/fixtures/sample-py/calculator.py +32 -0
  103. package/tests/code/fixtures/sample-ts/async-operations.ts +120 -0
  104. package/tests/code/fixtures/sample-ts/auth.ts +31 -0
  105. package/tests/code/fixtures/sample-ts/config.ts +52 -0
  106. package/tests/code/fixtures/sample-ts/database.ts +50 -0
  107. package/tests/code/fixtures/sample-ts/index.ts +39 -0
  108. package/tests/code/fixtures/sample-ts/types-advanced.ts +132 -0
  109. package/tests/code/fixtures/sample-ts/utils.ts +105 -0
  110. package/tests/code/fixtures/sample-ts/validator.ts +169 -0
  111. package/tests/code/indexer.test.ts +828 -0
  112. package/tests/code/integration.test.ts +708 -0
  113. package/tests/code/metadata.test.ts +457 -0
  114. package/tests/code/scanner.test.ts +131 -0
  115. package/tests/code/sync/merkle.test.ts +406 -0
  116. package/tests/code/sync/snapshot.test.ts +360 -0
  117. package/tests/code/sync/synchronizer.test.ts +501 -0
  118. package/vitest.config.ts +1 -0
@@ -0,0 +1,212 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { renderTemplate, validateArguments } from "./template.js";
3
+ import type { PromptArgument } from "./types.js";
4
+
5
+ describe("renderTemplate", () => {
6
+ it("should render template with provided arguments", () => {
7
+ const template = "Search {{collection}} for {{query}}";
8
+ const args = { collection: "papers", query: "machine learning" };
9
+ const result = renderTemplate(template, args);
10
+
11
+ expect(result.text).toBe("Search papers for machine learning");
12
+ });
13
+
14
+ it("should handle multiple occurrences of the same variable", () => {
15
+ const template = "{{name}} said hello. {{name}} said goodbye.";
16
+ const args = { name: "Alice" };
17
+ const result = renderTemplate(template, args);
18
+
19
+ expect(result.text).toBe("Alice said hello. Alice said goodbye.");
20
+ });
21
+
22
+ it("should use default values for missing arguments", () => {
23
+ const template = "Return {{limit}} results";
24
+ const args = {};
25
+ const definitions: PromptArgument[] = [
26
+ { name: "limit", description: "Number of results", required: false, default: "5" },
27
+ ];
28
+ const result = renderTemplate(template, args, definitions);
29
+
30
+ expect(result.text).toBe("Return 5 results");
31
+ });
32
+
33
+ it("should prefer provided arguments over defaults", () => {
34
+ const template = "Return {{limit}} results";
35
+ const args = { limit: "10" };
36
+ const definitions: PromptArgument[] = [
37
+ { name: "limit", description: "Number of results", required: false, default: "5" },
38
+ ];
39
+ const result = renderTemplate(template, args, definitions);
40
+
41
+ expect(result.text).toBe("Return 10 results");
42
+ });
43
+
44
+ it("should keep placeholder if no value and no default", () => {
45
+ const template = "Search {{collection}} for {{query}}";
46
+ const args = { collection: "papers" };
47
+ const result = renderTemplate(template, args);
48
+
49
+ expect(result.text).toBe("Search papers for {{query}}");
50
+ });
51
+
52
+ it("should handle templates with no placeholders", () => {
53
+ const template = "This is a plain text template";
54
+ const args = { foo: "bar" };
55
+ const result = renderTemplate(template, args);
56
+
57
+ expect(result.text).toBe("This is a plain text template");
58
+ });
59
+
60
+ it("should handle empty template", () => {
61
+ const template = "";
62
+ const args = { foo: "bar" };
63
+ const result = renderTemplate(template, args);
64
+
65
+ expect(result.text).toBe("");
66
+ });
67
+
68
+ it("should handle empty args", () => {
69
+ const template = "Search {{collection}}";
70
+ const result = renderTemplate(template);
71
+
72
+ expect(result.text).toBe("Search {{collection}}");
73
+ });
74
+
75
+ it("should handle complex template with multiple variables and defaults", () => {
76
+ const template =
77
+ "Search the '{{collection}}' collection for documents similar to: '{{query}}'. Return the top {{limit}} most relevant results.";
78
+ const args = { collection: "papers", query: "neural networks" };
79
+ const definitions: PromptArgument[] = [
80
+ { name: "collection", description: "Collection name", required: true },
81
+ { name: "query", description: "Search query", required: true },
82
+ { name: "limit", description: "Number of results", required: false, default: "5" },
83
+ ];
84
+ const result = renderTemplate(template, args, definitions);
85
+
86
+ expect(result.text).toBe(
87
+ "Search the 'papers' collection for documents similar to: 'neural networks'. Return the top 5 most relevant results."
88
+ );
89
+ });
90
+
91
+ it("should handle special characters in argument values", () => {
92
+ const template = "Query: {{query}}";
93
+ const args = { query: "What is $100 + $200?" };
94
+ const result = renderTemplate(template, args);
95
+
96
+ expect(result.text).toBe("Query: What is $100 + $200?");
97
+ });
98
+
99
+ it("should handle numeric-like values as strings", () => {
100
+ const template = "ID: {{id}}, Count: {{count}}";
101
+ const args = { id: "123", count: "456" };
102
+ const result = renderTemplate(template, args);
103
+
104
+ expect(result.text).toBe("ID: 123, Count: 456");
105
+ });
106
+
107
+ it("should handle whitespace in variable names", () => {
108
+ const template = "Value: {{key}}";
109
+ const args = { key: " spaced " };
110
+ const result = renderTemplate(template, args);
111
+
112
+ expect(result.text).toBe("Value: spaced ");
113
+ });
114
+
115
+ it("should not match invalid placeholder patterns", () => {
116
+ const template = "{{ invalid }} {missing} {{also invalid}} {{valid}}";
117
+ const args = { valid: "yes" };
118
+ const result = renderTemplate(template, args);
119
+
120
+ // Only {{valid}} should be replaced, others don't match \w+ pattern
121
+ expect(result.text).toBe("{{ invalid }} {missing} {{also invalid}} yes");
122
+ });
123
+ });
124
+
125
+ describe("validateArguments", () => {
126
+ it("should pass validation when all required arguments are provided", () => {
127
+ const args = { collection: "papers", query: "test" };
128
+ const definitions: PromptArgument[] = [
129
+ { name: "collection", description: "Collection name", required: true },
130
+ { name: "query", description: "Search query", required: true },
131
+ ];
132
+
133
+ expect(() => validateArguments(args, definitions)).not.toThrow();
134
+ });
135
+
136
+ it("should throw error when required argument is missing", () => {
137
+ const args = { collection: "papers" };
138
+ const definitions: PromptArgument[] = [
139
+ { name: "collection", description: "Collection name", required: true },
140
+ { name: "query", description: "Search query", required: true },
141
+ ];
142
+
143
+ expect(() => validateArguments(args, definitions)).toThrow("Missing required arguments: query");
144
+ });
145
+
146
+ it("should throw error when required argument is empty string", () => {
147
+ const args = { collection: "papers", query: "" };
148
+ const definitions: PromptArgument[] = [
149
+ { name: "collection", description: "Collection name", required: true },
150
+ { name: "query", description: "Search query", required: true },
151
+ ];
152
+
153
+ expect(() => validateArguments(args, definitions)).toThrow("Missing required arguments: query");
154
+ });
155
+
156
+ it("should throw error listing multiple missing arguments", () => {
157
+ const args = {};
158
+ const definitions: PromptArgument[] = [
159
+ { name: "collection", description: "Collection name", required: true },
160
+ { name: "query", description: "Search query", required: true },
161
+ { name: "filter", description: "Filter", required: true },
162
+ ];
163
+
164
+ expect(() => validateArguments(args, definitions)).toThrow(
165
+ "Missing required arguments: collection, query, filter"
166
+ );
167
+ });
168
+
169
+ it("should not throw for missing optional arguments", () => {
170
+ const args = { collection: "papers", query: "test" };
171
+ const definitions: PromptArgument[] = [
172
+ { name: "collection", description: "Collection name", required: true },
173
+ { name: "query", description: "Search query", required: true },
174
+ { name: "limit", description: "Limit", required: false, default: "5" },
175
+ ];
176
+
177
+ expect(() => validateArguments(args, definitions)).not.toThrow();
178
+ });
179
+
180
+ it("should pass validation with empty definitions", () => {
181
+ const args = { foo: "bar" };
182
+ const definitions: PromptArgument[] = [];
183
+
184
+ expect(() => validateArguments(args, definitions)).not.toThrow();
185
+ });
186
+
187
+ it("should pass validation with no definitions", () => {
188
+ const args = { foo: "bar" };
189
+
190
+ expect(() => validateArguments(args, [])).not.toThrow();
191
+ });
192
+
193
+ it("should not require arguments that are not defined", () => {
194
+ const args = { collection: "papers", extra: "data" };
195
+ const definitions: PromptArgument[] = [
196
+ { name: "collection", description: "Collection name", required: true },
197
+ ];
198
+
199
+ expect(() => validateArguments(args, definitions)).not.toThrow();
200
+ });
201
+
202
+ it("should handle mixed required and optional arguments", () => {
203
+ const args = { name: "test" };
204
+ const definitions: PromptArgument[] = [
205
+ { name: "name", description: "Name", required: true },
206
+ { name: "limit", description: "Limit", required: false },
207
+ { name: "filter", description: "Filter", required: false },
208
+ ];
209
+
210
+ expect(() => validateArguments(args, definitions)).not.toThrow();
211
+ });
212
+ });
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Simple template rendering engine for prompts
3
+ */
4
+
5
+ import type { PromptArgument, RenderedPrompt } from "./types.js";
6
+
7
+ /**
8
+ * Render a template string by replacing {{variable}} placeholders with actual values
9
+ * @param template Template string with {{variable}} placeholders
10
+ * @param args Record of argument values
11
+ * @param definitions Argument definitions with defaults
12
+ * @returns Rendered template
13
+ */
14
+ export function renderTemplate(
15
+ template: string,
16
+ args: Record<string, string> = {},
17
+ definitions: PromptArgument[] = []
18
+ ): RenderedPrompt {
19
+ let rendered = template;
20
+
21
+ // Create a map of defaults
22
+ const defaults = new Map<string, string>();
23
+ for (const def of definitions) {
24
+ if (def.default !== undefined) {
25
+ defaults.set(def.name, def.default);
26
+ }
27
+ }
28
+
29
+ // Replace all {{variable}} placeholders
30
+ rendered = rendered.replace(/\{\{(\w+)\}\}/g, (match, varName) => {
31
+ // Check if value is provided in args
32
+ if (args[varName] !== undefined) {
33
+ return args[varName];
34
+ }
35
+
36
+ // Check if there's a default value
37
+ if (defaults.has(varName)) {
38
+ return defaults.get(varName)!;
39
+ }
40
+
41
+ // If no value and no default, keep the placeholder
42
+ return match;
43
+ });
44
+
45
+ return { text: rendered };
46
+ }
47
+
48
+ /**
49
+ * Validate that all required arguments are provided
50
+ * @param args Provided arguments
51
+ * @param definitions Argument definitions
52
+ * @throws Error if required arguments are missing
53
+ */
54
+ export function validateArguments(
55
+ args: Record<string, string>,
56
+ definitions: PromptArgument[]
57
+ ): void {
58
+ const missing: string[] = [];
59
+
60
+ for (const def of definitions) {
61
+ if (def.required && (args[def.name] === undefined || args[def.name] === "")) {
62
+ missing.push(def.name);
63
+ }
64
+ }
65
+
66
+ if (missing.length > 0) {
67
+ throw new Error(`Missing required arguments: ${missing.join(", ")}`);
68
+ }
69
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Configuration types for MCP prompts
3
+ */
4
+
5
+ /**
6
+ * Argument definition for a prompt
7
+ */
8
+ export interface PromptArgument {
9
+ name: string;
10
+ description: string;
11
+ required: boolean;
12
+ default?: string;
13
+ }
14
+
15
+ /**
16
+ * Individual prompt definition
17
+ */
18
+ export interface PromptDefinition {
19
+ name: string;
20
+ description: string;
21
+ arguments: PromptArgument[];
22
+ template: string;
23
+ }
24
+
25
+ /**
26
+ * Root configuration structure
27
+ */
28
+ export interface PromptsConfig {
29
+ prompts: PromptDefinition[];
30
+ }
31
+
32
+ /**
33
+ * Parsed template with resolved arguments
34
+ */
35
+ export interface RenderedPrompt {
36
+ text: string;
37
+ }
@@ -0,0 +1,141 @@
1
+ import { beforeEach, describe, expect, it } from "vitest";
2
+ import { CharacterChunker } from "../../../src/code/chunker/character-chunker.js";
3
+ import type { ChunkerConfig } from "../../../src/code/types.js";
4
+
5
+ describe("CharacterChunker", () => {
6
+ let chunker: CharacterChunker;
7
+ let config: ChunkerConfig;
8
+
9
+ beforeEach(() => {
10
+ config = {
11
+ chunkSize: 100,
12
+ chunkOverlap: 20,
13
+ maxChunkSize: 200,
14
+ };
15
+ chunker = new CharacterChunker(config);
16
+ });
17
+
18
+ describe("chunk", () => {
19
+ it("should chunk small code into single chunk", async () => {
20
+ const code =
21
+ "function hello() {\n console.log('Starting hello function');\n return 'world';\n}";
22
+ const chunks = await chunker.chunk(code, "test.ts", "typescript");
23
+
24
+ expect(chunks).toHaveLength(1);
25
+ expect(chunks[0].content).toContain("hello");
26
+ expect(chunks[0].startLine).toBe(1);
27
+ expect(chunks[0].metadata.language).toBe("typescript");
28
+ });
29
+
30
+ it("should chunk large code into multiple chunks", async () => {
31
+ const code = Array(20)
32
+ .fill("function testFunction() { console.log('This is a test function'); return true; }\n")
33
+ .join("");
34
+ const chunks = await chunker.chunk(code, "test.js", "javascript");
35
+
36
+ expect(chunks.length).toBeGreaterThan(1);
37
+ chunks.forEach((chunk) => {
38
+ expect(chunk.content.length).toBeLessThanOrEqual(config.maxChunkSize);
39
+ });
40
+ });
41
+
42
+ it("should preserve line numbers", async () => {
43
+ const code =
44
+ "This is line 1 with enough content to not be filtered\n" +
45
+ "This is line 2 with enough content to not be filtered\n" +
46
+ "This is line 3 with enough content to not be filtered";
47
+ const chunks = await chunker.chunk(code, "test.txt", "text");
48
+
49
+ expect(chunks.length).toBeGreaterThan(0);
50
+ expect(chunks[0].startLine).toBe(1);
51
+ expect(chunks[0].endLine).toBeGreaterThan(chunks[0].startLine);
52
+ });
53
+
54
+ it("should apply overlap between chunks", async () => {
55
+ const code = Array(20).fill("const x = 1;\n").join("");
56
+ const chunks = await chunker.chunk(code, "test.js", "javascript");
57
+
58
+ if (chunks.length > 1) {
59
+ // Check that there's overlap in content
60
+ expect(chunks.length).toBeGreaterThan(1);
61
+ }
62
+ });
63
+
64
+ it("should find good break points", async () => {
65
+ const code = `function foo() {
66
+ return 1;
67
+ }
68
+
69
+ function bar() {
70
+ return 2;
71
+ }
72
+
73
+ function baz() {
74
+ return 3;
75
+ }`;
76
+
77
+ const chunks = await chunker.chunk(code, "test.js", "javascript");
78
+ // Should try to break at function boundaries
79
+ chunks.forEach((chunk) => {
80
+ expect(chunk.content.length).toBeGreaterThan(0);
81
+ });
82
+ });
83
+
84
+ it("should handle empty code", async () => {
85
+ const code = "";
86
+ const chunks = await chunker.chunk(code, "test.ts", "typescript");
87
+ expect(chunks).toHaveLength(0);
88
+ });
89
+
90
+ it("should handle code with only whitespace", async () => {
91
+ const code = " \n\n\n ";
92
+ const chunks = await chunker.chunk(code, "test.ts", "typescript");
93
+ expect(chunks).toHaveLength(0);
94
+ });
95
+
96
+ it("should skip very small chunks", async () => {
97
+ const code = "x";
98
+ const chunks = await chunker.chunk(code, "test.ts", "typescript");
99
+ expect(chunks).toHaveLength(0);
100
+ });
101
+ });
102
+
103
+ describe("supportsLanguage", () => {
104
+ it("should support all languages", () => {
105
+ expect(chunker.supportsLanguage("typescript")).toBe(true);
106
+ expect(chunker.supportsLanguage("python")).toBe(true);
107
+ expect(chunker.supportsLanguage("unknown")).toBe(true);
108
+ });
109
+ });
110
+
111
+ describe("getStrategyName", () => {
112
+ it("should return correct strategy name", () => {
113
+ expect(chunker.getStrategyName()).toBe("character-based");
114
+ });
115
+ });
116
+
117
+ describe("metadata", () => {
118
+ it("should include correct chunk metadata", async () => {
119
+ const code = "function test() {\n console.log('test function');\n return 1;\n}";
120
+ const chunks = await chunker.chunk(code, "/path/to/file.ts", "typescript");
121
+
122
+ expect(chunks.length).toBeGreaterThan(0);
123
+ expect(chunks[0].metadata).toEqual({
124
+ filePath: "/path/to/file.ts",
125
+ language: "typescript",
126
+ chunkIndex: 0,
127
+ chunkType: "block",
128
+ });
129
+ });
130
+
131
+ it("should increment chunk index", async () => {
132
+ const code = Array(20).fill("function test() {}\n").join("");
133
+ const chunks = await chunker.chunk(code, "test.ts", "typescript");
134
+
135
+ if (chunks.length > 1) {
136
+ expect(chunks[0].metadata.chunkIndex).toBe(0);
137
+ expect(chunks[1].metadata.chunkIndex).toBe(1);
138
+ }
139
+ });
140
+ });
141
+ });