@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/federated.ts
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
|
-
import logger from "../logger.js";
|
|
11
10
|
import type { CodeIndexer } from "../code/indexer.js";
|
|
12
11
|
import type { CodeSearchResult } from "../code/types.js";
|
|
13
12
|
import type { GitHistoryIndexer } from "../git/indexer.js";
|
|
14
13
|
import type { GitSearchResult } from "../git/types.js";
|
|
14
|
+
import logger from "../logger.js";
|
|
15
15
|
import { withToolLogging } from "./logging.js";
|
|
16
16
|
import * as schemas from "./schemas.js";
|
|
17
17
|
|
|
@@ -85,7 +85,7 @@ export interface FederatedSearchResponse {
|
|
|
85
85
|
*/
|
|
86
86
|
export function buildCorrelations(
|
|
87
87
|
codeResults: CodeSearchResult[],
|
|
88
|
-
gitResults: GitSearchResult[]
|
|
88
|
+
gitResults: GitSearchResult[]
|
|
89
89
|
): CodeCommitCorrelation[] {
|
|
90
90
|
const correlations: CodeCommitCorrelation[] = [];
|
|
91
91
|
|
|
@@ -95,9 +95,7 @@ export function buildCorrelations(
|
|
|
95
95
|
// Find commits that modified this file
|
|
96
96
|
for (const gitResult of gitResults) {
|
|
97
97
|
// Check if any file in the commit matches the code result's file path
|
|
98
|
-
const matchesFile = gitResult.files.some((file) =>
|
|
99
|
-
pathsMatch(codeResult.filePath, file),
|
|
100
|
-
);
|
|
98
|
+
const matchesFile = gitResult.files.some((file) => pathsMatch(codeResult.filePath, file));
|
|
101
99
|
|
|
102
100
|
if (matchesFile) {
|
|
103
101
|
relatedCommits.push({
|
|
@@ -157,9 +155,7 @@ export function pathsMatch(path1: string, path2: string): boolean {
|
|
|
157
155
|
/**
|
|
158
156
|
* Normalize scores to [0, 1] range using min-max normalization
|
|
159
157
|
*/
|
|
160
|
-
export function normalizeScores<T extends { score: number }>(
|
|
161
|
-
results: T[],
|
|
162
|
-
): T[] {
|
|
158
|
+
export function normalizeScores<T extends { score: number }>(results: T[]): T[] {
|
|
163
159
|
if (results.length === 0) return [];
|
|
164
160
|
if (results.length === 1) return results.map((r) => ({ ...r, score: 1 }));
|
|
165
161
|
|
|
@@ -203,7 +199,7 @@ async function performContextualSearch(
|
|
|
203
199
|
codeLimit?: number;
|
|
204
200
|
gitLimit?: number;
|
|
205
201
|
correlate?: boolean;
|
|
206
|
-
}
|
|
202
|
+
}
|
|
207
203
|
): Promise<ContextualSearchResult> {
|
|
208
204
|
const { path, query, codeLimit = 5, gitLimit = 5, correlate = true } = params;
|
|
209
205
|
|
|
@@ -214,15 +210,11 @@ async function performContextualSearch(
|
|
|
214
210
|
]);
|
|
215
211
|
|
|
216
212
|
if (codeStatus.status !== "indexed") {
|
|
217
|
-
throw new Error(
|
|
218
|
-
`Code index not found for "${path}". Run index_codebase first.`,
|
|
219
|
-
);
|
|
213
|
+
throw new Error(`Code index not found for "${path}". Run index_codebase first.`);
|
|
220
214
|
}
|
|
221
215
|
|
|
222
216
|
if (gitStatus.status !== "indexed") {
|
|
223
|
-
throw new Error(
|
|
224
|
-
`Git history index not found for "${path}". Run index_git_history first.`,
|
|
225
|
-
);
|
|
217
|
+
throw new Error(`Git history index not found for "${path}". Run index_git_history first.`);
|
|
226
218
|
}
|
|
227
219
|
|
|
228
220
|
// Execute searches in parallel
|
|
@@ -232,9 +224,7 @@ async function performContextualSearch(
|
|
|
232
224
|
]);
|
|
233
225
|
|
|
234
226
|
// Build correlations if requested
|
|
235
|
-
const correlations = correlate
|
|
236
|
-
? buildCorrelations(codeResults, gitResults)
|
|
237
|
-
: [];
|
|
227
|
+
const correlations = correlate ? buildCorrelations(codeResults, gitResults) : [];
|
|
238
228
|
|
|
239
229
|
return {
|
|
240
230
|
codeResults,
|
|
@@ -261,7 +251,7 @@ async function performFederatedSearch(
|
|
|
261
251
|
query: string;
|
|
262
252
|
searchType?: "code" | "git" | "both";
|
|
263
253
|
limit?: number;
|
|
264
|
-
}
|
|
254
|
+
}
|
|
265
255
|
): Promise<FederatedSearchResponse> {
|
|
266
256
|
const { paths, query, searchType = "both", limit = 20 } = params;
|
|
267
257
|
|
|
@@ -306,8 +296,8 @@ async function performFederatedSearch(
|
|
|
306
296
|
...r,
|
|
307
297
|
resultType: "code" as const,
|
|
308
298
|
repoPath: path,
|
|
309
|
-
}))
|
|
310
|
-
)
|
|
299
|
+
}))
|
|
300
|
+
)
|
|
311
301
|
);
|
|
312
302
|
}
|
|
313
303
|
|
|
@@ -322,8 +312,8 @@ async function performFederatedSearch(
|
|
|
322
312
|
...r,
|
|
323
313
|
resultType: "git" as const,
|
|
324
314
|
repoPath: path,
|
|
325
|
-
}))
|
|
326
|
-
)
|
|
315
|
+
}))
|
|
316
|
+
)
|
|
327
317
|
);
|
|
328
318
|
}
|
|
329
319
|
}
|
|
@@ -333,11 +323,10 @@ async function performFederatedSearch(
|
|
|
333
323
|
|
|
334
324
|
// Normalize scores per result type to ensure fair comparison
|
|
335
325
|
const codeResults = allResults.filter(
|
|
336
|
-
(r): r is FederatedResult & { resultType: "code" } =>
|
|
337
|
-
r.resultType === "code",
|
|
326
|
+
(r): r is FederatedResult & { resultType: "code" } => r.resultType === "code"
|
|
338
327
|
);
|
|
339
328
|
const gitResults = allResults.filter(
|
|
340
|
-
(r): r is FederatedResult & { resultType: "git" } => r.resultType === "git"
|
|
329
|
+
(r): r is FederatedResult & { resultType: "git" } => r.resultType === "git"
|
|
341
330
|
);
|
|
342
331
|
|
|
343
332
|
const normalizedCode = normalizeScores(codeResults);
|
|
@@ -353,7 +342,7 @@ async function performFederatedSearch(
|
|
|
353
342
|
if (!groupedResults.has(key)) {
|
|
354
343
|
groupedResults.set(key, []);
|
|
355
344
|
}
|
|
356
|
-
groupedResults.get(key)
|
|
345
|
+
groupedResults.get(key)?.push(result);
|
|
357
346
|
}
|
|
358
347
|
|
|
359
348
|
// Sort each group by score and create rank lookup
|
|
@@ -399,10 +388,7 @@ async function performFederatedSearch(
|
|
|
399
388
|
/**
|
|
400
389
|
* Register federated search tools on the MCP server
|
|
401
390
|
*/
|
|
402
|
-
export function registerFederatedTools(
|
|
403
|
-
server: McpServer,
|
|
404
|
-
deps: FederatedToolDependencies,
|
|
405
|
-
): void {
|
|
391
|
+
export function registerFederatedTools(server: McpServer, deps: FederatedToolDependencies): void {
|
|
406
392
|
const { codeIndexer, gitHistoryIndexer } = deps;
|
|
407
393
|
|
|
408
394
|
// contextual_search
|
|
@@ -419,16 +405,15 @@ export function registerFederatedTools(
|
|
|
419
405
|
withToolLogging(
|
|
420
406
|
"contextual_search",
|
|
421
407
|
async ({ path, query, codeLimit, gitLimit, correlate }) => {
|
|
422
|
-
log.info(
|
|
423
|
-
{ tool: "contextual_search", path, query: query.substring(0, 80) },
|
|
424
|
-
"Tool called",
|
|
425
|
-
);
|
|
408
|
+
log.info({ tool: "contextual_search", path, query: query.substring(0, 80) }, "Tool called");
|
|
426
409
|
try {
|
|
427
|
-
const result = await performContextualSearch(
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
410
|
+
const result = await performContextualSearch(codeIndexer, gitHistoryIndexer, {
|
|
411
|
+
path,
|
|
412
|
+
query,
|
|
413
|
+
codeLimit,
|
|
414
|
+
gitLimit,
|
|
415
|
+
correlate,
|
|
416
|
+
});
|
|
432
417
|
|
|
433
418
|
// Format output
|
|
434
419
|
const sections: string[] = [];
|
|
@@ -444,7 +429,7 @@ export function registerFederatedTools(
|
|
|
444
429
|
r.language +
|
|
445
430
|
"\n" +
|
|
446
431
|
r.content +
|
|
447
|
-
"\n```\n"
|
|
432
|
+
"\n```\n"
|
|
448
433
|
);
|
|
449
434
|
});
|
|
450
435
|
}
|
|
@@ -456,7 +441,7 @@ export function registerFederatedTools(
|
|
|
456
441
|
sections.push(
|
|
457
442
|
`### ${idx + 1}. ${r.shortHash} - ${r.subject} (score: ${r.score.toFixed(3)})\n` +
|
|
458
443
|
`Author: ${r.author} | Date: ${r.date} | Type: ${r.commitType}\n` +
|
|
459
|
-
`Files: ${r.files.slice(0, 5).join(", ")}${r.files.length > 5 ? ` (+${r.files.length - 5} more)` : ""}\n
|
|
444
|
+
`Files: ${r.files.slice(0, 5).join(", ")}${r.files.length > 5 ? ` (+${r.files.length - 5} more)` : ""}\n`
|
|
460
445
|
);
|
|
461
446
|
});
|
|
462
447
|
}
|
|
@@ -470,7 +455,7 @@ export function registerFederatedTools(
|
|
|
470
455
|
.map((commit) => ` - ${commit.shortHash}: ${commit.subject}`)
|
|
471
456
|
.join("\n");
|
|
472
457
|
sections.push(
|
|
473
|
-
`**${c.codeResult.filePath}:${c.codeResult.startLine}** modified by:\n${commits}\n
|
|
458
|
+
`**${c.codeResult.filePath}:${c.codeResult.startLine}** modified by:\n${commits}\n`
|
|
474
459
|
);
|
|
475
460
|
});
|
|
476
461
|
}
|
|
@@ -496,8 +481,8 @@ export function registerFederatedTools(
|
|
|
496
481
|
isError: true,
|
|
497
482
|
};
|
|
498
483
|
}
|
|
499
|
-
}
|
|
500
|
-
)
|
|
484
|
+
}
|
|
485
|
+
)
|
|
501
486
|
);
|
|
502
487
|
|
|
503
488
|
// federated_search
|
|
@@ -511,77 +496,72 @@ export function registerFederatedTools(
|
|
|
511
496
|
"Supports code-only, git-only, or combined search modes.",
|
|
512
497
|
inputSchema: schemas.FederatedSearchSchema,
|
|
513
498
|
},
|
|
514
|
-
withToolLogging(
|
|
515
|
-
"federated_search",
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
codeIndexer,
|
|
524
|
-
gitHistoryIndexer,
|
|
525
|
-
{ paths, query, searchType, limit },
|
|
526
|
-
);
|
|
527
|
-
|
|
528
|
-
if (response.results.length === 0) {
|
|
529
|
-
return {
|
|
530
|
-
content: [
|
|
531
|
-
{
|
|
532
|
-
type: "text",
|
|
533
|
-
text: `No results found for query "${query}" across ${paths.length} repository(ies).`,
|
|
534
|
-
},
|
|
535
|
-
],
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
// Format results
|
|
540
|
-
const sections: string[] = [
|
|
541
|
-
`# Federated Search Results\n` +
|
|
542
|
-
`Query: "${query}" | Type: ${response.metadata.searchType} | ` +
|
|
543
|
-
`Repositories: ${response.metadata.repositoriesSearched.length}\n`,
|
|
544
|
-
];
|
|
545
|
-
|
|
546
|
-
response.results.forEach((r, idx) => {
|
|
547
|
-
if (r.resultType === "code") {
|
|
548
|
-
sections.push(
|
|
549
|
-
`## ${idx + 1}. [CODE] ${r.filePath}:${r.startLine}-${r.endLine}\n` +
|
|
550
|
-
`Repository: ${r.repoPath} | Language: ${r.language} | Score: ${r.score.toFixed(3)}\n` +
|
|
551
|
-
"```" +
|
|
552
|
-
r.language +
|
|
553
|
-
"\n" +
|
|
554
|
-
r.content +
|
|
555
|
-
"\n```\n",
|
|
556
|
-
);
|
|
557
|
-
} else {
|
|
558
|
-
sections.push(
|
|
559
|
-
`## ${idx + 1}. [GIT] ${r.shortHash} - ${r.subject}\n` +
|
|
560
|
-
`Repository: ${r.repoPath} | Author: ${r.author} | Date: ${r.date} | Score: ${r.score.toFixed(3)}\n` +
|
|
561
|
-
`Type: ${r.commitType} | Files: ${r.files.slice(0, 3).join(", ")}${r.files.length > 3 ? ` (+${r.files.length - 3} more)` : ""}\n`,
|
|
562
|
-
);
|
|
563
|
-
}
|
|
564
|
-
});
|
|
565
|
-
|
|
566
|
-
sections.push(
|
|
567
|
-
`\n---\nTotal: ${response.metadata.totalResults} result(s) from ${response.metadata.repositoriesSearched.length} repository(ies).`,
|
|
568
|
-
);
|
|
499
|
+
withToolLogging("federated_search", async ({ paths, query, searchType, limit }) => {
|
|
500
|
+
log.info({ tool: "federated_search", paths, query: query.substring(0, 80) }, "Tool called");
|
|
501
|
+
try {
|
|
502
|
+
const response = await performFederatedSearch(codeIndexer, gitHistoryIndexer, {
|
|
503
|
+
paths,
|
|
504
|
+
query,
|
|
505
|
+
searchType,
|
|
506
|
+
limit,
|
|
507
|
+
});
|
|
569
508
|
|
|
570
|
-
|
|
571
|
-
content: [{ type: "text", text: sections.join("\n") }],
|
|
572
|
-
};
|
|
573
|
-
} catch (error) {
|
|
509
|
+
if (response.results.length === 0) {
|
|
574
510
|
return {
|
|
575
511
|
content: [
|
|
576
512
|
{
|
|
577
513
|
type: "text",
|
|
578
|
-
text: `
|
|
514
|
+
text: `No results found for query "${query}" across ${paths.length} repository(ies).`,
|
|
579
515
|
},
|
|
580
516
|
],
|
|
581
|
-
isError: true,
|
|
582
517
|
};
|
|
583
518
|
}
|
|
584
|
-
|
|
585
|
-
|
|
519
|
+
|
|
520
|
+
// Format results
|
|
521
|
+
const sections: string[] = [
|
|
522
|
+
`# Federated Search Results\n` +
|
|
523
|
+
`Query: "${query}" | Type: ${response.metadata.searchType} | ` +
|
|
524
|
+
`Repositories: ${response.metadata.repositoriesSearched.length}\n`,
|
|
525
|
+
];
|
|
526
|
+
|
|
527
|
+
response.results.forEach((r, idx) => {
|
|
528
|
+
if (r.resultType === "code") {
|
|
529
|
+
sections.push(
|
|
530
|
+
`## ${idx + 1}. [CODE] ${r.filePath}:${r.startLine}-${r.endLine}\n` +
|
|
531
|
+
`Repository: ${r.repoPath} | Language: ${r.language} | Score: ${r.score.toFixed(3)}\n` +
|
|
532
|
+
"```" +
|
|
533
|
+
r.language +
|
|
534
|
+
"\n" +
|
|
535
|
+
r.content +
|
|
536
|
+
"\n```\n"
|
|
537
|
+
);
|
|
538
|
+
} else {
|
|
539
|
+
sections.push(
|
|
540
|
+
`## ${idx + 1}. [GIT] ${r.shortHash} - ${r.subject}\n` +
|
|
541
|
+
`Repository: ${r.repoPath} | Author: ${r.author} | Date: ${r.date} | Score: ${r.score.toFixed(3)}\n` +
|
|
542
|
+
`Type: ${r.commitType} | Files: ${r.files.slice(0, 3).join(", ")}${r.files.length > 3 ? ` (+${r.files.length - 3} more)` : ""}\n`
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
sections.push(
|
|
548
|
+
`\n---\nTotal: ${response.metadata.totalResults} result(s) from ${response.metadata.repositoriesSearched.length} repository(ies).`
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
return {
|
|
552
|
+
content: [{ type: "text", text: sections.join("\n") }],
|
|
553
|
+
};
|
|
554
|
+
} catch (error) {
|
|
555
|
+
return {
|
|
556
|
+
content: [
|
|
557
|
+
{
|
|
558
|
+
type: "text",
|
|
559
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
560
|
+
},
|
|
561
|
+
],
|
|
562
|
+
isError: true,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
})
|
|
586
566
|
);
|
|
587
567
|
}
|
package/src/tools/git-history.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 { GitHistoryIndexer } from "../git/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 GitHistoryToolDependencies {
|
|
|
14
14
|
gitHistoryIndexer: GitHistoryIndexer;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export function registerGitHistoryTools(
|
|
18
|
-
server: McpServer,
|
|
19
|
-
deps: GitHistoryToolDependencies,
|
|
20
|
-
): void {
|
|
17
|
+
export function registerGitHistoryTools(server: McpServer, deps: GitHistoryToolDependencies): void {
|
|
21
18
|
const { gitHistoryIndexer } = deps;
|
|
22
19
|
|
|
23
20
|
// index_git_history
|
|
@@ -32,20 +29,14 @@ export function registerGitHistoryTools(
|
|
|
32
29
|
withToolLogging(
|
|
33
30
|
"index_git_history",
|
|
34
31
|
async ({ path, forceReindex, sinceDate, maxCommits }, extra) => {
|
|
35
|
-
log.info(
|
|
36
|
-
{ tool: "index_git_history", path, forceReindex },
|
|
37
|
-
"Tool called",
|
|
38
|
-
);
|
|
32
|
+
log.info({ tool: "index_git_history", path, forceReindex }, "Tool called");
|
|
39
33
|
const progressToken = extra._meta?.progressToken;
|
|
40
34
|
|
|
41
35
|
const stats = await gitHistoryIndexer.indexHistory(
|
|
42
36
|
path,
|
|
43
37
|
{ forceReindex, sinceDate, maxCommits },
|
|
44
38
|
(progress) => {
|
|
45
|
-
log.debug(
|
|
46
|
-
{ phase: progress.phase, percentage: progress.percentage },
|
|
47
|
-
progress.message,
|
|
48
|
-
);
|
|
39
|
+
log.debug({ phase: progress.phase, percentage: progress.percentage }, progress.message);
|
|
49
40
|
if (progressToken !== undefined) {
|
|
50
41
|
extra.sendNotification({
|
|
51
42
|
method: "notifications/progress",
|
|
@@ -57,7 +48,7 @@ export function registerGitHistoryTools(
|
|
|
57
48
|
},
|
|
58
49
|
});
|
|
59
50
|
}
|
|
60
|
-
}
|
|
51
|
+
}
|
|
61
52
|
);
|
|
62
53
|
|
|
63
54
|
let statusMessage = `Indexed ${stats.commitsIndexed}/${stats.commitsScanned} commits (${stats.chunksCreated} chunks) in ${(stats.durationMs / 1000).toFixed(1)}s`;
|
|
@@ -72,8 +63,8 @@ export function registerGitHistoryTools(
|
|
|
72
63
|
content: [{ type: "text", text: statusMessage }],
|
|
73
64
|
isError: stats.status === "failed",
|
|
74
65
|
};
|
|
75
|
-
}
|
|
76
|
-
)
|
|
66
|
+
}
|
|
67
|
+
)
|
|
77
68
|
);
|
|
78
69
|
|
|
79
70
|
// search_git_history
|
|
@@ -87,18 +78,10 @@ export function registerGitHistoryTools(
|
|
|
87
78
|
},
|
|
88
79
|
withToolLogging(
|
|
89
80
|
"search_git_history",
|
|
90
|
-
async ({
|
|
91
|
-
path,
|
|
92
|
-
query,
|
|
93
|
-
limit,
|
|
94
|
-
commitTypes,
|
|
95
|
-
authors,
|
|
96
|
-
dateFrom,
|
|
97
|
-
dateTo,
|
|
98
|
-
}) => {
|
|
81
|
+
async ({ path, query, limit, commitTypes, authors, dateFrom, dateTo }) => {
|
|
99
82
|
log.info(
|
|
100
83
|
{ tool: "search_git_history", path, query: query.substring(0, 80) },
|
|
101
|
-
"Tool called"
|
|
84
|
+
"Tool called"
|
|
102
85
|
);
|
|
103
86
|
const results = await gitHistoryIndexer.searchHistory(path, query, {
|
|
104
87
|
limit,
|
|
@@ -110,9 +93,7 @@ export function registerGitHistoryTools(
|
|
|
110
93
|
|
|
111
94
|
if (results.length === 0) {
|
|
112
95
|
return {
|
|
113
|
-
content: [
|
|
114
|
-
{ type: "text", text: `No results found for query: "${query}"` },
|
|
115
|
-
],
|
|
96
|
+
content: [{ type: "text", text: `No results found for query: "${query}"` }],
|
|
116
97
|
};
|
|
117
98
|
}
|
|
118
99
|
|
|
@@ -127,7 +108,7 @@ export function registerGitHistoryTools(
|
|
|
127
108
|
`Date: ${r.date.split("T")[0]}\n` +
|
|
128
109
|
`Subject: ${r.subject}\n` +
|
|
129
110
|
`Files: ${r.files.slice(0, 5).join(", ")}${r.files.length > 5 ? ` (+${r.files.length - 5} more)` : ""}\n\n` +
|
|
130
|
-
`${r.content.substring(0, 500)}${r.content.length > 500 ? "..." : ""}\n
|
|
111
|
+
`${r.content.substring(0, 500)}${r.content.length > 500 ? "..." : ""}\n`
|
|
131
112
|
)
|
|
132
113
|
.join("\n");
|
|
133
114
|
|
|
@@ -139,8 +120,8 @@ export function registerGitHistoryTools(
|
|
|
139
120
|
},
|
|
140
121
|
],
|
|
141
122
|
};
|
|
142
|
-
}
|
|
143
|
-
)
|
|
123
|
+
}
|
|
124
|
+
)
|
|
144
125
|
);
|
|
145
126
|
|
|
146
127
|
// index_new_commits
|
|
@@ -156,26 +137,20 @@ export function registerGitHistoryTools(
|
|
|
156
137
|
log.info({ tool: "index_new_commits", path }, "Tool called");
|
|
157
138
|
const progressToken = extra._meta?.progressToken;
|
|
158
139
|
|
|
159
|
-
const stats = await gitHistoryIndexer.indexNewCommits(
|
|
160
|
-
|
|
161
|
-
(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
message: `[${progress.phase}] ${progress.message}`,
|
|
174
|
-
},
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
},
|
|
178
|
-
);
|
|
140
|
+
const stats = await gitHistoryIndexer.indexNewCommits(path, (progress) => {
|
|
141
|
+
log.debug({ phase: progress.phase, percentage: progress.percentage }, progress.message);
|
|
142
|
+
if (progressToken !== undefined) {
|
|
143
|
+
extra.sendNotification({
|
|
144
|
+
method: "notifications/progress",
|
|
145
|
+
params: {
|
|
146
|
+
progressToken,
|
|
147
|
+
progress: progress.percentage,
|
|
148
|
+
total: 100,
|
|
149
|
+
message: `[${progress.phase}] ${progress.message}`,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
});
|
|
179
154
|
|
|
180
155
|
let message: string;
|
|
181
156
|
if (stats.newCommits === 0) {
|
|
@@ -189,7 +164,7 @@ export function registerGitHistoryTools(
|
|
|
189
164
|
return {
|
|
190
165
|
content: [{ type: "text", text: message }],
|
|
191
166
|
};
|
|
192
|
-
})
|
|
167
|
+
})
|
|
193
168
|
);
|
|
194
169
|
|
|
195
170
|
// get_git_index_status
|
|
@@ -197,8 +172,7 @@ export function registerGitHistoryTools(
|
|
|
197
172
|
"get_git_index_status",
|
|
198
173
|
{
|
|
199
174
|
title: "Get Git Index Status",
|
|
200
|
-
description:
|
|
201
|
-
"Get the indexing status and statistics for a repository's git history index.",
|
|
175
|
+
description: "Get the indexing status and statistics for a repository's git history index.",
|
|
202
176
|
inputSchema: schemas.GetGitIndexStatusSchema,
|
|
203
177
|
},
|
|
204
178
|
withToolLogging("get_git_index_status", async ({ path }) => {
|
|
@@ -240,7 +214,7 @@ export function registerGitHistoryTools(
|
|
|
240
214
|
return {
|
|
241
215
|
content: [{ type: "text", text: JSON.stringify(statusInfo, null, 2) }],
|
|
242
216
|
};
|
|
243
|
-
})
|
|
217
|
+
})
|
|
244
218
|
);
|
|
245
219
|
|
|
246
220
|
// clear_git_index
|
|
@@ -256,10 +230,8 @@ export function registerGitHistoryTools(
|
|
|
256
230
|
log.info({ tool: "clear_git_index", path }, "Tool called");
|
|
257
231
|
await gitHistoryIndexer.clearIndex(path);
|
|
258
232
|
return {
|
|
259
|
-
content: [
|
|
260
|
-
{ type: "text", text: `Git history index cleared for "${path}".` },
|
|
261
|
-
],
|
|
233
|
+
content: [{ type: "text", text: `Git history index cleared for "${path}".` }],
|
|
262
234
|
};
|
|
263
|
-
})
|
|
235
|
+
})
|
|
264
236
|
);
|
|
265
237
|
}
|
package/src/tools/index.ts
CHANGED
|
@@ -24,10 +24,7 @@ export interface ToolDependencies {
|
|
|
24
24
|
/**
|
|
25
25
|
* Register all MCP tools on the server
|
|
26
26
|
*/
|
|
27
|
-
export function registerAllTools(
|
|
28
|
-
server: McpServer,
|
|
29
|
-
deps: ToolDependencies,
|
|
30
|
-
): void {
|
|
27
|
+
export function registerAllTools(server: McpServer, deps: ToolDependencies): void {
|
|
31
28
|
registerCollectionTools(server, {
|
|
32
29
|
qdrant: deps.qdrant,
|
|
33
30
|
embeddings: deps.embeddings,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { withToolLogging } from "./logging.js";
|
|
3
3
|
|
|
4
4
|
vi.mock("../logger.js", () => ({
|
|
@@ -45,7 +45,7 @@ describe("withToolLogging", () => {
|
|
|
45
45
|
tool: "create_collection",
|
|
46
46
|
durationMs: expect.any(Number),
|
|
47
47
|
}),
|
|
48
|
-
"Tool completed"
|
|
48
|
+
"Tool completed"
|
|
49
49
|
);
|
|
50
50
|
});
|
|
51
51
|
|
|
@@ -65,7 +65,7 @@ describe("withToolLogging", () => {
|
|
|
65
65
|
durationMs: expect.any(Number),
|
|
66
66
|
error: "Error: Collection not found",
|
|
67
67
|
}),
|
|
68
|
-
"Tool failed"
|
|
68
|
+
"Tool failed"
|
|
69
69
|
);
|
|
70
70
|
expect(mockLog.info).not.toHaveBeenCalled();
|
|
71
71
|
});
|
|
@@ -83,7 +83,7 @@ describe("withToolLogging", () => {
|
|
|
83
83
|
durationMs: expect.any(Number),
|
|
84
84
|
err: testError,
|
|
85
85
|
}),
|
|
86
|
-
"Tool threw an error"
|
|
86
|
+
"Tool threw an error"
|
|
87
87
|
);
|
|
88
88
|
});
|
|
89
89
|
|
|
@@ -100,7 +100,7 @@ describe("withToolLogging", () => {
|
|
|
100
100
|
tool: "semantic_search",
|
|
101
101
|
durationMs: expect.any(Number),
|
|
102
102
|
}),
|
|
103
|
-
"Tool completed with no results"
|
|
103
|
+
"Tool completed with no results"
|
|
104
104
|
);
|
|
105
105
|
expect(mockLog.info).not.toHaveBeenCalled();
|
|
106
106
|
});
|
|
@@ -115,7 +115,7 @@ describe("withToolLogging", () => {
|
|
|
115
115
|
|
|
116
116
|
expect(mockLog.warn).toHaveBeenCalledWith(
|
|
117
117
|
expect.objectContaining({ tool: "hybrid_search" }),
|
|
118
|
-
"Tool completed with no results"
|
|
118
|
+
"Tool completed with no results"
|
|
119
119
|
);
|
|
120
120
|
});
|
|
121
121
|
|
|
@@ -129,7 +129,7 @@ describe("withToolLogging", () => {
|
|
|
129
129
|
|
|
130
130
|
expect(mockLog.warn).toHaveBeenCalledWith(
|
|
131
131
|
expect.objectContaining({ tool: "search_code" }),
|
|
132
|
-
"Tool completed with no results"
|
|
132
|
+
"Tool completed with no results"
|
|
133
133
|
);
|
|
134
134
|
});
|
|
135
135
|
|
|
@@ -144,7 +144,7 @@ describe("withToolLogging", () => {
|
|
|
144
144
|
// Should log info, not warn, because get_index_status is not a search tool
|
|
145
145
|
expect(mockLog.info).toHaveBeenCalledWith(
|
|
146
146
|
expect.objectContaining({ tool: "get_index_status" }),
|
|
147
|
-
"Tool completed"
|
|
147
|
+
"Tool completed"
|
|
148
148
|
);
|
|
149
149
|
expect(mockLog.warn).not.toHaveBeenCalled();
|
|
150
150
|
});
|
|
@@ -159,7 +159,7 @@ describe("withToolLogging", () => {
|
|
|
159
159
|
|
|
160
160
|
expect(mockLog.info).toHaveBeenCalledWith(
|
|
161
161
|
expect.objectContaining({ tool: "search_code" }),
|
|
162
|
-
"Tool completed"
|
|
162
|
+
"Tool completed"
|
|
163
163
|
);
|
|
164
164
|
expect(mockLog.warn).not.toHaveBeenCalled();
|
|
165
165
|
});
|
|
@@ -200,7 +200,7 @@ describe("withToolLogging", () => {
|
|
|
200
200
|
|
|
201
201
|
expect(mockLog.warn).toHaveBeenCalledWith(
|
|
202
202
|
expect.objectContaining({ tool: "search_git_history" }),
|
|
203
|
-
"Tool completed with no results"
|
|
203
|
+
"Tool completed with no results"
|
|
204
204
|
);
|
|
205
205
|
});
|
|
206
206
|
});
|
package/src/tools/logging.ts
CHANGED
|
@@ -29,9 +29,7 @@ function isEmptySearchResult(result: CallToolResult): boolean {
|
|
|
29
29
|
if (first.type === "text") {
|
|
30
30
|
const text = (first as { type: "text"; text: string }).text;
|
|
31
31
|
return (
|
|
32
|
-
text.startsWith("No results found") ||
|
|
33
|
-
text === "[]" ||
|
|
34
|
-
text.includes("Found 0 result(s)")
|
|
32
|
+
text.startsWith("No results found") || text === "[]" || text.includes("Found 0 result(s)")
|
|
35
33
|
);
|
|
36
34
|
}
|
|
37
35
|
return false;
|
|
@@ -44,9 +42,10 @@ function isEmptySearchResult(result: CallToolResult): boolean {
|
|
|
44
42
|
* - Logs "Tool completed with no results" at warn level for search tools
|
|
45
43
|
* - Logs "Tool threw an error" at error level when handler throws (re-throws)
|
|
46
44
|
*/
|
|
47
|
-
export function withToolLogging<
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
export function withToolLogging<T extends (...args: any[]) => Promise<CallToolResult>>(
|
|
46
|
+
toolName: string,
|
|
47
|
+
handler: T
|
|
48
|
+
): T {
|
|
50
49
|
const wrapped = async (...args: Parameters<T>): Promise<CallToolResult> => {
|
|
51
50
|
const startTime = Date.now();
|
|
52
51
|
try {
|
|
@@ -58,15 +57,9 @@ export function withToolLogging<
|
|
|
58
57
|
result.content?.[0]?.type === "text"
|
|
59
58
|
? (result.content[0] as { type: "text"; text: string }).text
|
|
60
59
|
: "Unknown error";
|
|
61
|
-
log.error(
|
|
62
|
-
{ tool: toolName, durationMs, error: errorText },
|
|
63
|
-
"Tool failed",
|
|
64
|
-
);
|
|
60
|
+
log.error({ tool: toolName, durationMs, error: errorText }, "Tool failed");
|
|
65
61
|
} else if (SEARCH_TOOLS.has(toolName) && isEmptySearchResult(result)) {
|
|
66
|
-
log.warn(
|
|
67
|
-
{ tool: toolName, durationMs },
|
|
68
|
-
"Tool completed with no results",
|
|
69
|
-
);
|
|
62
|
+
log.warn({ tool: toolName, durationMs }, "Tool completed with no results");
|
|
70
63
|
} else {
|
|
71
64
|
log.info({ tool: toolName, durationMs }, "Tool completed");
|
|
72
65
|
}
|
|
@@ -74,10 +67,7 @@ export function withToolLogging<
|
|
|
74
67
|
return result;
|
|
75
68
|
} catch (error) {
|
|
76
69
|
const durationMs = Date.now() - startTime;
|
|
77
|
-
log.error(
|
|
78
|
-
{ tool: toolName, durationMs, err: error },
|
|
79
|
-
"Tool threw an error",
|
|
80
|
-
);
|
|
70
|
+
log.error({ tool: toolName, durationMs, err: error }, "Tool threw an error");
|
|
81
71
|
throw error;
|
|
82
72
|
}
|
|
83
73
|
};
|