@mhalder/qdrant-mcp-server 3.3.3 → 3.3.4
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 +0 -2
- package/.github/workflows/claude-code-review.yml +1 -1
- package/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/biome.json +3 -2
- package/build/code/chunker/tree-sitter-chunker.d.ts.map +1 -1
- package/build/code/chunker/tree-sitter-chunker.js +2 -12
- package/build/code/chunker/tree-sitter-chunker.js.map +1 -1
- package/build/code/indexer.d.ts.map +1 -1
- package/build/code/indexer.js +12 -18
- package/build/code/indexer.js.map +1 -1
- package/build/code/scanner.js +1 -1
- package/build/code/scanner.js.map +1 -1
- package/build/embeddings/cohere.d.ts +1 -1
- package/build/embeddings/cohere.d.ts.map +1 -1
- package/build/embeddings/cohere.js +2 -2
- package/build/embeddings/cohere.js.map +1 -1
- package/build/embeddings/cohere.test.js +1 -5
- package/build/embeddings/cohere.test.js.map +1 -1
- package/build/embeddings/factory.d.ts +1 -1
- package/build/embeddings/factory.d.ts.map +1 -1
- package/build/embeddings/factory.js +7 -9
- package/build/embeddings/factory.js.map +1 -1
- package/build/embeddings/factory.test.js +3 -3
- package/build/embeddings/factory.test.js.map +1 -1
- package/build/embeddings/ollama.d.ts +1 -1
- package/build/embeddings/ollama.d.ts.map +1 -1
- package/build/embeddings/ollama.js +6 -8
- package/build/embeddings/ollama.js.map +1 -1
- package/build/embeddings/ollama.test.js +2 -6
- package/build/embeddings/ollama.test.js.map +1 -1
- package/build/embeddings/openai.d.ts +1 -1
- package/build/embeddings/openai.d.ts.map +1 -1
- package/build/embeddings/openai.js +4 -7
- package/build/embeddings/openai.js.map +1 -1
- package/build/embeddings/openai.test.js +3 -12
- package/build/embeddings/openai.test.js.map +1 -1
- package/build/embeddings/sparse.test.js +12 -2
- package/build/embeddings/sparse.test.js.map +1 -1
- package/build/embeddings/voyage.d.ts +1 -1
- package/build/embeddings/voyage.d.ts.map +1 -1
- package/build/embeddings/voyage.js +2 -3
- package/build/embeddings/voyage.js.map +1 -1
- package/build/embeddings/voyage.test.js +2 -6
- package/build/embeddings/voyage.test.js.map +1 -1
- package/build/git/chunker.d.ts.map +1 -1
- package/build/git/chunker.js +2 -2
- package/build/git/chunker.js.map +1 -1
- package/build/git/chunker.test.js +1 -1
- package/build/git/chunker.test.js.map +1 -1
- package/build/git/extractor.d.ts.map +1 -1
- package/build/git/extractor.integration.test.js +9 -5
- package/build/git/extractor.integration.test.js.map +1 -1
- package/build/git/extractor.js +1 -1
- package/build/git/extractor.js.map +1 -1
- package/build/git/extractor.test.js +2 -2
- package/build/git/extractor.test.js.map +1 -1
- package/build/git/index.d.ts +4 -4
- package/build/git/index.d.ts.map +1 -1
- package/build/git/index.js +3 -3
- package/build/git/index.js.map +1 -1
- package/build/git/indexer.d.ts.map +1 -1
- package/build/git/indexer.js +9 -21
- package/build/git/indexer.js.map +1 -1
- package/build/git/indexer.test.js +4 -8
- package/build/git/indexer.test.js.map +1 -1
- package/build/git/sync/synchronizer.d.ts.map +1 -1
- package/build/git/sync/synchronizer.js.map +1 -1
- package/build/git/sync/synchronizer.test.js +4 -2
- package/build/git/sync/synchronizer.test.js.map +1 -1
- package/build/index.js +5 -9
- package/build/index.js.map +1 -1
- package/build/index.test.js +3 -3
- package/build/index.test.js.map +1 -1
- package/build/logger.d.ts.map +1 -1
- package/build/logger.js +1 -9
- package/build/logger.js.map +1 -1
- package/build/prompts/register.d.ts.map +1 -1
- package/build/prompts/register.js.map +1 -1
- package/build/qdrant/client.d.ts.map +1 -1
- package/build/qdrant/client.js.map +1 -1
- package/build/qdrant/client.test.js +10 -34
- package/build/qdrant/client.test.js.map +1 -1
- package/build/resources/index.d.ts +1 -1
- package/build/resources/index.d.ts.map +1 -1
- package/build/resources/index.js +1 -1
- package/build/resources/index.js.map +1 -1
- package/build/tools/code.d.ts.map +1 -1
- package/build/tools/code.js +3 -9
- package/build/tools/code.js.map +1 -1
- package/build/tools/collection.d.ts.map +1 -1
- package/build/tools/collection.js +1 -3
- package/build/tools/collection.js.map +1 -1
- package/build/tools/document.d.ts.map +1 -1
- package/build/tools/document.js +1 -1
- package/build/tools/document.js.map +1 -1
- package/build/tools/federated.d.ts.map +1 -1
- package/build/tools/federated.js +15 -6
- package/build/tools/federated.js.map +1 -1
- package/build/tools/federated.test.js +18 -22
- package/build/tools/federated.test.js.map +1 -1
- package/build/tools/git-history.d.ts.map +1 -1
- package/build/tools/git-history.js +3 -7
- package/build/tools/git-history.js.map +1 -1
- package/build/tools/index.d.ts.map +1 -1
- package/build/tools/index.js.map +1 -1
- package/build/tools/logging.d.ts.map +1 -1
- package/build/tools/logging.js +1 -3
- package/build/tools/logging.js.map +1 -1
- package/build/tools/logging.test.js +1 -1
- package/build/tools/logging.test.js.map +1 -1
- package/build/tools/schemas.d.ts.map +1 -1
- package/build/tools/schemas.js +17 -64
- package/build/tools/schemas.js.map +1 -1
- package/build/tools/search.d.ts.map +1 -1
- package/build/tools/search.js +1 -1
- package/build/tools/search.js.map +1 -1
- package/commitlint.config.js +12 -23
- package/package.json +1 -1
- package/scripts/verify-providers.js +12 -32
- package/src/code/chunker/tree-sitter-chunker.ts +9 -35
- package/src/code/indexer.ts +45 -107
- package/src/code/scanner.ts +1 -1
- package/src/embeddings/cohere.test.ts +17 -45
- package/src/embeddings/cohere.ts +10 -17
- package/src/embeddings/factory.test.ts +18 -18
- package/src/embeddings/factory.ts +18 -25
- package/src/embeddings/ollama.test.ts +38 -67
- package/src/embeddings/ollama.ts +15 -27
- package/src/embeddings/openai.test.ts +17 -53
- package/src/embeddings/openai.ts +11 -22
- package/src/embeddings/sparse.test.ts +12 -2
- package/src/embeddings/voyage.test.ts +39 -80
- package/src/embeddings/voyage.ts +9 -13
- package/src/git/chunker.test.ts +1 -1
- package/src/git/chunker.ts +6 -22
- package/src/git/extractor.integration.test.ts +12 -16
- package/src/git/extractor.test.ts +21 -35
- package/src/git/extractor.ts +14 -36
- package/src/git/index.ts +9 -10
- package/src/git/indexer.test.ts +29 -57
- package/src/git/indexer.ts +38 -86
- package/src/git/sync/synchronizer.test.ts +6 -9
- package/src/git/sync/synchronizer.ts +2 -5
- package/src/index.test.ts +7 -9
- package/src/index.ts +34 -80
- package/src/logger.ts +3 -14
- package/src/prompts/register.ts +3 -10
- package/src/qdrant/client.test.ts +63 -169
- package/src/qdrant/client.ts +19 -45
- package/src/resources/index.ts +4 -10
- package/src/tools/code.ts +43 -66
- package/src/tools/collection.ts +19 -38
- package/src/tools/document.ts +10 -19
- package/src/tools/federated.test.ts +34 -57
- package/src/tools/federated.ts +88 -108
- package/src/tools/git-history.ts +32 -60
- package/src/tools/index.ts +1 -4
- package/src/tools/logging.test.ts +10 -10
- package/src/tools/logging.ts +8 -18
- package/src/tools/schemas.ts +23 -78
- package/src/tools/search.ts +77 -94
- package/tests/code/chunker/tree-sitter-chunker.test.ts +6 -19
- package/tests/code/indexer.test.ts +100 -192
- package/tests/code/integration.test.ts +61 -117
- package/tests/code/scanner.test.ts +12 -39
- package/tests/code/sync/snapshot.test.ts +4 -14
- package/tests/code/sync/synchronizer.test.ts +10 -40
package/src/tools/code.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
-
import logger from "../logger.js";
|
|
7
6
|
import type { CodeIndexer } from "../code/indexer.js";
|
|
7
|
+
import logger from "../logger.js";
|
|
8
8
|
import { withToolLogging } from "./logging.js";
|
|
9
9
|
import * as schemas from "./schemas.js";
|
|
10
10
|
|
|
@@ -14,10 +14,7 @@ export interface CodeToolDependencies {
|
|
|
14
14
|
codeIndexer: CodeIndexer;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export function registerCodeTools(
|
|
18
|
-
server: McpServer,
|
|
19
|
-
deps: CodeToolDependencies,
|
|
20
|
-
): void {
|
|
17
|
+
export function registerCodeTools(server: McpServer, deps: CodeToolDependencies): void {
|
|
21
18
|
const { codeIndexer } = deps;
|
|
22
19
|
|
|
23
20
|
// index_codebase
|
|
@@ -39,10 +36,7 @@ export function registerCodeTools(
|
|
|
39
36
|
path,
|
|
40
37
|
{ forceReindex, extensions, ignorePatterns },
|
|
41
38
|
(progress) => {
|
|
42
|
-
log.debug(
|
|
43
|
-
{ phase: progress.phase, percentage: progress.percentage },
|
|
44
|
-
progress.message,
|
|
45
|
-
);
|
|
39
|
+
log.debug({ phase: progress.phase, percentage: progress.percentage }, progress.message);
|
|
46
40
|
if (progressToken !== undefined) {
|
|
47
41
|
extra.sendNotification({
|
|
48
42
|
method: "notifications/progress",
|
|
@@ -54,7 +48,7 @@ export function registerCodeTools(
|
|
|
54
48
|
},
|
|
55
49
|
});
|
|
56
50
|
}
|
|
57
|
-
}
|
|
51
|
+
}
|
|
58
52
|
);
|
|
59
53
|
|
|
60
54
|
let statusMessage = `Indexed ${stats.filesIndexed}/${stats.filesScanned} files (${stats.chunksCreated} chunks) in ${(stats.durationMs / 1000).toFixed(1)}s`;
|
|
@@ -69,8 +63,8 @@ export function registerCodeTools(
|
|
|
69
63
|
content: [{ type: "text", text: statusMessage }],
|
|
70
64
|
isError: stats.status === "failed",
|
|
71
65
|
};
|
|
72
|
-
}
|
|
73
|
-
)
|
|
66
|
+
}
|
|
67
|
+
)
|
|
74
68
|
);
|
|
75
69
|
|
|
76
70
|
// search_code
|
|
@@ -82,48 +76,40 @@ export function registerCodeTools(
|
|
|
82
76
|
"Search indexed codebase using natural language queries. Returns semantically relevant code chunks with file paths and line numbers.",
|
|
83
77
|
inputSchema: schemas.SearchCodeSchema,
|
|
84
78
|
},
|
|
85
|
-
withToolLogging(
|
|
86
|
-
"search_code",
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const results = await codeIndexer.searchCode(path, query, {
|
|
93
|
-
limit,
|
|
94
|
-
fileTypes,
|
|
95
|
-
pathPattern,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
if (results.length === 0) {
|
|
99
|
-
return {
|
|
100
|
-
content: [
|
|
101
|
-
{ type: "text", text: `No results found for query: "${query}"` },
|
|
102
|
-
],
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Format results with file references
|
|
107
|
-
const formattedResults = results
|
|
108
|
-
.map(
|
|
109
|
-
(r, idx) =>
|
|
110
|
-
`\n--- Result ${idx + 1} (score: ${r.score.toFixed(3)}) ---\n` +
|
|
111
|
-
`File: ${r.filePath}:${r.startLine}-${r.endLine}\n` +
|
|
112
|
-
`Language: ${r.language}\n\n` +
|
|
113
|
-
`${r.content}\n`,
|
|
114
|
-
)
|
|
115
|
-
.join("\n");
|
|
79
|
+
withToolLogging("search_code", async ({ path, query, limit, fileTypes, pathPattern }) => {
|
|
80
|
+
log.info({ tool: "search_code", path, query: query.substring(0, 80) }, "Tool called");
|
|
81
|
+
const results = await codeIndexer.searchCode(path, query, {
|
|
82
|
+
limit,
|
|
83
|
+
fileTypes,
|
|
84
|
+
pathPattern,
|
|
85
|
+
});
|
|
116
86
|
|
|
87
|
+
if (results.length === 0) {
|
|
117
88
|
return {
|
|
118
|
-
content: [
|
|
119
|
-
{
|
|
120
|
-
type: "text",
|
|
121
|
-
text: `Found ${results.length} result(s):\n${formattedResults}`,
|
|
122
|
-
},
|
|
123
|
-
],
|
|
89
|
+
content: [{ type: "text", text: `No results found for query: "${query}"` }],
|
|
124
90
|
};
|
|
125
|
-
}
|
|
126
|
-
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Format results with file references
|
|
94
|
+
const formattedResults = results
|
|
95
|
+
.map(
|
|
96
|
+
(r, idx) =>
|
|
97
|
+
`\n--- Result ${idx + 1} (score: ${r.score.toFixed(3)}) ---\n` +
|
|
98
|
+
`File: ${r.filePath}:${r.startLine}-${r.endLine}\n` +
|
|
99
|
+
`Language: ${r.language}\n\n` +
|
|
100
|
+
`${r.content}\n`
|
|
101
|
+
)
|
|
102
|
+
.join("\n");
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
content: [
|
|
106
|
+
{
|
|
107
|
+
type: "text",
|
|
108
|
+
text: `Found ${results.length} result(s):\n${formattedResults}`,
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
};
|
|
112
|
+
})
|
|
127
113
|
);
|
|
128
114
|
|
|
129
115
|
// reindex_changes
|
|
@@ -140,10 +126,7 @@ export function registerCodeTools(
|
|
|
140
126
|
const progressToken = extra._meta?.progressToken;
|
|
141
127
|
|
|
142
128
|
const stats = await codeIndexer.reindexChanges(path, (progress) => {
|
|
143
|
-
log.debug(
|
|
144
|
-
{ phase: progress.phase, percentage: progress.percentage },
|
|
145
|
-
progress.message,
|
|
146
|
-
);
|
|
129
|
+
log.debug({ phase: progress.phase, percentage: progress.percentage }, progress.message);
|
|
147
130
|
if (progressToken !== undefined) {
|
|
148
131
|
extra.sendNotification({
|
|
149
132
|
method: "notifications/progress",
|
|
@@ -164,18 +147,14 @@ export function registerCodeTools(
|
|
|
164
147
|
message += `- Chunks added: ${stats.chunksAdded}\n`;
|
|
165
148
|
message += `- Duration: ${(stats.durationMs / 1000).toFixed(1)}s`;
|
|
166
149
|
|
|
167
|
-
if (
|
|
168
|
-
stats.filesAdded === 0 &&
|
|
169
|
-
stats.filesModified === 0 &&
|
|
170
|
-
stats.filesDeleted === 0
|
|
171
|
-
) {
|
|
150
|
+
if (stats.filesAdded === 0 && stats.filesModified === 0 && stats.filesDeleted === 0) {
|
|
172
151
|
message = `No changes detected. Codebase is up to date.`;
|
|
173
152
|
}
|
|
174
153
|
|
|
175
154
|
return {
|
|
176
155
|
content: [{ type: "text", text: message }],
|
|
177
156
|
};
|
|
178
|
-
})
|
|
157
|
+
})
|
|
179
158
|
);
|
|
180
159
|
|
|
181
160
|
// get_index_status
|
|
@@ -215,7 +194,7 @@ export function registerCodeTools(
|
|
|
215
194
|
return {
|
|
216
195
|
content: [{ type: "text", text: JSON.stringify(status, null, 2) }],
|
|
217
196
|
};
|
|
218
|
-
})
|
|
197
|
+
})
|
|
219
198
|
);
|
|
220
199
|
|
|
221
200
|
// clear_index
|
|
@@ -231,10 +210,8 @@ export function registerCodeTools(
|
|
|
231
210
|
log.info({ tool: "clear_index", path }, "Tool called");
|
|
232
211
|
await codeIndexer.clearIndex(path);
|
|
233
212
|
return {
|
|
234
|
-
content: [
|
|
235
|
-
{ type: "text", text: `Index cleared for codebase at "${path}".` },
|
|
236
|
-
],
|
|
213
|
+
content: [{ type: "text", text: `Index cleared for codebase at "${path}".` }],
|
|
237
214
|
};
|
|
238
|
-
})
|
|
215
|
+
})
|
|
239
216
|
);
|
|
240
217
|
}
|
package/src/tools/collection.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
-
import logger from "../logger.js";
|
|
7
6
|
import type { EmbeddingProvider } from "../embeddings/base.js";
|
|
7
|
+
import logger from "../logger.js";
|
|
8
8
|
import type { QdrantManager } from "../qdrant/client.js";
|
|
9
9
|
import { withToolLogging } from "./logging.js";
|
|
10
10
|
import * as schemas from "./schemas.js";
|
|
@@ -16,10 +16,7 @@ export interface CollectionToolDependencies {
|
|
|
16
16
|
embeddings: EmbeddingProvider;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export function registerCollectionTools(
|
|
20
|
-
server: McpServer,
|
|
21
|
-
deps: CollectionToolDependencies,
|
|
22
|
-
): void {
|
|
19
|
+
export function registerCollectionTools(server: McpServer, deps: CollectionToolDependencies): void {
|
|
23
20
|
const { qdrant, embeddings } = deps;
|
|
24
21
|
|
|
25
22
|
// create_collection
|
|
@@ -31,31 +28,20 @@ export function registerCollectionTools(
|
|
|
31
28
|
"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.",
|
|
32
29
|
inputSchema: schemas.CreateCollectionSchema,
|
|
33
30
|
},
|
|
34
|
-
withToolLogging(
|
|
35
|
-
"create_collection",
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
{ tool: "create_collection", collection: name },
|
|
39
|
-
"Tool called",
|
|
40
|
-
);
|
|
41
|
-
const vectorSize = embeddings.getDimensions();
|
|
42
|
-
await qdrant.createCollection(
|
|
43
|
-
name,
|
|
44
|
-
vectorSize,
|
|
45
|
-
distance,
|
|
46
|
-
enableHybrid || false,
|
|
47
|
-
);
|
|
31
|
+
withToolLogging("create_collection", async ({ name, distance, enableHybrid }) => {
|
|
32
|
+
log.info({ tool: "create_collection", collection: name }, "Tool called");
|
|
33
|
+
const vectorSize = embeddings.getDimensions();
|
|
34
|
+
await qdrant.createCollection(name, vectorSize, distance, enableHybrid || false);
|
|
48
35
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
36
|
+
let message = `Collection "${name}" created successfully with ${vectorSize} dimensions and ${distance || "Cosine"} distance metric.`;
|
|
37
|
+
if (enableHybrid) {
|
|
38
|
+
message += " Hybrid search is enabled for this collection.";
|
|
39
|
+
}
|
|
53
40
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
),
|
|
41
|
+
return {
|
|
42
|
+
content: [{ type: "text", text: message }],
|
|
43
|
+
};
|
|
44
|
+
})
|
|
59
45
|
);
|
|
60
46
|
|
|
61
47
|
// list_collections
|
|
@@ -72,7 +58,7 @@ export function registerCollectionTools(
|
|
|
72
58
|
return {
|
|
73
59
|
content: [{ type: "text", text: JSON.stringify(collections, null, 2) }],
|
|
74
60
|
};
|
|
75
|
-
})
|
|
61
|
+
})
|
|
76
62
|
);
|
|
77
63
|
|
|
78
64
|
// get_collection_info
|
|
@@ -85,15 +71,12 @@ export function registerCollectionTools(
|
|
|
85
71
|
inputSchema: schemas.GetCollectionInfoSchema,
|
|
86
72
|
},
|
|
87
73
|
withToolLogging("get_collection_info", async ({ name }) => {
|
|
88
|
-
log.info(
|
|
89
|
-
{ tool: "get_collection_info", collection: name },
|
|
90
|
-
"Tool called",
|
|
91
|
-
);
|
|
74
|
+
log.info({ tool: "get_collection_info", collection: name }, "Tool called");
|
|
92
75
|
const info = await qdrant.getCollectionInfo(name);
|
|
93
76
|
return {
|
|
94
77
|
content: [{ type: "text", text: JSON.stringify(info, null, 2) }],
|
|
95
78
|
};
|
|
96
|
-
})
|
|
79
|
+
})
|
|
97
80
|
);
|
|
98
81
|
|
|
99
82
|
// delete_collection
|
|
@@ -108,10 +91,8 @@ export function registerCollectionTools(
|
|
|
108
91
|
log.info({ tool: "delete_collection", collection: name }, "Tool called");
|
|
109
92
|
await qdrant.deleteCollection(name);
|
|
110
93
|
return {
|
|
111
|
-
content: [
|
|
112
|
-
{ type: "text", text: `Collection "${name}" deleted successfully.` },
|
|
113
|
-
],
|
|
94
|
+
content: [{ type: "text", text: `Collection "${name}" deleted successfully.` }],
|
|
114
95
|
};
|
|
115
|
-
})
|
|
96
|
+
})
|
|
116
97
|
);
|
|
117
98
|
}
|
package/src/tools/document.ts
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
-
import logger from "../logger.js";
|
|
7
6
|
import type { EmbeddingProvider } from "../embeddings/base.js";
|
|
8
7
|
import { BM25SparseVectorGenerator } from "../embeddings/sparse.js";
|
|
8
|
+
import logger from "../logger.js";
|
|
9
9
|
import type { QdrantManager } from "../qdrant/client.js";
|
|
10
10
|
import { withToolLogging } from "./logging.js";
|
|
11
11
|
import * as schemas from "./schemas.js";
|
|
@@ -17,10 +17,7 @@ export interface DocumentToolDependencies {
|
|
|
17
17
|
embeddings: EmbeddingProvider;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export function registerDocumentTools(
|
|
21
|
-
server: McpServer,
|
|
22
|
-
deps: DocumentToolDependencies,
|
|
23
|
-
): void {
|
|
20
|
+
export function registerDocumentTools(server: McpServer, deps: DocumentToolDependencies): void {
|
|
24
21
|
const { qdrant, embeddings } = deps;
|
|
25
22
|
|
|
26
23
|
// add_documents
|
|
@@ -33,10 +30,7 @@ export function registerDocumentTools(
|
|
|
33
30
|
inputSchema: schemas.AddDocumentsSchema,
|
|
34
31
|
},
|
|
35
32
|
withToolLogging("add_documents", async ({ collection, documents }) => {
|
|
36
|
-
log.info(
|
|
37
|
-
{ tool: "add_documents", collection, count: documents.length },
|
|
38
|
-
"Tool called",
|
|
39
|
-
);
|
|
33
|
+
log.info({ tool: "add_documents", collection, count: documents.length }, "Tool called");
|
|
40
34
|
// Check if collection exists and get info
|
|
41
35
|
const exists = await qdrant.collectionExists(collection);
|
|
42
36
|
if (!exists) {
|
|
@@ -69,7 +63,7 @@ export function registerDocumentTools(
|
|
|
69
63
|
text: string;
|
|
70
64
|
metadata?: Record<string, any>;
|
|
71
65
|
},
|
|
72
|
-
index: number
|
|
66
|
+
index: number
|
|
73
67
|
) => ({
|
|
74
68
|
id: doc.id,
|
|
75
69
|
vector: embeddingResults[index].embedding,
|
|
@@ -78,7 +72,7 @@ export function registerDocumentTools(
|
|
|
78
72
|
text: doc.text,
|
|
79
73
|
...doc.metadata,
|
|
80
74
|
},
|
|
81
|
-
})
|
|
75
|
+
})
|
|
82
76
|
);
|
|
83
77
|
|
|
84
78
|
await qdrant.addPointsWithSparse(collection, points);
|
|
@@ -91,7 +85,7 @@ export function registerDocumentTools(
|
|
|
91
85
|
text: string;
|
|
92
86
|
metadata?: Record<string, any>;
|
|
93
87
|
},
|
|
94
|
-
index: number
|
|
88
|
+
index: number
|
|
95
89
|
) => ({
|
|
96
90
|
id: doc.id,
|
|
97
91
|
vector: embeddingResults[index].embedding,
|
|
@@ -99,7 +93,7 @@ export function registerDocumentTools(
|
|
|
99
93
|
text: doc.text,
|
|
100
94
|
...doc.metadata,
|
|
101
95
|
},
|
|
102
|
-
})
|
|
96
|
+
})
|
|
103
97
|
);
|
|
104
98
|
|
|
105
99
|
await qdrant.addPoints(collection, points);
|
|
@@ -113,7 +107,7 @@ export function registerDocumentTools(
|
|
|
113
107
|
},
|
|
114
108
|
],
|
|
115
109
|
};
|
|
116
|
-
})
|
|
110
|
+
})
|
|
117
111
|
);
|
|
118
112
|
|
|
119
113
|
// delete_documents
|
|
@@ -125,10 +119,7 @@ export function registerDocumentTools(
|
|
|
125
119
|
inputSchema: schemas.DeleteDocumentsSchema,
|
|
126
120
|
},
|
|
127
121
|
withToolLogging("delete_documents", async ({ collection, ids }) => {
|
|
128
|
-
log.info(
|
|
129
|
-
{ tool: "delete_documents", collection, count: ids.length },
|
|
130
|
-
"Tool called",
|
|
131
|
-
);
|
|
122
|
+
log.info({ tool: "delete_documents", collection, count: ids.length }, "Tool called");
|
|
132
123
|
await qdrant.deletePoints(collection, ids);
|
|
133
124
|
return {
|
|
134
125
|
content: [
|
|
@@ -138,6 +129,6 @@ export function registerDocumentTools(
|
|
|
138
129
|
},
|
|
139
130
|
],
|
|
140
131
|
};
|
|
141
|
-
})
|
|
132
|
+
})
|
|
142
133
|
);
|
|
143
134
|
}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
import type { CodeSearchResult } from "../code/types.js";
|
|
3
3
|
import type { GitSearchResult } from "../git/types.js";
|
|
4
|
-
import {
|
|
5
|
-
buildCorrelations,
|
|
6
|
-
normalizeScores,
|
|
7
|
-
calculateRRFScore,
|
|
8
|
-
pathsMatch,
|
|
9
|
-
} from "./federated.js";
|
|
4
|
+
import { buildCorrelations, calculateRRFScore, normalizeScores, pathsMatch } from "./federated.js";
|
|
10
5
|
|
|
11
6
|
vi.mock("../logger.js", () => ({
|
|
12
7
|
default: {
|
|
@@ -137,9 +132,7 @@ describe("pathsMatch", () => {
|
|
|
137
132
|
it("should NOT match paths with different parent directories", () => {
|
|
138
133
|
// This was the false positive case
|
|
139
134
|
expect(pathsMatch("app/models/user.ts", "lib/user.ts")).toBe(false);
|
|
140
|
-
expect(pathsMatch("src/auth/middleware.ts", "other/middleware.ts")).toBe(
|
|
141
|
-
false,
|
|
142
|
-
);
|
|
135
|
+
expect(pathsMatch("src/auth/middleware.ts", "other/middleware.ts")).toBe(false);
|
|
143
136
|
});
|
|
144
137
|
|
|
145
138
|
it("should NOT match completely different filenames", () => {
|
|
@@ -162,9 +155,7 @@ describe("pathsMatch", () => {
|
|
|
162
155
|
});
|
|
163
156
|
|
|
164
157
|
describe("buildCorrelations", () => {
|
|
165
|
-
const createCodeResult = (
|
|
166
|
-
overrides: Partial<CodeSearchResult> = {},
|
|
167
|
-
): CodeSearchResult => ({
|
|
158
|
+
const createCodeResult = (overrides: Partial<CodeSearchResult> = {}): CodeSearchResult => ({
|
|
168
159
|
content: "function test() {}",
|
|
169
160
|
filePath: "src/utils/helper.ts",
|
|
170
161
|
startLine: 10,
|
|
@@ -175,9 +166,7 @@ describe("buildCorrelations", () => {
|
|
|
175
166
|
...overrides,
|
|
176
167
|
});
|
|
177
168
|
|
|
178
|
-
const createGitResult = (
|
|
179
|
-
overrides: Partial<GitSearchResult> = {},
|
|
180
|
-
): GitSearchResult => ({
|
|
169
|
+
const createGitResult = (overrides: Partial<GitSearchResult> = {}): GitSearchResult => ({
|
|
181
170
|
content: "Commit content",
|
|
182
171
|
commitHash: "abc123def456",
|
|
183
172
|
shortHash: "abc123d",
|
|
@@ -192,9 +181,7 @@ describe("buildCorrelations", () => {
|
|
|
192
181
|
|
|
193
182
|
it("should find correlations when file paths match", () => {
|
|
194
183
|
const codeResults = [createCodeResult({ filePath: "src/utils/helper.ts" })];
|
|
195
|
-
const gitResults = [
|
|
196
|
-
createGitResult({ files: ["src/utils/helper.ts", "src/index.ts"] }),
|
|
197
|
-
];
|
|
184
|
+
const gitResults = [createGitResult({ files: ["src/utils/helper.ts", "src/index.ts"] })];
|
|
198
185
|
|
|
199
186
|
const correlations = buildCorrelations(codeResults, gitResults);
|
|
200
187
|
|
|
@@ -205,9 +192,7 @@ describe("buildCorrelations", () => {
|
|
|
205
192
|
});
|
|
206
193
|
|
|
207
194
|
it("should match partial paths (relative vs absolute)", () => {
|
|
208
|
-
const codeResults = [
|
|
209
|
-
createCodeResult({ filePath: "/home/user/project/src/utils/helper.ts" }),
|
|
210
|
-
];
|
|
195
|
+
const codeResults = [createCodeResult({ filePath: "/home/user/project/src/utils/helper.ts" })];
|
|
211
196
|
const gitResults = [createGitResult({ files: ["src/utils/helper.ts"] })];
|
|
212
197
|
|
|
213
198
|
const correlations = buildCorrelations(codeResults, gitResults);
|
|
@@ -242,12 +227,8 @@ describe("buildCorrelations", () => {
|
|
|
242
227
|
|
|
243
228
|
expect(correlations).toHaveLength(1);
|
|
244
229
|
expect(correlations[0].relatedCommits).toHaveLength(2);
|
|
245
|
-
expect(correlations[0].relatedCommits.map((c) => c.shortHash)).toContain(
|
|
246
|
-
|
|
247
|
-
);
|
|
248
|
-
expect(correlations[0].relatedCommits.map((c) => c.shortHash)).toContain(
|
|
249
|
-
"xyz789a",
|
|
250
|
-
);
|
|
230
|
+
expect(correlations[0].relatedCommits.map((c) => c.shortHash)).toContain("abc123d");
|
|
231
|
+
expect(correlations[0].relatedCommits.map((c) => c.shortHash)).toContain("xyz789a");
|
|
251
232
|
});
|
|
252
233
|
|
|
253
234
|
it("should handle multiple code results with different correlations", () => {
|
|
@@ -297,9 +278,7 @@ describe("buildCorrelations", () => {
|
|
|
297
278
|
});
|
|
298
279
|
|
|
299
280
|
it("should normalize Windows-style paths", () => {
|
|
300
|
-
const codeResults = [
|
|
301
|
-
createCodeResult({ filePath: "src\\utils\\helper.ts" }),
|
|
302
|
-
];
|
|
281
|
+
const codeResults = [createCodeResult({ filePath: "src\\utils\\helper.ts" })];
|
|
303
282
|
const gitResults = [createGitResult({ files: ["src/utils/helper.ts"] })];
|
|
304
283
|
|
|
305
284
|
const correlations = buildCorrelations(codeResults, gitResults);
|
|
@@ -379,11 +358,11 @@ describe("contextual_search integration", () => {
|
|
|
379
358
|
|
|
380
359
|
// Get the handler for contextual_search
|
|
381
360
|
const contextualSearchCall = mockServer.registerTool.mock.calls.find(
|
|
382
|
-
(call) => call[0] === "contextual_search"
|
|
361
|
+
(call) => call[0] === "contextual_search"
|
|
383
362
|
);
|
|
384
363
|
expect(contextualSearchCall).toBeDefined();
|
|
385
364
|
|
|
386
|
-
const handler = contextualSearchCall
|
|
365
|
+
const handler = contextualSearchCall?.[2];
|
|
387
366
|
const result = await handler({
|
|
388
367
|
path: "/test/repo",
|
|
389
368
|
query: "test function",
|
|
@@ -392,15 +371,13 @@ describe("contextual_search integration", () => {
|
|
|
392
371
|
correlate: true,
|
|
393
372
|
});
|
|
394
373
|
|
|
395
|
-
expect(mockCodeIndexer.searchCode).toHaveBeenCalledWith(
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
{ limit: 5 },
|
|
399
|
-
);
|
|
374
|
+
expect(mockCodeIndexer.searchCode).toHaveBeenCalledWith("/test/repo", "test function", {
|
|
375
|
+
limit: 5,
|
|
376
|
+
});
|
|
400
377
|
expect(mockGitHistoryIndexer.searchHistory).toHaveBeenCalledWith(
|
|
401
378
|
"/test/repo",
|
|
402
379
|
"test function",
|
|
403
|
-
{ limit: 5 }
|
|
380
|
+
{ limit: 5 }
|
|
404
381
|
);
|
|
405
382
|
expect(result.content[0].text).toContain("Code Results");
|
|
406
383
|
expect(result.content[0].text).toContain("Git History Results");
|
|
@@ -422,9 +399,9 @@ describe("contextual_search integration", () => {
|
|
|
422
399
|
});
|
|
423
400
|
|
|
424
401
|
const contextualSearchCall = mockServer.registerTool.mock.calls.find(
|
|
425
|
-
(call) => call[0] === "contextual_search"
|
|
402
|
+
(call) => call[0] === "contextual_search"
|
|
426
403
|
);
|
|
427
|
-
const handler = contextualSearchCall
|
|
404
|
+
const handler = contextualSearchCall?.[2];
|
|
428
405
|
const result = await handler({
|
|
429
406
|
path: "/test/repo",
|
|
430
407
|
query: "test",
|
|
@@ -449,9 +426,9 @@ describe("contextual_search integration", () => {
|
|
|
449
426
|
});
|
|
450
427
|
|
|
451
428
|
const contextualSearchCall = mockServer.registerTool.mock.calls.find(
|
|
452
|
-
(call) => call[0] === "contextual_search"
|
|
429
|
+
(call) => call[0] === "contextual_search"
|
|
453
430
|
);
|
|
454
|
-
const handler = contextualSearchCall
|
|
431
|
+
const handler = contextualSearchCall?.[2];
|
|
455
432
|
const result = await handler({
|
|
456
433
|
path: "/test/repo",
|
|
457
434
|
query: "test",
|
|
@@ -500,9 +477,9 @@ describe("contextual_search integration", () => {
|
|
|
500
477
|
});
|
|
501
478
|
|
|
502
479
|
const contextualSearchCall = mockServer.registerTool.mock.calls.find(
|
|
503
|
-
(call) => call[0] === "contextual_search"
|
|
480
|
+
(call) => call[0] === "contextual_search"
|
|
504
481
|
);
|
|
505
|
-
const handler = contextualSearchCall
|
|
482
|
+
const handler = contextualSearchCall?.[2];
|
|
506
483
|
const result = await handler({
|
|
507
484
|
path: "/test/repo",
|
|
508
485
|
query: "test",
|
|
@@ -574,9 +551,9 @@ describe("federated_search integration", () => {
|
|
|
574
551
|
});
|
|
575
552
|
|
|
576
553
|
const federatedSearchCall = mockServer.registerTool.mock.calls.find(
|
|
577
|
-
(call) => call[0] === "federated_search"
|
|
554
|
+
(call) => call[0] === "federated_search"
|
|
578
555
|
);
|
|
579
|
-
const handler = federatedSearchCall
|
|
556
|
+
const handler = federatedSearchCall?.[2];
|
|
580
557
|
const result = await handler({
|
|
581
558
|
paths: ["/repo1", "/repo2"],
|
|
582
559
|
query: "test function",
|
|
@@ -608,9 +585,9 @@ describe("federated_search integration", () => {
|
|
|
608
585
|
});
|
|
609
586
|
|
|
610
587
|
const federatedSearchCall = mockServer.registerTool.mock.calls.find(
|
|
611
|
-
(call) => call[0] === "federated_search"
|
|
588
|
+
(call) => call[0] === "federated_search"
|
|
612
589
|
);
|
|
613
|
-
const handler = federatedSearchCall
|
|
590
|
+
const handler = federatedSearchCall?.[2];
|
|
614
591
|
const result = await handler({
|
|
615
592
|
paths: ["/repo1", "/repo2"],
|
|
616
593
|
query: "test",
|
|
@@ -636,9 +613,9 @@ describe("federated_search integration", () => {
|
|
|
636
613
|
});
|
|
637
614
|
|
|
638
615
|
const federatedSearchCall = mockServer.registerTool.mock.calls.find(
|
|
639
|
-
(call) => call[0] === "federated_search"
|
|
616
|
+
(call) => call[0] === "federated_search"
|
|
640
617
|
);
|
|
641
|
-
const handler = federatedSearchCall
|
|
618
|
+
const handler = federatedSearchCall?.[2];
|
|
642
619
|
await handler({
|
|
643
620
|
paths: ["/repo1"],
|
|
644
621
|
query: "test",
|
|
@@ -664,9 +641,9 @@ describe("federated_search integration", () => {
|
|
|
664
641
|
});
|
|
665
642
|
|
|
666
643
|
const federatedSearchCall = mockServer.registerTool.mock.calls.find(
|
|
667
|
-
(call) => call[0] === "federated_search"
|
|
644
|
+
(call) => call[0] === "federated_search"
|
|
668
645
|
);
|
|
669
|
-
const handler = federatedSearchCall
|
|
646
|
+
const handler = federatedSearchCall?.[2];
|
|
670
647
|
await handler({
|
|
671
648
|
paths: ["/repo1"],
|
|
672
649
|
query: "test",
|
|
@@ -721,9 +698,9 @@ describe("federated_search integration", () => {
|
|
|
721
698
|
});
|
|
722
699
|
|
|
723
700
|
const federatedSearchCall = mockServer.registerTool.mock.calls.find(
|
|
724
|
-
(call) => call[0] === "federated_search"
|
|
701
|
+
(call) => call[0] === "federated_search"
|
|
725
702
|
);
|
|
726
|
-
const handler = federatedSearchCall
|
|
703
|
+
const handler = federatedSearchCall?.[2];
|
|
727
704
|
const result = await handler({
|
|
728
705
|
paths: ["/repo1"],
|
|
729
706
|
query: "test",
|
|
@@ -751,9 +728,9 @@ describe("federated_search integration", () => {
|
|
|
751
728
|
});
|
|
752
729
|
|
|
753
730
|
const federatedSearchCall = mockServer.registerTool.mock.calls.find(
|
|
754
|
-
(call) => call[0] === "federated_search"
|
|
731
|
+
(call) => call[0] === "federated_search"
|
|
755
732
|
);
|
|
756
|
-
const handler = federatedSearchCall
|
|
733
|
+
const handler = federatedSearchCall?.[2];
|
|
757
734
|
const result = await handler({
|
|
758
735
|
paths: ["/repo1"],
|
|
759
736
|
query: "nonexistent query",
|