@mhalder/qdrant-mcp-server 3.2.0 → 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 (132) hide show
  1. package/.dagger/.gitattributes +1 -0
  2. package/.dagger/package.json +6 -0
  3. package/.dagger/src/index.ts +83 -0
  4. package/.dagger/tsconfig.json +13 -0
  5. package/.dagger/yarn.lock +8 -0
  6. package/.github/workflows/ci.yml +17 -27
  7. package/.github/workflows/release.yml +16 -19
  8. package/CHANGELOG.md +13 -0
  9. package/README.md +11 -9
  10. package/build/code/chunker/tree-sitter-chunker.d.ts.map +1 -1
  11. package/build/code/chunker/tree-sitter-chunker.js +15 -3
  12. package/build/code/chunker/tree-sitter-chunker.js.map +1 -1
  13. package/build/code/indexer.d.ts +1 -0
  14. package/build/code/indexer.d.ts.map +1 -1
  15. package/build/code/indexer.js +24 -4
  16. package/build/code/indexer.js.map +1 -1
  17. package/build/embeddings/cohere.d.ts +1 -0
  18. package/build/embeddings/cohere.d.ts.map +1 -1
  19. package/build/embeddings/cohere.js +8 -1
  20. package/build/embeddings/cohere.js.map +1 -1
  21. package/build/embeddings/cohere.test.js +11 -0
  22. package/build/embeddings/cohere.test.js.map +1 -1
  23. package/build/embeddings/factory.d.ts.map +1 -1
  24. package/build/embeddings/factory.js +2 -0
  25. package/build/embeddings/factory.js.map +1 -1
  26. package/build/embeddings/factory.test.js +12 -1
  27. package/build/embeddings/factory.test.js.map +1 -1
  28. package/build/embeddings/ollama.d.ts +1 -0
  29. package/build/embeddings/ollama.d.ts.map +1 -1
  30. package/build/embeddings/ollama.js +8 -1
  31. package/build/embeddings/ollama.js.map +1 -1
  32. package/build/embeddings/ollama.test.js +11 -0
  33. package/build/embeddings/ollama.test.js.map +1 -1
  34. package/build/embeddings/openai.d.ts +1 -0
  35. package/build/embeddings/openai.d.ts.map +1 -1
  36. package/build/embeddings/openai.js +8 -1
  37. package/build/embeddings/openai.js.map +1 -1
  38. package/build/embeddings/openai.test.js +11 -0
  39. package/build/embeddings/openai.test.js.map +1 -1
  40. package/build/embeddings/voyage.d.ts +1 -0
  41. package/build/embeddings/voyage.d.ts.map +1 -1
  42. package/build/embeddings/voyage.js +8 -1
  43. package/build/embeddings/voyage.js.map +1 -1
  44. package/build/embeddings/voyage.test.js +11 -0
  45. package/build/embeddings/voyage.test.js.map +1 -1
  46. package/build/git/indexer.d.ts +1 -0
  47. package/build/git/indexer.d.ts.map +1 -1
  48. package/build/git/indexer.js +16 -3
  49. package/build/git/indexer.js.map +1 -1
  50. package/build/git/indexer.test.js +15 -9
  51. package/build/git/indexer.test.js.map +1 -1
  52. package/build/index.js +35 -26
  53. package/build/index.js.map +1 -1
  54. package/build/index.test.js +105 -91
  55. package/build/index.test.js.map +1 -1
  56. package/build/logger.d.ts +4 -0
  57. package/build/logger.d.ts.map +1 -0
  58. package/build/logger.js +24 -0
  59. package/build/logger.js.map +1 -0
  60. package/build/qdrant/client.d.ts +1 -0
  61. package/build/qdrant/client.d.ts.map +1 -1
  62. package/build/qdrant/client.js +10 -0
  63. package/build/qdrant/client.js.map +1 -1
  64. package/build/qdrant/client.test.js +11 -0
  65. package/build/qdrant/client.test.js.map +1 -1
  66. package/build/tools/code.d.ts.map +1 -1
  67. package/build/tools/code.js +44 -13
  68. package/build/tools/code.js.map +1 -1
  69. package/build/tools/collection.d.ts.map +1 -1
  70. package/build/tools/collection.js +15 -8
  71. package/build/tools/collection.js.map +1 -1
  72. package/build/tools/document.d.ts.map +1 -1
  73. package/build/tools/document.js +9 -4
  74. package/build/tools/document.js.map +1 -1
  75. package/build/tools/federated.d.ts.map +1 -1
  76. package/build/tools/federated.js +9 -4
  77. package/build/tools/federated.js.map +1 -1
  78. package/build/tools/federated.test.js +11 -0
  79. package/build/tools/federated.test.js.map +1 -1
  80. package/build/tools/git-history.d.ts.map +1 -1
  81. package/build/tools/git-history.js +44 -12
  82. package/build/tools/git-history.js.map +1 -1
  83. package/build/tools/logging.d.ts +16 -0
  84. package/build/tools/logging.d.ts.map +1 -0
  85. package/build/tools/logging.js +68 -0
  86. package/build/tools/logging.js.map +1 -0
  87. package/build/tools/logging.test.d.ts +2 -0
  88. package/build/tools/logging.test.d.ts.map +1 -0
  89. package/build/tools/logging.test.js +139 -0
  90. package/build/tools/logging.test.js.map +1 -0
  91. package/build/tools/schemas.d.ts +32 -19
  92. package/build/tools/schemas.d.ts.map +1 -1
  93. package/build/tools/schemas.js +9 -3
  94. package/build/tools/schemas.js.map +1 -1
  95. package/build/tools/search.d.ts.map +1 -1
  96. package/build/tools/search.js +13 -4
  97. package/build/tools/search.js.map +1 -1
  98. package/dagger.json +8 -0
  99. package/mise.toml +2 -0
  100. package/package.json +14 -13
  101. package/src/code/chunker/tree-sitter-chunker.ts +41 -9
  102. package/src/code/indexer.ts +41 -6
  103. package/src/embeddings/cohere.test.ts +12 -0
  104. package/src/embeddings/cohere.ts +10 -2
  105. package/src/embeddings/factory.test.ts +13 -1
  106. package/src/embeddings/factory.ts +3 -0
  107. package/src/embeddings/ollama.test.ts +12 -0
  108. package/src/embeddings/ollama.ts +10 -2
  109. package/src/embeddings/openai.test.ts +12 -0
  110. package/src/embeddings/openai.ts +10 -2
  111. package/src/embeddings/voyage.test.ts +12 -0
  112. package/src/embeddings/voyage.ts +10 -2
  113. package/src/git/indexer.test.ts +22 -16
  114. package/src/git/indexer.ts +30 -4
  115. package/src/index.test.ts +128 -106
  116. package/src/index.ts +59 -38
  117. package/src/logger.ts +33 -0
  118. package/src/qdrant/client.test.ts +12 -0
  119. package/src/qdrant/client.ts +22 -0
  120. package/src/tools/code.ts +107 -62
  121. package/src/tools/collection.ts +39 -22
  122. package/src/tools/document.ts +52 -22
  123. package/src/tools/federated.test.ts +12 -0
  124. package/src/tools/federated.ts +143 -125
  125. package/src/tools/git-history.ts +117 -60
  126. package/src/tools/logging.test.ts +206 -0
  127. package/src/tools/logging.ts +85 -0
  128. package/src/tools/schemas.ts +9 -3
  129. package/src/tools/search.ts +93 -71
  130. package/tests/code/chunker/tree-sitter-chunker.test.ts +13 -1
  131. package/tests/code/indexer.test.ts +12 -0
  132. package/tests/code/integration.test.ts +14 -1
@@ -3,10 +3,14 @@
3
3
  */
4
4
 
5
5
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import logger from "../logger.js";
6
7
  import type { EmbeddingProvider } from "../embeddings/base.js";
7
8
  import type { QdrantManager } from "../qdrant/client.js";
9
+ import { withToolLogging } from "./logging.js";
8
10
  import * as schemas from "./schemas.js";
9
11
 
12
+ const log = logger.child({ component: "tools" });
13
+
10
14
  export interface CollectionToolDependencies {
11
15
  qdrant: QdrantManager;
12
16
  embeddings: EmbeddingProvider;
@@ -27,24 +31,31 @@ export function registerCollectionTools(
27
31
  "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.",
28
32
  inputSchema: schemas.CreateCollectionSchema,
29
33
  },
30
- async ({ name, distance, enableHybrid }) => {
31
- const vectorSize = embeddings.getDimensions();
32
- await qdrant.createCollection(
33
- name,
34
- vectorSize,
35
- distance,
36
- enableHybrid || false,
37
- );
34
+ withToolLogging(
35
+ "create_collection",
36
+ async ({ name, distance, enableHybrid }) => {
37
+ log.info(
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
+ );
38
48
 
39
- let message = `Collection "${name}" created successfully with ${vectorSize} dimensions and ${distance || "Cosine"} distance metric.`;
40
- if (enableHybrid) {
41
- message += " Hybrid search is enabled for this collection.";
42
- }
49
+ let message = `Collection "${name}" created successfully with ${vectorSize} dimensions and ${distance || "Cosine"} distance metric.`;
50
+ if (enableHybrid) {
51
+ message += " Hybrid search is enabled for this collection.";
52
+ }
43
53
 
44
- return {
45
- content: [{ type: "text", text: message }],
46
- };
47
- },
54
+ return {
55
+ content: [{ type: "text", text: message }],
56
+ };
57
+ },
58
+ ),
48
59
  );
49
60
 
50
61
  // list_collections
@@ -55,12 +66,13 @@ export function registerCollectionTools(
55
66
  description: "List all available collections in Qdrant.",
56
67
  inputSchema: {},
57
68
  },
58
- async () => {
69
+ withToolLogging("list_collections", async () => {
70
+ log.info({ tool: "list_collections" }, "Tool called");
59
71
  const collections = await qdrant.listCollections();
60
72
  return {
61
73
  content: [{ type: "text", text: JSON.stringify(collections, null, 2) }],
62
74
  };
63
- },
75
+ }),
64
76
  );
65
77
 
66
78
  // get_collection_info
@@ -72,12 +84,16 @@ export function registerCollectionTools(
72
84
  "Get detailed information about a collection including vector size, point count, and distance metric.",
73
85
  inputSchema: schemas.GetCollectionInfoSchema,
74
86
  },
75
- async ({ name }) => {
87
+ withToolLogging("get_collection_info", async ({ name }) => {
88
+ log.info(
89
+ { tool: "get_collection_info", collection: name },
90
+ "Tool called",
91
+ );
76
92
  const info = await qdrant.getCollectionInfo(name);
77
93
  return {
78
94
  content: [{ type: "text", text: JSON.stringify(info, null, 2) }],
79
95
  };
80
- },
96
+ }),
81
97
  );
82
98
 
83
99
  // delete_collection
@@ -88,13 +104,14 @@ export function registerCollectionTools(
88
104
  description: "Delete a collection and all its documents.",
89
105
  inputSchema: schemas.DeleteCollectionSchema,
90
106
  },
91
- async ({ name }) => {
107
+ withToolLogging("delete_collection", async ({ name }) => {
108
+ log.info({ tool: "delete_collection", collection: name }, "Tool called");
92
109
  await qdrant.deleteCollection(name);
93
110
  return {
94
111
  content: [
95
112
  { type: "text", text: `Collection "${name}" deleted successfully.` },
96
113
  ],
97
114
  };
98
- },
115
+ }),
99
116
  );
100
117
  }
@@ -3,11 +3,15 @@
3
3
  */
4
4
 
5
5
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import logger from "../logger.js";
6
7
  import type { EmbeddingProvider } from "../embeddings/base.js";
7
8
  import { BM25SparseVectorGenerator } from "../embeddings/sparse.js";
8
9
  import type { QdrantManager } from "../qdrant/client.js";
10
+ import { withToolLogging } from "./logging.js";
9
11
  import * as schemas from "./schemas.js";
10
12
 
13
+ const log = logger.child({ component: "tools" });
14
+
11
15
  export interface DocumentToolDependencies {
12
16
  qdrant: QdrantManager;
13
17
  embeddings: EmbeddingProvider;
@@ -28,7 +32,11 @@ export function registerDocumentTools(
28
32
  "Add documents to a collection. Documents will be automatically embedded using the configured embedding provider.",
29
33
  inputSchema: schemas.AddDocumentsSchema,
30
34
  },
31
- async ({ collection, documents }) => {
35
+ withToolLogging("add_documents", async ({ collection, documents }) => {
36
+ log.info(
37
+ { tool: "add_documents", collection, count: documents.length },
38
+ "Tool called",
39
+ );
32
40
  // Check if collection exists and get info
33
41
  const exists = await qdrant.collectionExists(collection);
34
42
  if (!exists) {
@@ -46,7 +54,7 @@ export function registerDocumentTools(
46
54
  const collectionInfo = await qdrant.getCollectionInfo(collection);
47
55
 
48
56
  // Generate embeddings for all documents
49
- const texts = documents.map((doc) => doc.text);
57
+ const texts = documents.map((doc: { text: string }) => doc.text);
50
58
  const embeddingResults = await embeddings.embedBatch(texts);
51
59
 
52
60
  // If hybrid search is enabled, generate sparse vectors and use appropriate method
@@ -54,27 +62,45 @@ export function registerDocumentTools(
54
62
  const sparseGenerator = new BM25SparseVectorGenerator();
55
63
 
56
64
  // Prepare points with both dense and sparse vectors
57
- const points = documents.map((doc, index) => ({
58
- id: doc.id,
59
- vector: embeddingResults[index].embedding,
60
- sparseVector: sparseGenerator.generate(doc.text),
61
- payload: {
62
- text: doc.text,
63
- ...doc.metadata,
64
- },
65
- }));
65
+ const points = documents.map(
66
+ (
67
+ doc: {
68
+ id: string | number;
69
+ text: string;
70
+ metadata?: Record<string, any>;
71
+ },
72
+ index: number,
73
+ ) => ({
74
+ id: doc.id,
75
+ vector: embeddingResults[index].embedding,
76
+ sparseVector: sparseGenerator.generate(doc.text),
77
+ payload: {
78
+ text: doc.text,
79
+ ...doc.metadata,
80
+ },
81
+ }),
82
+ );
66
83
 
67
84
  await qdrant.addPointsWithSparse(collection, points);
68
85
  } else {
69
86
  // Standard dense-only vectors
70
- const points = documents.map((doc, index) => ({
71
- id: doc.id,
72
- vector: embeddingResults[index].embedding,
73
- payload: {
74
- text: doc.text,
75
- ...doc.metadata,
76
- },
77
- }));
87
+ const points = documents.map(
88
+ (
89
+ doc: {
90
+ id: string | number;
91
+ text: string;
92
+ metadata?: Record<string, any>;
93
+ },
94
+ index: number,
95
+ ) => ({
96
+ id: doc.id,
97
+ vector: embeddingResults[index].embedding,
98
+ payload: {
99
+ text: doc.text,
100
+ ...doc.metadata,
101
+ },
102
+ }),
103
+ );
78
104
 
79
105
  await qdrant.addPoints(collection, points);
80
106
  }
@@ -87,7 +113,7 @@ export function registerDocumentTools(
87
113
  },
88
114
  ],
89
115
  };
90
- },
116
+ }),
91
117
  );
92
118
 
93
119
  // delete_documents
@@ -98,7 +124,11 @@ export function registerDocumentTools(
98
124
  description: "Delete specific documents from a collection by their IDs.",
99
125
  inputSchema: schemas.DeleteDocumentsSchema,
100
126
  },
101
- async ({ collection, ids }) => {
127
+ withToolLogging("delete_documents", async ({ collection, ids }) => {
128
+ log.info(
129
+ { tool: "delete_documents", collection, count: ids.length },
130
+ "Tool called",
131
+ );
102
132
  await qdrant.deletePoints(collection, ids);
103
133
  return {
104
134
  content: [
@@ -108,6 +138,6 @@ export function registerDocumentTools(
108
138
  },
109
139
  ],
110
140
  };
111
- },
141
+ }),
112
142
  );
113
143
  }
@@ -8,6 +8,18 @@ import {
8
8
  pathsMatch,
9
9
  } from "./federated.js";
10
10
 
11
+ vi.mock("../logger.js", () => ({
12
+ default: {
13
+ info: vi.fn(),
14
+ warn: vi.fn(),
15
+ error: vi.fn(),
16
+ debug: vi.fn(),
17
+ fatal: vi.fn(),
18
+ trace: vi.fn(),
19
+ child: vi.fn().mockReturnThis(),
20
+ },
21
+ }));
22
+
11
23
  // ============================================================================
12
24
  // Unit Tests for Helper Functions
13
25
  // ============================================================================
@@ -7,12 +7,16 @@
7
7
  */
8
8
 
9
9
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
+ import logger from "../logger.js";
10
11
  import type { CodeIndexer } from "../code/indexer.js";
11
12
  import type { CodeSearchResult } from "../code/types.js";
12
13
  import type { GitHistoryIndexer } from "../git/indexer.js";
13
14
  import type { GitSearchResult } from "../git/types.js";
15
+ import { withToolLogging } from "./logging.js";
14
16
  import * as schemas from "./schemas.js";
15
17
 
18
+ const log = logger.child({ component: "tools" });
19
+
16
20
  // ============================================================================
17
21
  // Types
18
22
  // ============================================================================
@@ -412,81 +416,88 @@ export function registerFederatedTools(
412
416
  "modified which files. Useful for understanding code evolution and finding related changes.",
413
417
  inputSchema: schemas.ContextualSearchSchema,
414
418
  },
415
- async ({ path, query, codeLimit, gitLimit, correlate }) => {
416
- try {
417
- const result = await performContextualSearch(
418
- codeIndexer,
419
- gitHistoryIndexer,
420
- { path, query, codeLimit, gitLimit, correlate },
419
+ withToolLogging(
420
+ "contextual_search",
421
+ async ({ path, query, codeLimit, gitLimit, correlate }) => {
422
+ log.info(
423
+ { tool: "contextual_search", path, query: query.substring(0, 80) },
424
+ "Tool called",
421
425
  );
426
+ try {
427
+ const result = await performContextualSearch(
428
+ codeIndexer,
429
+ gitHistoryIndexer,
430
+ { path, query, codeLimit, gitLimit, correlate },
431
+ );
432
+
433
+ // Format output
434
+ const sections: string[] = [];
435
+
436
+ // Code results section
437
+ if (result.codeResults.length > 0) {
438
+ sections.push("## Code Results\n");
439
+ result.codeResults.forEach((r, idx) => {
440
+ sections.push(
441
+ `### ${idx + 1}. ${r.filePath}:${r.startLine}-${r.endLine} (score: ${r.score.toFixed(3)})\n` +
442
+ `Language: ${r.language}\n` +
443
+ "```" +
444
+ r.language +
445
+ "\n" +
446
+ r.content +
447
+ "\n```\n",
448
+ );
449
+ });
450
+ }
422
451
 
423
- // Format output
424
- const sections: string[] = [];
425
-
426
- // Code results section
427
- if (result.codeResults.length > 0) {
428
- sections.push("## Code Results\n");
429
- result.codeResults.forEach((r, idx) => {
430
- sections.push(
431
- `### ${idx + 1}. ${r.filePath}:${r.startLine}-${r.endLine} (score: ${r.score.toFixed(3)})\n` +
432
- `Language: ${r.language}\n` +
433
- "```" +
434
- r.language +
435
- "\n" +
436
- r.content +
437
- "\n```\n",
438
- );
439
- });
440
- }
452
+ // Git results section
453
+ if (result.gitResults.length > 0) {
454
+ sections.push("\n## Git History Results\n");
455
+ result.gitResults.forEach((r, idx) => {
456
+ sections.push(
457
+ `### ${idx + 1}. ${r.shortHash} - ${r.subject} (score: ${r.score.toFixed(3)})\n` +
458
+ `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`,
460
+ );
461
+ });
462
+ }
441
463
 
442
- // Git results section
443
- if (result.gitResults.length > 0) {
444
- sections.push("\n## Git History Results\n");
445
- result.gitResults.forEach((r, idx) => {
446
- sections.push(
447
- `### ${idx + 1}. ${r.shortHash} - ${r.subject} (score: ${r.score.toFixed(3)})\n` +
448
- `Author: ${r.author} | Date: ${r.date} | Type: ${r.commitType}\n` +
449
- `Files: ${r.files.slice(0, 5).join(", ")}${r.files.length > 5 ? ` (+${r.files.length - 5} more)` : ""}\n`,
450
- );
451
- });
452
- }
464
+ // Correlations section
465
+ if (result.correlations.length > 0) {
466
+ sections.push("\n## Correlations (Code ↔ Commits)\n");
467
+ result.correlations.forEach((c) => {
468
+ const commits = c.relatedCommits
469
+ .slice(0, 3)
470
+ .map((commit) => ` - ${commit.shortHash}: ${commit.subject}`)
471
+ .join("\n");
472
+ sections.push(
473
+ `**${c.codeResult.filePath}:${c.codeResult.startLine}** modified by:\n${commits}\n`,
474
+ );
475
+ });
476
+ }
453
477
 
454
- // Correlations section
455
- if (result.correlations.length > 0) {
456
- sections.push("\n## Correlations (Code Commits)\n");
457
- result.correlations.forEach((c) => {
458
- const commits = c.relatedCommits
459
- .slice(0, 3)
460
- .map((commit) => ` - ${commit.shortHash}: ${commit.subject}`)
461
- .join("\n");
462
- sections.push(
463
- `**${c.codeResult.filePath}:${c.codeResult.startLine}** modified by:\n${commits}\n`,
464
- );
465
- });
466
- }
478
+ // Summary
479
+ const summary =
480
+ `\n---\nFound ${result.metadata.codeResultCount} code result(s), ` +
481
+ `${result.metadata.gitResultCount} git result(s), ` +
482
+ `${result.metadata.correlationCount} correlation(s).`;
483
+ sections.push(summary);
467
484
 
468
- // Summary
469
- const summary =
470
- `\n---\nFound ${result.metadata.codeResultCount} code result(s), ` +
471
- `${result.metadata.gitResultCount} git result(s), ` +
472
- `${result.metadata.correlationCount} correlation(s).`;
473
- sections.push(summary);
474
-
475
- return {
476
- content: [{ type: "text", text: sections.join("\n") }],
477
- };
478
- } catch (error) {
479
- return {
480
- content: [
481
- {
482
- type: "text",
483
- text: `Error: ${error instanceof Error ? error.message : String(error)}`,
484
- },
485
- ],
486
- isError: true,
487
- };
488
- }
489
- },
485
+ return {
486
+ content: [{ type: "text", text: sections.join("\n") }],
487
+ };
488
+ } catch (error) {
489
+ return {
490
+ content: [
491
+ {
492
+ type: "text",
493
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
494
+ },
495
+ ],
496
+ isError: true,
497
+ };
498
+ }
499
+ },
500
+ ),
490
501
  );
491
502
 
492
503
  // federated_search
@@ -500,70 +511,77 @@ export function registerFederatedTools(
500
511
  "Supports code-only, git-only, or combined search modes.",
501
512
  inputSchema: schemas.FederatedSearchSchema,
502
513
  },
503
- async ({ paths, query, searchType, limit }) => {
504
- try {
505
- const response = await performFederatedSearch(
506
- codeIndexer,
507
- gitHistoryIndexer,
508
- { paths, query, searchType, limit },
514
+ withToolLogging(
515
+ "federated_search",
516
+ async ({ paths, query, searchType, limit }) => {
517
+ log.info(
518
+ { tool: "federated_search", paths, query: query.substring(0, 80) },
519
+ "Tool called",
509
520
  );
521
+ try {
522
+ const response = await performFederatedSearch(
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
+ }
510
538
 
511
- if (response.results.length === 0) {
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
+ );
569
+
570
+ return {
571
+ content: [{ type: "text", text: sections.join("\n") }],
572
+ };
573
+ } catch (error) {
512
574
  return {
513
575
  content: [
514
576
  {
515
577
  type: "text",
516
- text: `No results found for query "${query}" across ${paths.length} repository(ies).`,
578
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
517
579
  },
518
580
  ],
581
+ isError: true,
519
582
  };
520
583
  }
521
-
522
- // Format results
523
- const sections: string[] = [
524
- `# Federated Search Results\n` +
525
- `Query: "${query}" | Type: ${response.metadata.searchType} | ` +
526
- `Repositories: ${response.metadata.repositoriesSearched.length}\n`,
527
- ];
528
-
529
- response.results.forEach((r, idx) => {
530
- if (r.resultType === "code") {
531
- sections.push(
532
- `## ${idx + 1}. [CODE] ${r.filePath}:${r.startLine}-${r.endLine}\n` +
533
- `Repository: ${r.repoPath} | Language: ${r.language} | Score: ${r.score.toFixed(3)}\n` +
534
- "```" +
535
- r.language +
536
- "\n" +
537
- r.content +
538
- "\n```\n",
539
- );
540
- } else {
541
- sections.push(
542
- `## ${idx + 1}. [GIT] ${r.shortHash} - ${r.subject}\n` +
543
- `Repository: ${r.repoPath} | Author: ${r.author} | Date: ${r.date} | Score: ${r.score.toFixed(3)}\n` +
544
- `Type: ${r.commitType} | Files: ${r.files.slice(0, 3).join(", ")}${r.files.length > 3 ? ` (+${r.files.length - 3} more)` : ""}\n`,
545
- );
546
- }
547
- });
548
-
549
- sections.push(
550
- `\n---\nTotal: ${response.metadata.totalResults} result(s) from ${response.metadata.repositoriesSearched.length} repository(ies).`,
551
- );
552
-
553
- return {
554
- content: [{ type: "text", text: sections.join("\n") }],
555
- };
556
- } catch (error) {
557
- return {
558
- content: [
559
- {
560
- type: "text",
561
- text: `Error: ${error instanceof Error ? error.message : String(error)}`,
562
- },
563
- ],
564
- isError: true,
565
- };
566
- }
567
- },
584
+ },
585
+ ),
568
586
  );
569
587
  }