rag-lite-ts 2.1.0 → 2.2.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 (310) hide show
  1. package/dist/{cli → cjs/cli}/indexer.js +1 -1
  2. package/dist/{cli → cjs/cli}/search.js +5 -10
  3. package/dist/{core → cjs/core}/binary-index-format.d.ts +28 -2
  4. package/dist/cjs/core/binary-index-format.js +291 -0
  5. package/dist/{core → cjs/core}/ingestion.d.ts +5 -1
  6. package/dist/{core → cjs/core}/ingestion.js +76 -9
  7. package/dist/{core → cjs/core}/model-validator.js +1 -1
  8. package/dist/{core → cjs/core}/reranking-strategies.js +4 -5
  9. package/dist/{core → cjs/core}/search.js +2 -1
  10. package/dist/{core → cjs/core}/types.d.ts +1 -1
  11. package/dist/{core → cjs/core}/vector-index.d.ts +4 -0
  12. package/dist/{core → cjs/core}/vector-index.js +10 -2
  13. package/dist/{file-processor.d.ts → cjs/file-processor.d.ts} +2 -0
  14. package/dist/{file-processor.js → cjs/file-processor.js} +20 -0
  15. package/dist/{index-manager.d.ts → cjs/index-manager.d.ts} +17 -1
  16. package/dist/{index-manager.js → cjs/index-manager.js} +148 -7
  17. package/dist/{multimodal → cjs/multimodal}/clip-embedder.js +71 -66
  18. package/dist/esm/api-errors.d.ts +90 -0
  19. package/dist/esm/api-errors.js +320 -0
  20. package/dist/esm/cli/indexer.d.ts +11 -0
  21. package/dist/esm/cli/indexer.js +471 -0
  22. package/dist/esm/cli/search.d.ts +7 -0
  23. package/dist/esm/cli/search.js +332 -0
  24. package/dist/esm/cli.d.ts +3 -0
  25. package/dist/esm/cli.js +529 -0
  26. package/dist/esm/config.d.ts +51 -0
  27. package/dist/esm/config.js +79 -0
  28. package/dist/esm/core/abstract-embedder.d.ts +125 -0
  29. package/dist/esm/core/abstract-embedder.js +264 -0
  30. package/dist/esm/core/actionable-error-messages.d.ts +60 -0
  31. package/dist/esm/core/actionable-error-messages.js +397 -0
  32. package/dist/esm/core/adapters.d.ts +93 -0
  33. package/dist/esm/core/adapters.js +139 -0
  34. package/dist/esm/core/batch-processing-optimizer.d.ts +155 -0
  35. package/dist/esm/core/batch-processing-optimizer.js +536 -0
  36. package/dist/esm/core/binary-index-format.d.ts +78 -0
  37. package/dist/esm/core/binary-index-format.js +291 -0
  38. package/dist/esm/core/chunker.d.ts +119 -0
  39. package/dist/esm/core/chunker.js +73 -0
  40. package/dist/esm/core/cli-database-utils.d.ts +53 -0
  41. package/dist/esm/core/cli-database-utils.js +239 -0
  42. package/dist/esm/core/config.d.ts +102 -0
  43. package/dist/esm/core/config.js +247 -0
  44. package/dist/esm/core/content-errors.d.ts +111 -0
  45. package/dist/esm/core/content-errors.js +362 -0
  46. package/dist/esm/core/content-manager.d.ts +335 -0
  47. package/dist/esm/core/content-manager.js +1476 -0
  48. package/dist/esm/core/content-performance-optimizer.d.ts +150 -0
  49. package/dist/esm/core/content-performance-optimizer.js +516 -0
  50. package/dist/esm/core/content-resolver.d.ts +104 -0
  51. package/dist/esm/core/content-resolver.js +285 -0
  52. package/dist/esm/core/cross-modal-search.d.ts +164 -0
  53. package/dist/esm/core/cross-modal-search.js +342 -0
  54. package/dist/esm/core/database-connection-manager.d.ts +109 -0
  55. package/dist/esm/core/database-connection-manager.js +310 -0
  56. package/dist/esm/core/db.d.ts +213 -0
  57. package/dist/esm/core/db.js +895 -0
  58. package/dist/esm/core/embedder-factory.d.ts +154 -0
  59. package/dist/esm/core/embedder-factory.js +311 -0
  60. package/dist/esm/core/error-handler.d.ts +112 -0
  61. package/dist/esm/core/error-handler.js +239 -0
  62. package/dist/esm/core/index.d.ts +59 -0
  63. package/dist/esm/core/index.js +69 -0
  64. package/dist/esm/core/ingestion.d.ts +202 -0
  65. package/dist/esm/core/ingestion.js +901 -0
  66. package/dist/esm/core/interfaces.d.ts +408 -0
  67. package/dist/esm/core/interfaces.js +106 -0
  68. package/dist/esm/core/lazy-dependency-loader.d.ts +147 -0
  69. package/dist/esm/core/lazy-dependency-loader.js +435 -0
  70. package/dist/esm/core/mode-detection-service.d.ts +150 -0
  71. package/dist/esm/core/mode-detection-service.js +565 -0
  72. package/dist/esm/core/mode-model-validator.d.ts +92 -0
  73. package/dist/esm/core/mode-model-validator.js +203 -0
  74. package/dist/esm/core/model-registry.d.ts +116 -0
  75. package/dist/esm/core/model-registry.js +411 -0
  76. package/dist/esm/core/model-validator.d.ts +217 -0
  77. package/dist/esm/core/model-validator.js +782 -0
  78. package/dist/esm/core/path-manager.d.ts +47 -0
  79. package/dist/esm/core/path-manager.js +71 -0
  80. package/dist/esm/core/raglite-paths.d.ts +121 -0
  81. package/dist/esm/core/raglite-paths.js +145 -0
  82. package/dist/esm/core/reranking-config.d.ts +42 -0
  83. package/dist/esm/core/reranking-config.js +147 -0
  84. package/dist/esm/core/reranking-factory.d.ts +92 -0
  85. package/dist/esm/core/reranking-factory.js +410 -0
  86. package/dist/esm/core/reranking-strategies.d.ts +310 -0
  87. package/dist/esm/core/reranking-strategies.js +650 -0
  88. package/dist/esm/core/resource-cleanup.d.ts +163 -0
  89. package/dist/esm/core/resource-cleanup.js +371 -0
  90. package/dist/esm/core/resource-manager.d.ts +212 -0
  91. package/dist/esm/core/resource-manager.js +564 -0
  92. package/dist/esm/core/search-pipeline.d.ts +111 -0
  93. package/dist/esm/core/search-pipeline.js +287 -0
  94. package/dist/esm/core/search.d.ts +141 -0
  95. package/dist/esm/core/search.js +320 -0
  96. package/dist/esm/core/streaming-operations.d.ts +145 -0
  97. package/dist/esm/core/streaming-operations.js +409 -0
  98. package/dist/esm/core/types.d.ts +66 -0
  99. package/dist/esm/core/types.js +6 -0
  100. package/dist/esm/core/universal-embedder.d.ts +177 -0
  101. package/dist/esm/core/universal-embedder.js +139 -0
  102. package/dist/esm/core/validation-messages.d.ts +99 -0
  103. package/dist/esm/core/validation-messages.js +334 -0
  104. package/dist/esm/core/vector-index.d.ts +72 -0
  105. package/dist/esm/core/vector-index.js +333 -0
  106. package/dist/esm/dom-polyfills.d.ts +6 -0
  107. package/dist/esm/dom-polyfills.js +37 -0
  108. package/dist/esm/factories/index.d.ts +27 -0
  109. package/dist/esm/factories/index.js +29 -0
  110. package/dist/esm/factories/ingestion-factory.d.ts +200 -0
  111. package/dist/esm/factories/ingestion-factory.js +477 -0
  112. package/dist/esm/factories/search-factory.d.ts +154 -0
  113. package/dist/esm/factories/search-factory.js +344 -0
  114. package/dist/esm/file-processor.d.ts +147 -0
  115. package/dist/esm/file-processor.js +963 -0
  116. package/dist/esm/index-manager.d.ts +116 -0
  117. package/dist/esm/index-manager.js +598 -0
  118. package/dist/esm/index.d.ts +75 -0
  119. package/dist/esm/index.js +110 -0
  120. package/dist/esm/indexer.d.ts +7 -0
  121. package/dist/esm/indexer.js +54 -0
  122. package/dist/esm/ingestion.d.ts +63 -0
  123. package/dist/esm/ingestion.js +124 -0
  124. package/dist/esm/mcp-server.d.ts +46 -0
  125. package/dist/esm/mcp-server.js +1820 -0
  126. package/dist/esm/multimodal/clip-embedder.d.ts +327 -0
  127. package/dist/esm/multimodal/clip-embedder.js +996 -0
  128. package/dist/esm/multimodal/index.d.ts +6 -0
  129. package/dist/esm/multimodal/index.js +6 -0
  130. package/dist/esm/preprocess.d.ts +19 -0
  131. package/dist/esm/preprocess.js +203 -0
  132. package/dist/esm/preprocessors/index.d.ts +17 -0
  133. package/dist/esm/preprocessors/index.js +38 -0
  134. package/dist/esm/preprocessors/mdx.d.ts +25 -0
  135. package/dist/esm/preprocessors/mdx.js +101 -0
  136. package/dist/esm/preprocessors/mermaid.d.ts +68 -0
  137. package/dist/esm/preprocessors/mermaid.js +329 -0
  138. package/dist/esm/preprocessors/registry.d.ts +56 -0
  139. package/dist/esm/preprocessors/registry.js +179 -0
  140. package/dist/esm/run-error-recovery-tests.d.ts +7 -0
  141. package/dist/esm/run-error-recovery-tests.js +101 -0
  142. package/dist/esm/search-standalone.d.ts +7 -0
  143. package/dist/esm/search-standalone.js +117 -0
  144. package/dist/esm/search.d.ts +99 -0
  145. package/dist/esm/search.js +177 -0
  146. package/dist/esm/test-utils.d.ts +18 -0
  147. package/dist/esm/test-utils.js +27 -0
  148. package/dist/esm/text/chunker.d.ts +33 -0
  149. package/dist/esm/text/chunker.js +279 -0
  150. package/dist/esm/text/embedder.d.ts +111 -0
  151. package/dist/esm/text/embedder.js +386 -0
  152. package/dist/esm/text/index.d.ts +8 -0
  153. package/dist/esm/text/index.js +9 -0
  154. package/dist/esm/text/preprocessors/index.d.ts +17 -0
  155. package/dist/esm/text/preprocessors/index.js +38 -0
  156. package/dist/esm/text/preprocessors/mdx.d.ts +25 -0
  157. package/dist/esm/text/preprocessors/mdx.js +101 -0
  158. package/dist/esm/text/preprocessors/mermaid.d.ts +68 -0
  159. package/dist/esm/text/preprocessors/mermaid.js +330 -0
  160. package/dist/esm/text/preprocessors/registry.d.ts +56 -0
  161. package/dist/esm/text/preprocessors/registry.js +180 -0
  162. package/dist/esm/text/reranker.d.ts +49 -0
  163. package/dist/esm/text/reranker.js +274 -0
  164. package/dist/esm/text/sentence-transformer-embedder.d.ts +96 -0
  165. package/dist/esm/text/sentence-transformer-embedder.js +340 -0
  166. package/dist/esm/text/tokenizer.d.ts +22 -0
  167. package/dist/esm/text/tokenizer.js +64 -0
  168. package/dist/esm/types.d.ts +83 -0
  169. package/dist/esm/types.js +3 -0
  170. package/dist/esm/utils/vector-math.d.ts +31 -0
  171. package/dist/esm/utils/vector-math.js +70 -0
  172. package/package.json +30 -12
  173. package/dist/core/binary-index-format.js +0 -122
  174. /package/dist/{api-errors.d.ts → cjs/api-errors.d.ts} +0 -0
  175. /package/dist/{api-errors.js → cjs/api-errors.js} +0 -0
  176. /package/dist/{cli → cjs/cli}/indexer.d.ts +0 -0
  177. /package/dist/{cli → cjs/cli}/search.d.ts +0 -0
  178. /package/dist/{cli.d.ts → cjs/cli.d.ts} +0 -0
  179. /package/dist/{cli.js → cjs/cli.js} +0 -0
  180. /package/dist/{config.d.ts → cjs/config.d.ts} +0 -0
  181. /package/dist/{config.js → cjs/config.js} +0 -0
  182. /package/dist/{core → cjs/core}/abstract-embedder.d.ts +0 -0
  183. /package/dist/{core → cjs/core}/abstract-embedder.js +0 -0
  184. /package/dist/{core → cjs/core}/actionable-error-messages.d.ts +0 -0
  185. /package/dist/{core → cjs/core}/actionable-error-messages.js +0 -0
  186. /package/dist/{core → cjs/core}/adapters.d.ts +0 -0
  187. /package/dist/{core → cjs/core}/adapters.js +0 -0
  188. /package/dist/{core → cjs/core}/batch-processing-optimizer.d.ts +0 -0
  189. /package/dist/{core → cjs/core}/batch-processing-optimizer.js +0 -0
  190. /package/dist/{core → cjs/core}/chunker.d.ts +0 -0
  191. /package/dist/{core → cjs/core}/chunker.js +0 -0
  192. /package/dist/{core → cjs/core}/cli-database-utils.d.ts +0 -0
  193. /package/dist/{core → cjs/core}/cli-database-utils.js +0 -0
  194. /package/dist/{core → cjs/core}/config.d.ts +0 -0
  195. /package/dist/{core → cjs/core}/config.js +0 -0
  196. /package/dist/{core → cjs/core}/content-errors.d.ts +0 -0
  197. /package/dist/{core → cjs/core}/content-errors.js +0 -0
  198. /package/dist/{core → cjs/core}/content-manager.d.ts +0 -0
  199. /package/dist/{core → cjs/core}/content-manager.js +0 -0
  200. /package/dist/{core → cjs/core}/content-performance-optimizer.d.ts +0 -0
  201. /package/dist/{core → cjs/core}/content-performance-optimizer.js +0 -0
  202. /package/dist/{core → cjs/core}/content-resolver.d.ts +0 -0
  203. /package/dist/{core → cjs/core}/content-resolver.js +0 -0
  204. /package/dist/{core → cjs/core}/cross-modal-search.d.ts +0 -0
  205. /package/dist/{core → cjs/core}/cross-modal-search.js +0 -0
  206. /package/dist/{core → cjs/core}/database-connection-manager.d.ts +0 -0
  207. /package/dist/{core → cjs/core}/database-connection-manager.js +0 -0
  208. /package/dist/{core → cjs/core}/db.d.ts +0 -0
  209. /package/dist/{core → cjs/core}/db.js +0 -0
  210. /package/dist/{core → cjs/core}/embedder-factory.d.ts +0 -0
  211. /package/dist/{core → cjs/core}/embedder-factory.js +0 -0
  212. /package/dist/{core → cjs/core}/error-handler.d.ts +0 -0
  213. /package/dist/{core → cjs/core}/error-handler.js +0 -0
  214. /package/dist/{core → cjs/core}/index.d.ts +0 -0
  215. /package/dist/{core → cjs/core}/index.js +0 -0
  216. /package/dist/{core → cjs/core}/interfaces.d.ts +0 -0
  217. /package/dist/{core → cjs/core}/interfaces.js +0 -0
  218. /package/dist/{core → cjs/core}/lazy-dependency-loader.d.ts +0 -0
  219. /package/dist/{core → cjs/core}/lazy-dependency-loader.js +0 -0
  220. /package/dist/{core → cjs/core}/mode-detection-service.d.ts +0 -0
  221. /package/dist/{core → cjs/core}/mode-detection-service.js +0 -0
  222. /package/dist/{core → cjs/core}/mode-model-validator.d.ts +0 -0
  223. /package/dist/{core → cjs/core}/mode-model-validator.js +0 -0
  224. /package/dist/{core → cjs/core}/model-registry.d.ts +0 -0
  225. /package/dist/{core → cjs/core}/model-registry.js +0 -0
  226. /package/dist/{core → cjs/core}/model-validator.d.ts +0 -0
  227. /package/dist/{core → cjs/core}/path-manager.d.ts +0 -0
  228. /package/dist/{core → cjs/core}/path-manager.js +0 -0
  229. /package/dist/{core → cjs/core}/raglite-paths.d.ts +0 -0
  230. /package/dist/{core → cjs/core}/raglite-paths.js +0 -0
  231. /package/dist/{core → cjs/core}/reranking-config.d.ts +0 -0
  232. /package/dist/{core → cjs/core}/reranking-config.js +0 -0
  233. /package/dist/{core → cjs/core}/reranking-factory.d.ts +0 -0
  234. /package/dist/{core → cjs/core}/reranking-factory.js +0 -0
  235. /package/dist/{core → cjs/core}/reranking-strategies.d.ts +0 -0
  236. /package/dist/{core → cjs/core}/resource-cleanup.d.ts +0 -0
  237. /package/dist/{core → cjs/core}/resource-cleanup.js +0 -0
  238. /package/dist/{core → cjs/core}/resource-manager.d.ts +0 -0
  239. /package/dist/{core → cjs/core}/resource-manager.js +0 -0
  240. /package/dist/{core → cjs/core}/search-pipeline.d.ts +0 -0
  241. /package/dist/{core → cjs/core}/search-pipeline.js +0 -0
  242. /package/dist/{core → cjs/core}/search.d.ts +0 -0
  243. /package/dist/{core → cjs/core}/streaming-operations.d.ts +0 -0
  244. /package/dist/{core → cjs/core}/streaming-operations.js +0 -0
  245. /package/dist/{core → cjs/core}/types.js +0 -0
  246. /package/dist/{core → cjs/core}/universal-embedder.d.ts +0 -0
  247. /package/dist/{core → cjs/core}/universal-embedder.js +0 -0
  248. /package/dist/{core → cjs/core}/validation-messages.d.ts +0 -0
  249. /package/dist/{core → cjs/core}/validation-messages.js +0 -0
  250. /package/dist/{dom-polyfills.d.ts → cjs/dom-polyfills.d.ts} +0 -0
  251. /package/dist/{dom-polyfills.js → cjs/dom-polyfills.js} +0 -0
  252. /package/dist/{factories → cjs/factories}/index.d.ts +0 -0
  253. /package/dist/{factories → cjs/factories}/index.js +0 -0
  254. /package/dist/{factories → cjs/factories}/ingestion-factory.d.ts +0 -0
  255. /package/dist/{factories → cjs/factories}/ingestion-factory.js +0 -0
  256. /package/dist/{factories → cjs/factories}/search-factory.d.ts +0 -0
  257. /package/dist/{factories → cjs/factories}/search-factory.js +0 -0
  258. /package/dist/{index.d.ts → cjs/index.d.ts} +0 -0
  259. /package/dist/{index.js → cjs/index.js} +0 -0
  260. /package/dist/{indexer.d.ts → cjs/indexer.d.ts} +0 -0
  261. /package/dist/{indexer.js → cjs/indexer.js} +0 -0
  262. /package/dist/{ingestion.d.ts → cjs/ingestion.d.ts} +0 -0
  263. /package/dist/{ingestion.js → cjs/ingestion.js} +0 -0
  264. /package/dist/{mcp-server.d.ts → cjs/mcp-server.d.ts} +0 -0
  265. /package/dist/{mcp-server.js → cjs/mcp-server.js} +0 -0
  266. /package/dist/{multimodal → cjs/multimodal}/clip-embedder.d.ts +0 -0
  267. /package/dist/{multimodal → cjs/multimodal}/index.d.ts +0 -0
  268. /package/dist/{multimodal → cjs/multimodal}/index.js +0 -0
  269. /package/dist/{preprocess.d.ts → cjs/preprocess.d.ts} +0 -0
  270. /package/dist/{preprocess.js → cjs/preprocess.js} +0 -0
  271. /package/dist/{preprocessors → cjs/preprocessors}/index.d.ts +0 -0
  272. /package/dist/{preprocessors → cjs/preprocessors}/index.js +0 -0
  273. /package/dist/{preprocessors → cjs/preprocessors}/mdx.d.ts +0 -0
  274. /package/dist/{preprocessors → cjs/preprocessors}/mdx.js +0 -0
  275. /package/dist/{preprocessors → cjs/preprocessors}/mermaid.d.ts +0 -0
  276. /package/dist/{preprocessors → cjs/preprocessors}/mermaid.js +0 -0
  277. /package/dist/{preprocessors → cjs/preprocessors}/registry.d.ts +0 -0
  278. /package/dist/{preprocessors → cjs/preprocessors}/registry.js +0 -0
  279. /package/dist/{run-error-recovery-tests.d.ts → cjs/run-error-recovery-tests.d.ts} +0 -0
  280. /package/dist/{run-error-recovery-tests.js → cjs/run-error-recovery-tests.js} +0 -0
  281. /package/dist/{search-standalone.d.ts → cjs/search-standalone.d.ts} +0 -0
  282. /package/dist/{search-standalone.js → cjs/search-standalone.js} +0 -0
  283. /package/dist/{search.d.ts → cjs/search.d.ts} +0 -0
  284. /package/dist/{search.js → cjs/search.js} +0 -0
  285. /package/dist/{test-utils.d.ts → cjs/test-utils.d.ts} +0 -0
  286. /package/dist/{test-utils.js → cjs/test-utils.js} +0 -0
  287. /package/dist/{text → cjs/text}/chunker.d.ts +0 -0
  288. /package/dist/{text → cjs/text}/chunker.js +0 -0
  289. /package/dist/{text → cjs/text}/embedder.d.ts +0 -0
  290. /package/dist/{text → cjs/text}/embedder.js +0 -0
  291. /package/dist/{text → cjs/text}/index.d.ts +0 -0
  292. /package/dist/{text → cjs/text}/index.js +0 -0
  293. /package/dist/{text → cjs/text}/preprocessors/index.d.ts +0 -0
  294. /package/dist/{text → cjs/text}/preprocessors/index.js +0 -0
  295. /package/dist/{text → cjs/text}/preprocessors/mdx.d.ts +0 -0
  296. /package/dist/{text → cjs/text}/preprocessors/mdx.js +0 -0
  297. /package/dist/{text → cjs/text}/preprocessors/mermaid.d.ts +0 -0
  298. /package/dist/{text → cjs/text}/preprocessors/mermaid.js +0 -0
  299. /package/dist/{text → cjs/text}/preprocessors/registry.d.ts +0 -0
  300. /package/dist/{text → cjs/text}/preprocessors/registry.js +0 -0
  301. /package/dist/{text → cjs/text}/reranker.d.ts +0 -0
  302. /package/dist/{text → cjs/text}/reranker.js +0 -0
  303. /package/dist/{text → cjs/text}/sentence-transformer-embedder.d.ts +0 -0
  304. /package/dist/{text → cjs/text}/sentence-transformer-embedder.js +0 -0
  305. /package/dist/{text → cjs/text}/tokenizer.d.ts +0 -0
  306. /package/dist/{text → cjs/text}/tokenizer.js +0 -0
  307. /package/dist/{types.d.ts → cjs/types.d.ts} +0 -0
  308. /package/dist/{types.js → cjs/types.js} +0 -0
  309. /package/dist/{utils → cjs/utils}/vector-math.d.ts +0 -0
  310. /package/dist/{utils → cjs/utils}/vector-math.js +0 -0
@@ -0,0 +1,1820 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP server entry point for rag-lite-ts with Chameleon Multimodal Architecture
4
+ *
5
+ * This is a thin wrapper around the polymorphic search and ingestion functions
6
+ * that exposes them as MCP tools without creating REST/GraphQL endpoints.
7
+ *
8
+ * The MCP server supports both text-only and multimodal modes:
9
+ * - Text mode: Optimized for text documents using sentence-transformer models
10
+ * - Multimodal mode: Supports mixed text and image content using CLIP models
11
+ *
12
+ * Key Features:
13
+ * - Automatic mode detection from database configuration
14
+ * - Polymorphic runtime that adapts to stored mode settings
15
+ * - Support for multiple embedding models and reranking strategies
16
+ * - Content type filtering and multimodal search capabilities
17
+ * - Comprehensive model and strategy information tools
18
+ *
19
+ * The MCP server lives in the same package as CLI with dual entry points
20
+ * and provides proper MCP tool definitions for search and indexing capabilities.
21
+ *
22
+ * Requirements addressed: 6.2, 6.4, 6.5, 6.6, 9.1, 9.2, 9.3
23
+ */
24
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
25
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
26
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
27
+ import { existsSync, statSync, createWriteStream } from 'fs';
28
+ import { resolve } from 'path';
29
+ import { SearchFactory } from './factories/search-factory.js';
30
+ import { IngestionFactory } from './factories/ingestion-factory.js';
31
+ import { getSystemInfo } from './core/db.js';
32
+ import { DatabaseConnectionManager } from './core/database-connection-manager.js';
33
+ import { config, validateCoreConfig, ConfigurationError } from './core/config.js';
34
+ /**
35
+ * Detect MIME type from file path or extension
36
+ */
37
+ function getMimeTypeFromPath(filePath) {
38
+ const ext = filePath.toLowerCase().split('.').pop() || '';
39
+ const mimeTypes = {
40
+ 'jpg': 'image/jpeg',
41
+ 'jpeg': 'image/jpeg',
42
+ 'png': 'image/png',
43
+ 'gif': 'image/gif',
44
+ 'webp': 'image/webp',
45
+ 'bmp': 'image/bmp',
46
+ 'svg': 'image/svg+xml'
47
+ };
48
+ return mimeTypes[ext] || 'image/jpeg'; // Default to JPEG if unknown
49
+ }
50
+ /**
51
+ * MCP Server class that wraps RAG-lite TS functionality
52
+ * Implements MCP protocol interface without creating REST/GraphQL endpoints
53
+ */
54
+ class RagLiteMCPServer {
55
+ server;
56
+ searchEngine = null;
57
+ isSearchEngineInitialized = false;
58
+ constructor() {
59
+ this.server = new Server({
60
+ name: 'rag-lite-ts',
61
+ version: '1.0.0',
62
+ }, {
63
+ capabilities: {
64
+ tools: {},
65
+ },
66
+ });
67
+ this.setupToolHandlers();
68
+ }
69
+ /**
70
+ * Set up MCP tool handlers for search and indexing capabilities
71
+ * Add proper MCP tool definitions for search and indexing capabilities
72
+ */
73
+ setupToolHandlers() {
74
+ // List available tools - with dynamic descriptions based on database mode
75
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
76
+ // Detect current mode and capabilities from database
77
+ const capabilities = await this.detectCapabilities();
78
+ return {
79
+ tools: [
80
+ {
81
+ name: 'search',
82
+ description: this.generateSearchDescription(capabilities),
83
+ inputSchema: {
84
+ type: 'object',
85
+ properties: {
86
+ query: {
87
+ type: 'string',
88
+ description: 'Search query string to find relevant documents',
89
+ minLength: 1,
90
+ maxLength: 500
91
+ },
92
+ top_k: {
93
+ type: 'number',
94
+ description: 'Number of results to return (default: 10, max: 100)',
95
+ minimum: 1,
96
+ maximum: 100,
97
+ default: 10
98
+ },
99
+ rerank: {
100
+ type: 'boolean',
101
+ description: 'Enable reranking for better result quality (default: false)',
102
+ default: false
103
+ }
104
+ },
105
+ required: ['query'],
106
+ additionalProperties: false
107
+ }
108
+ },
109
+ {
110
+ name: 'ingest',
111
+ description: 'Ingest documents from a file or directory path. Supports both text-only and multimodal modes. In text mode, processes .md and .txt files. In multimodal mode, also processes images (.jpg, .png, .gif, .webp).',
112
+ inputSchema: {
113
+ type: 'object',
114
+ properties: {
115
+ path: {
116
+ type: 'string',
117
+ description: 'File or directory path to ingest. Can be a single file or directory containing supported files.'
118
+ },
119
+ mode: {
120
+ type: 'string',
121
+ description: 'Processing mode: text for text-only content, multimodal for mixed text and image content (default: text)',
122
+ enum: ['text', 'multimodal'],
123
+ default: 'text'
124
+ },
125
+ model: {
126
+ type: 'string',
127
+ description: 'Embedding model to use. For text mode: sentence-transformers/all-MiniLM-L6-v2 (default), Xenova/all-mpnet-base-v2. For multimodal mode: Xenova/clip-vit-base-patch32 (default), Xenova/clip-vit-base-patch16',
128
+ enum: [
129
+ 'sentence-transformers/all-MiniLM-L6-v2',
130
+ 'Xenova/all-mpnet-base-v2',
131
+ 'Xenova/clip-vit-base-patch32',
132
+ 'Xenova/clip-vit-base-patch16'
133
+ ]
134
+ },
135
+ rerank_strategy: {
136
+ type: 'string',
137
+ description: 'Reranking strategy for multimodal mode. Options: text-derived (default), disabled',
138
+ enum: ['text-derived', 'disabled']
139
+ },
140
+ force_rebuild: {
141
+ type: 'boolean',
142
+ description: 'Force rebuild of the entire index (default: false)',
143
+ default: false
144
+ }
145
+ },
146
+ required: ['path'],
147
+ additionalProperties: false
148
+ }
149
+ },
150
+ {
151
+ name: 'ingest_image',
152
+ description: 'Ingest image files or image URLs into the multimodal index. Automatically sets mode to multimodal and uses CLIP embeddings. Supports local image files and remote image URLs (http/https).',
153
+ inputSchema: {
154
+ type: 'object',
155
+ properties: {
156
+ source: {
157
+ type: 'string',
158
+ description: 'Image file path or URL. Can be a local file path (e.g., ./image.jpg) or a remote URL (e.g., https://example.com/image.jpg). Supported formats: jpg, jpeg, png, gif, webp.'
159
+ },
160
+ model: {
161
+ type: 'string',
162
+ description: 'CLIP model to use for image embedding (default: Xenova/clip-vit-base-patch32)',
163
+ enum: [
164
+ 'Xenova/clip-vit-base-patch32',
165
+ 'Xenova/clip-vit-base-patch16'
166
+ ],
167
+ default: 'Xenova/clip-vit-base-patch32'
168
+ },
169
+ rerank_strategy: {
170
+ type: 'string',
171
+ description: 'Reranking strategy for multimodal mode. Options: text-derived (default), disabled',
172
+ enum: ['text-derived', 'disabled'],
173
+ default: 'text-derived'
174
+ },
175
+ title: {
176
+ type: 'string',
177
+ description: 'Optional title for the image. If not provided, will use filename or URL.'
178
+ },
179
+ metadata: {
180
+ type: 'object',
181
+ description: 'Optional metadata to associate with the image (e.g., tags, description, source info)'
182
+ }
183
+ },
184
+ required: ['source'],
185
+ additionalProperties: false
186
+ }
187
+ },
188
+ {
189
+ name: 'rebuild_index',
190
+ description: 'Rebuild the entire vector index from scratch. Useful when model version changes or for maintenance. This will regenerate all embeddings.',
191
+ inputSchema: {
192
+ type: 'object',
193
+ properties: {},
194
+ additionalProperties: false
195
+ }
196
+ },
197
+ {
198
+ name: 'get_stats',
199
+ description: 'Get statistics about the current search index including number of documents, chunks, and index status.',
200
+ inputSchema: {
201
+ type: 'object',
202
+ properties: {},
203
+ additionalProperties: false
204
+ }
205
+ },
206
+ {
207
+ name: 'get_mode_info',
208
+ description: 'Get current system mode and configuration information including detected mode, model, and reranking strategy.',
209
+ inputSchema: {
210
+ type: 'object',
211
+ properties: {},
212
+ additionalProperties: false
213
+ }
214
+ },
215
+ {
216
+ name: 'multimodal_search',
217
+ description: 'Search indexed documents with multimodal capabilities and content type filtering. Returns relevant document chunks with content type information. Image results include base64-encoded image data for display in MCP clients. Supports cross-modal search in multimodal mode (text queries can find images, image queries can find text).',
218
+ inputSchema: {
219
+ type: 'object',
220
+ properties: {
221
+ query: {
222
+ type: 'string',
223
+ description: 'Search query string to find relevant documents. In multimodal mode, text queries can find semantically similar images.',
224
+ minLength: 1,
225
+ maxLength: 500
226
+ },
227
+ top_k: {
228
+ type: 'number',
229
+ description: 'Number of results to return (default: 10, max: 100)',
230
+ minimum: 1,
231
+ maximum: 100,
232
+ default: 10
233
+ },
234
+ rerank: {
235
+ type: 'boolean',
236
+ description: 'Enable reranking for better result quality (default: false)',
237
+ default: false
238
+ },
239
+ content_type: {
240
+ type: 'string',
241
+ description: 'Filter results by content type (text, image, pdf, docx). If not specified, returns all content types. Use "image" to find only images, "text" for only text.',
242
+ enum: ['text', 'image', 'pdf', 'docx']
243
+ }
244
+ },
245
+ required: ['query'],
246
+ additionalProperties: false
247
+ }
248
+ },
249
+ {
250
+ name: 'list_supported_models',
251
+ description: 'List all supported embedding models with their capabilities, dimensions, and supported content types.',
252
+ inputSchema: {
253
+ type: 'object',
254
+ properties: {
255
+ model_type: {
256
+ type: 'string',
257
+ description: 'Filter models by type (sentence-transformer, clip). If not specified, returns all models.',
258
+ enum: ['sentence-transformer', 'clip']
259
+ },
260
+ content_type: {
261
+ type: 'string',
262
+ description: 'Filter models by supported content type (text, image). If not specified, returns all models.',
263
+ enum: ['text', 'image']
264
+ }
265
+ },
266
+ additionalProperties: false
267
+ }
268
+ },
269
+ {
270
+ name: 'list_reranking_strategies',
271
+ description: 'List all supported reranking strategies for different modes with their descriptions and requirements.',
272
+ inputSchema: {
273
+ type: 'object',
274
+ properties: {
275
+ mode: {
276
+ type: 'string',
277
+ description: 'Filter strategies by mode (text, multimodal). If not specified, returns strategies for all modes.',
278
+ enum: ['text', 'multimodal']
279
+ }
280
+ },
281
+ additionalProperties: false
282
+ }
283
+ },
284
+ {
285
+ name: 'get_system_stats',
286
+ description: 'Get comprehensive system statistics including mode-specific metrics, performance data, and resource usage.',
287
+ inputSchema: {
288
+ type: 'object',
289
+ properties: {
290
+ include_performance: {
291
+ type: 'boolean',
292
+ description: 'Include performance metrics and timing data (default: false)',
293
+ default: false
294
+ },
295
+ include_content_breakdown: {
296
+ type: 'boolean',
297
+ description: 'Include breakdown of content by type (default: false)',
298
+ default: false
299
+ }
300
+ },
301
+ additionalProperties: false
302
+ }
303
+ }
304
+ ],
305
+ };
306
+ });
307
+ // Handle tool calls
308
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
309
+ const { name, arguments: args } = request.params;
310
+ try {
311
+ switch (name) {
312
+ case 'search':
313
+ return await this.handleSearch(args);
314
+ case 'ingest':
315
+ return await this.handleIngest(args);
316
+ case 'ingest_image':
317
+ return await this.handleIngestImage(args);
318
+ case 'rebuild_index':
319
+ return await this.handleRebuildIndex(args);
320
+ case 'get_stats':
321
+ return await this.handleGetStats(args);
322
+ case 'get_mode_info':
323
+ return await this.handleGetModeInfo(args);
324
+ case 'multimodal_search':
325
+ return await this.handleMultimodalSearch(args);
326
+ case 'list_supported_models':
327
+ return await this.handleListSupportedModels(args);
328
+ case 'list_reranking_strategies':
329
+ return await this.handleListRerankingStrategies(args);
330
+ case 'get_system_stats':
331
+ return await this.handleGetSystemStats(args);
332
+ default:
333
+ throw new Error(`Unknown tool: ${name}`);
334
+ }
335
+ }
336
+ catch (error) {
337
+ const errorMessage = error instanceof Error ? error.message : String(error);
338
+ return {
339
+ content: [
340
+ {
341
+ type: 'text',
342
+ text: `Error: ${errorMessage}`,
343
+ },
344
+ ],
345
+ };
346
+ }
347
+ });
348
+ }
349
+ /**
350
+ * Handle search tool calls
351
+ * Wraps existing search functionality as MCP tool
352
+ */
353
+ async handleSearch(args) {
354
+ try {
355
+ // Validate arguments
356
+ if (!args.query || typeof args.query !== 'string') {
357
+ throw new Error('Query parameter is required and must be a string');
358
+ }
359
+ if (args.query.trim().length === 0) {
360
+ throw new Error('Query cannot be empty');
361
+ }
362
+ if (args.query.length > 500) {
363
+ throw new Error('Query is too long (maximum 500 characters)');
364
+ }
365
+ // Validate optional parameters
366
+ if (args.top_k !== undefined) {
367
+ if (typeof args.top_k !== 'number' || args.top_k < 1 || args.top_k > 100) {
368
+ throw new Error('top_k must be a number between 1 and 100');
369
+ }
370
+ }
371
+ if (args.rerank !== undefined && typeof args.rerank !== 'boolean') {
372
+ throw new Error('rerank must be a boolean');
373
+ }
374
+ // Check if database and index exist
375
+ if (!existsSync(config.db_file)) {
376
+ throw new Error('No database found. You need to ingest documents first using the ingest tool.');
377
+ }
378
+ if (!existsSync(config.index_file)) {
379
+ throw new Error('No vector index found. The ingestion may not have completed successfully. Try using the ingest tool or rebuild_index tool.');
380
+ }
381
+ // Initialize search engine if needed
382
+ if (!this.isSearchEngineInitialized) {
383
+ await this.initializeSearchEngine();
384
+ }
385
+ // Prepare search options
386
+ const searchOptions = {
387
+ top_k: args.top_k || config.top_k || 10,
388
+ rerank: args.rerank !== undefined ? args.rerank : config.rerank_enabled
389
+ };
390
+ // Perform search using existing search functionality
391
+ const startTime = Date.now();
392
+ const results = await this.searchEngine.search(args.query, searchOptions);
393
+ const searchTime = Date.now() - startTime;
394
+ // Format results for MCP response with proper image content support
395
+ const textResults = {
396
+ query: args.query,
397
+ results_count: results.length,
398
+ search_time_ms: searchTime,
399
+ results: results.map((result, index) => ({
400
+ rank: index + 1,
401
+ score: Math.round(result.score * 100) / 100,
402
+ content_type: result.contentType,
403
+ document: {
404
+ id: result.document.id,
405
+ title: result.document.title,
406
+ source: result.document.source,
407
+ content_type: result.document.contentType
408
+ },
409
+ text: result.content,
410
+ metadata: result.metadata,
411
+ // Reference to image content if applicable
412
+ has_image: result.contentType === 'image' && !!result.document.contentId
413
+ }))
414
+ };
415
+ // Build MCP response content array
416
+ const responseContent = [
417
+ {
418
+ type: 'text',
419
+ text: JSON.stringify(textResults, null, 2)
420
+ }
421
+ ];
422
+ // Add proper MCP image content for each image result
423
+ for (const result of results) {
424
+ if (result.contentType === 'image' && result.document.contentId) {
425
+ try {
426
+ const imageData = await this.searchEngine.getContent(result.document.contentId, 'base64');
427
+ const mimeType = getMimeTypeFromPath(result.document.source);
428
+ responseContent.push({
429
+ type: 'image',
430
+ data: imageData,
431
+ mimeType: mimeType,
432
+ annotations: {
433
+ audience: ['user'],
434
+ priority: 0.8,
435
+ title: result.document.title,
436
+ source: result.document.source
437
+ }
438
+ });
439
+ }
440
+ catch (error) {
441
+ // If image retrieval fails, log but don't fail the entire search
442
+ console.error(`Failed to retrieve image for ${result.document.source}:`, error);
443
+ }
444
+ }
445
+ }
446
+ return {
447
+ content: responseContent
448
+ };
449
+ }
450
+ catch (error) {
451
+ // Handle model mismatch errors specifically
452
+ if (error instanceof Error && error.message.includes('Model mismatch detected')) {
453
+ const modelMismatchError = {
454
+ error: 'MODEL_MISMATCH',
455
+ message: 'Cannot perform search due to model mismatch',
456
+ details: error.message,
457
+ resolution: {
458
+ action: 'manual_intervention_required',
459
+ explanation: 'The embedding model configuration does not match the indexed data. Please verify your setup before proceeding.',
460
+ options: [
461
+ 'Check if the model mismatch is intentional',
462
+ 'If you want to use a different model, manually run the rebuild_index tool',
463
+ 'Verify your model configuration matches your indexing setup'
464
+ ],
465
+ warning: 'Rebuilding will regenerate all embeddings and may take significant time'
466
+ }
467
+ };
468
+ return {
469
+ content: [
470
+ {
471
+ type: 'text',
472
+ text: JSON.stringify(modelMismatchError, null, 2),
473
+ },
474
+ ],
475
+ };
476
+ }
477
+ // Handle dimension mismatch errors
478
+ if (error instanceof Error && error.message.includes('dimension mismatch')) {
479
+ const dimensionMismatchError = {
480
+ error: 'DIMENSION_MISMATCH',
481
+ message: 'Cannot perform search due to vector dimension mismatch',
482
+ details: error.message,
483
+ resolution: {
484
+ action: 'manual_intervention_required',
485
+ explanation: 'The vector dimensions do not match between the current model and the indexed data. Please verify your setup before proceeding.',
486
+ options: [
487
+ 'Check your model configuration',
488
+ 'If you want to change models, manually run the rebuild_index tool',
489
+ 'Ensure consistency between indexing and search models'
490
+ ],
491
+ warning: 'Rebuilding will regenerate all embeddings and may take significant time'
492
+ }
493
+ };
494
+ return {
495
+ content: [
496
+ {
497
+ type: 'text',
498
+ text: JSON.stringify(dimensionMismatchError, null, 2),
499
+ },
500
+ ],
501
+ };
502
+ }
503
+ // Re-throw other errors to be handled by the main error handler
504
+ throw error;
505
+ }
506
+ }
507
+ /**
508
+ * Handle ingest tool calls
509
+ * Wraps existing ingestion functionality as MCP tool
510
+ */
511
+ async handleIngest(args) {
512
+ try {
513
+ // Validate arguments
514
+ if (!args.path || typeof args.path !== 'string') {
515
+ throw new Error('Path parameter is required and must be a string');
516
+ }
517
+ // Validate path exists
518
+ const resolvedPath = resolve(args.path);
519
+ if (!existsSync(resolvedPath)) {
520
+ throw new Error(`Path does not exist: ${args.path}`);
521
+ }
522
+ // Check if it's a file or directory and validate
523
+ let stats;
524
+ try {
525
+ stats = statSync(resolvedPath);
526
+ }
527
+ catch (error) {
528
+ throw new Error(`Cannot access path: ${args.path}. Check permissions.`);
529
+ }
530
+ // Validate mode parameter
531
+ const mode = args.mode || 'text';
532
+ // Validate file type for single files (only formats with actual processing implementations)
533
+ if (stats.isFile()) {
534
+ const textExtensions = ['.md', '.txt', '.mdx', '.pdf', '.docx'];
535
+ const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp'];
536
+ const validExtensions = mode === 'multimodal'
537
+ ? [...textExtensions, ...imageExtensions]
538
+ : textExtensions;
539
+ const hasValidExtension = validExtensions.some(ext => args.path.toLowerCase().endsWith(ext));
540
+ if (!hasValidExtension) {
541
+ const supportedTypes = mode === 'multimodal'
542
+ ? '.md, .txt, .mdx, .pdf, .docx, .jpg, .jpeg, .png, .gif, .webp, .bmp'
543
+ : '.md, .txt, .mdx, .pdf, .docx';
544
+ throw new Error(`Unsupported file type: ${args.path}. Supported types: ${supportedTypes}`);
545
+ }
546
+ }
547
+ if (!['text', 'multimodal'].includes(mode)) {
548
+ throw new Error(`Invalid mode: ${mode}. Supported modes: text, multimodal`);
549
+ }
550
+ // Validate model parameter if provided
551
+ const supportedModels = [
552
+ 'sentence-transformers/all-MiniLM-L6-v2',
553
+ 'Xenova/all-mpnet-base-v2',
554
+ 'Xenova/clip-vit-base-patch32',
555
+ 'Xenova/clip-vit-base-patch16'
556
+ ];
557
+ if (args.model && !supportedModels.includes(args.model)) {
558
+ throw new Error(`Unsupported model: ${args.model}. Supported models: ${supportedModels.join(', ')}`);
559
+ }
560
+ // Validate model compatibility with mode
561
+ if (mode === 'text' && args.model) {
562
+ const textModels = ['sentence-transformers/all-MiniLM-L6-v2', 'Xenova/all-mpnet-base-v2'];
563
+ if (!textModels.includes(args.model)) {
564
+ throw new Error(`Model ${args.model} is not compatible with text mode. Use: ${textModels.join(', ')}`);
565
+ }
566
+ }
567
+ if (mode === 'multimodal' && args.model) {
568
+ const multimodalModels = ['Xenova/clip-vit-base-patch32', 'Xenova/clip-vit-base-patch16'];
569
+ if (!multimodalModels.includes(args.model)) {
570
+ throw new Error(`Model ${args.model} is not compatible with multimodal mode. Use: ${multimodalModels.join(', ')}`);
571
+ }
572
+ }
573
+ // Validate reranking strategy for multimodal mode
574
+ if (args.rerank_strategy) {
575
+ if (mode === 'text') {
576
+ throw new Error('Reranking strategy parameter is only supported in multimodal mode');
577
+ }
578
+ const validStrategies = ['text-derived', 'disabled'];
579
+ if (!validStrategies.includes(args.rerank_strategy)) {
580
+ throw new Error(`Invalid reranking strategy: ${args.rerank_strategy}. Supported strategies: ${validStrategies.join(', ')}`);
581
+ }
582
+ }
583
+ // Prepare factory options
584
+ const factoryOptions = {
585
+ mode: mode
586
+ };
587
+ if (args.model) {
588
+ factoryOptions.embeddingModel = args.model;
589
+ }
590
+ if (args.rerank_strategy && mode === 'multimodal') {
591
+ factoryOptions.rerankingStrategy = args.rerank_strategy;
592
+ }
593
+ if (args.force_rebuild) {
594
+ factoryOptions.forceRebuild = true;
595
+ }
596
+ // Create and run ingestion pipeline using text factory
597
+ // The IngestionFactory already supports mode and reranking strategy parameters
598
+ const pipeline = await IngestionFactory.create(config.db_file, config.index_file, factoryOptions);
599
+ try {
600
+ const result = await pipeline.ingestPath(resolvedPath);
601
+ // Reset search engine initialization flag since index may have changed
602
+ this.isSearchEngineInitialized = false;
603
+ this.searchEngine = null;
604
+ // Format results for MCP response
605
+ const ingestionSummary = {
606
+ path: resolvedPath,
607
+ path_type: stats.isDirectory() ? 'directory' : 'file',
608
+ mode: mode,
609
+ model: args.model || (mode === 'multimodal' ? 'Xenova/clip-vit-base-patch32' : 'sentence-transformers/all-MiniLM-L6-v2'),
610
+ reranking_strategy: args.rerank_strategy || (mode === 'multimodal' ? 'text-derived' : 'cross-encoder'),
611
+ documents_processed: result.documentsProcessed,
612
+ chunks_created: result.chunksCreated,
613
+ embeddings_generated: result.embeddingsGenerated,
614
+ document_errors: result.documentErrors,
615
+ embedding_errors: result.embeddingErrors,
616
+ processing_time_ms: result.processingTimeMs,
617
+ processing_time_seconds: Math.round(result.processingTimeMs / 1000 * 100) / 100,
618
+ chunks_per_second: result.processingTimeMs > 0 ?
619
+ Math.round(result.chunksCreated / (result.processingTimeMs / 1000) * 100) / 100 : 0,
620
+ supported_file_types: mode === 'multimodal'
621
+ ? ['md', 'txt', 'mdx', 'pdf', 'docx', 'jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp']
622
+ : ['md', 'txt', 'mdx', 'pdf', 'docx'],
623
+ success: true
624
+ };
625
+ return {
626
+ content: [
627
+ {
628
+ type: 'text',
629
+ text: JSON.stringify(ingestionSummary, null, 2),
630
+ },
631
+ ],
632
+ };
633
+ }
634
+ finally {
635
+ await pipeline.cleanup();
636
+ }
637
+ }
638
+ catch (error) {
639
+ // Handle model mismatch errors specifically
640
+ if (error instanceof Error && error.message.includes('Model mismatch detected')) {
641
+ const modelMismatchError = {
642
+ error: 'MODEL_MISMATCH',
643
+ message: 'Cannot perform ingestion due to model mismatch',
644
+ details: error.message,
645
+ resolution: {
646
+ action: 'manual_intervention_required',
647
+ explanation: 'The embedding model configuration does not match the indexed data. Please verify your setup before proceeding.',
648
+ options: [
649
+ 'Check if the model mismatch is intentional',
650
+ 'If you want to use a different model, manually run the rebuild_index tool',
651
+ 'Use force_rebuild: true parameter if you want to rebuild during ingestion',
652
+ 'Verify your model configuration matches your indexing setup'
653
+ ],
654
+ warning: 'Rebuilding will regenerate all embeddings and may take significant time'
655
+ }
656
+ };
657
+ return {
658
+ content: [
659
+ {
660
+ type: 'text',
661
+ text: JSON.stringify(modelMismatchError, null, 2),
662
+ },
663
+ ],
664
+ };
665
+ }
666
+ // Handle dimension mismatch errors
667
+ if (error instanceof Error && error.message.includes('dimension mismatch')) {
668
+ const dimensionMismatchError = {
669
+ error: 'DIMENSION_MISMATCH',
670
+ message: 'Cannot perform ingestion due to vector dimension mismatch',
671
+ details: error.message,
672
+ resolution: {
673
+ action: 'manual_intervention_required',
674
+ explanation: 'The vector dimensions do not match between the current model and the indexed data. Please verify your setup before proceeding.',
675
+ options: [
676
+ 'Check your model configuration',
677
+ 'If you want to change models, manually run the rebuild_index tool',
678
+ 'Use force_rebuild: true parameter if you want to rebuild during ingestion',
679
+ 'Ensure consistency between indexing and search models'
680
+ ],
681
+ warning: 'Rebuilding will regenerate all embeddings and may take significant time'
682
+ }
683
+ };
684
+ return {
685
+ content: [
686
+ {
687
+ type: 'text',
688
+ text: JSON.stringify(dimensionMismatchError, null, 2),
689
+ },
690
+ ],
691
+ };
692
+ }
693
+ // Handle initialization errors that might contain model mismatch information
694
+ if (error instanceof Error && error.message.includes('Failed to initialize')) {
695
+ // Check if the underlying error is a model mismatch
696
+ if (error.message.includes('Model mismatch') || error.message.includes('dimension mismatch')) {
697
+ const initializationError = {
698
+ error: 'INITIALIZATION_FAILED',
699
+ message: 'Cannot initialize ingestion due to model compatibility issues',
700
+ details: error.message,
701
+ resolution: {
702
+ action: 'manual_intervention_required',
703
+ explanation: 'The system cannot initialize due to model compatibility issues. Please verify your setup before proceeding.',
704
+ options: [
705
+ 'Check your model configuration',
706
+ 'If you want to change models, manually run the rebuild_index tool',
707
+ 'Verify consistency between your indexing and search setup'
708
+ ],
709
+ warning: 'Rebuilding will regenerate all embeddings and may take significant time'
710
+ }
711
+ };
712
+ return {
713
+ content: [
714
+ {
715
+ type: 'text',
716
+ text: JSON.stringify(initializationError, null, 2),
717
+ },
718
+ ],
719
+ };
720
+ }
721
+ }
722
+ // Re-throw other errors to be handled by the main error handler
723
+ throw error;
724
+ }
725
+ }
726
+ /**
727
+ * Handle ingest image tool calls
728
+ * Specialized tool for ingesting images from local files or URLs
729
+ */
730
+ async handleIngestImage(args) {
731
+ try {
732
+ // Validate arguments
733
+ if (!args.source || typeof args.source !== 'string') {
734
+ throw new Error('Source parameter is required and must be a string (file path or URL)');
735
+ }
736
+ const source = args.source.trim();
737
+ if (source.length === 0) {
738
+ throw new Error('Source cannot be empty');
739
+ }
740
+ // Check if source is a URL or local file
741
+ const isUrl = source.startsWith('http://') || source.startsWith('https://');
742
+ let resolvedPath;
743
+ let isTemporaryFile = false;
744
+ let tempFilePath = null;
745
+ if (isUrl) {
746
+ // Download image from URL to temporary location
747
+ console.error(`📥 Downloading image from URL: ${source}`);
748
+ try {
749
+ // Import required modules for URL download
750
+ const https = await import('https');
751
+ const http = await import('http');
752
+ const { promises: fs } = await import('fs');
753
+ const { join } = await import('path');
754
+ const { tmpdir } = await import('os');
755
+ const { randomBytes } = await import('crypto');
756
+ // Generate temporary file path
757
+ const tempDir = tmpdir();
758
+ const randomName = randomBytes(16).toString('hex');
759
+ const urlExt = source.split('.').pop()?.split('?')[0] || 'jpg';
760
+ tempFilePath = join(tempDir, `mcp-image-${randomName}.${urlExt}`);
761
+ // Download the image
762
+ await new Promise((resolve, reject) => {
763
+ const protocol = source.startsWith('https://') ? https : http;
764
+ protocol.get(source, (response) => {
765
+ if (response.statusCode === 301 || response.statusCode === 302) {
766
+ // Handle redirects
767
+ const redirectUrl = response.headers.location;
768
+ if (redirectUrl) {
769
+ const redirectProtocol = redirectUrl.startsWith('https://') ? https : http;
770
+ redirectProtocol.get(redirectUrl, (redirectResponse) => {
771
+ if (redirectResponse.statusCode !== 200) {
772
+ reject(new Error(`Failed to download image: HTTP ${redirectResponse.statusCode}`));
773
+ return;
774
+ }
775
+ const fileStream = createWriteStream(tempFilePath);
776
+ redirectResponse.pipe(fileStream);
777
+ fileStream.on('finish', () => {
778
+ fileStream.close();
779
+ resolve();
780
+ });
781
+ fileStream.on('error', reject);
782
+ }).on('error', reject);
783
+ }
784
+ }
785
+ else if (response.statusCode !== 200) {
786
+ reject(new Error(`Failed to download image: HTTP ${response.statusCode}`));
787
+ return;
788
+ }
789
+ else {
790
+ const fileStream = createWriteStream(tempFilePath);
791
+ response.pipe(fileStream);
792
+ fileStream.on('finish', () => {
793
+ fileStream.close();
794
+ resolve();
795
+ });
796
+ fileStream.on('error', reject);
797
+ }
798
+ }).on('error', reject);
799
+ });
800
+ resolvedPath = tempFilePath;
801
+ isTemporaryFile = true;
802
+ console.error(`✅ Image downloaded to: ${tempFilePath}`);
803
+ }
804
+ catch (downloadError) {
805
+ throw new Error(`Failed to download image from URL: ${downloadError instanceof Error ? downloadError.message : 'Unknown error'}`);
806
+ }
807
+ }
808
+ else {
809
+ // Local file path
810
+ resolvedPath = resolve(source);
811
+ if (!existsSync(resolvedPath)) {
812
+ throw new Error(`Image file does not exist: ${source}`);
813
+ }
814
+ // Validate it's a file
815
+ try {
816
+ const stats = statSync(resolvedPath);
817
+ if (!stats.isFile()) {
818
+ throw new Error(`Source is not a file: ${source}`);
819
+ }
820
+ }
821
+ catch (error) {
822
+ throw new Error(`Cannot access image file: ${source}. Check permissions.`);
823
+ }
824
+ }
825
+ // Validate image file extension
826
+ const validExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp'];
827
+ const hasValidExtension = validExtensions.some(ext => resolvedPath.toLowerCase().endsWith(ext));
828
+ if (!hasValidExtension) {
829
+ if (isTemporaryFile && tempFilePath) {
830
+ // Clean up temp file
831
+ try {
832
+ const { promises: fs } = await import('fs');
833
+ await fs.unlink(tempFilePath);
834
+ }
835
+ catch { }
836
+ }
837
+ throw new Error(`Unsupported image format. Supported formats: ${validExtensions.join(', ')}`);
838
+ }
839
+ // Prepare factory options for multimodal mode
840
+ const factoryOptions = {
841
+ mode: 'multimodal' // Always use multimodal mode for image ingestion
842
+ };
843
+ if (args.model) {
844
+ factoryOptions.embeddingModel = args.model;
845
+ }
846
+ else {
847
+ factoryOptions.embeddingModel = 'Xenova/clip-vit-base-patch32'; // Default CLIP model
848
+ }
849
+ if (args.rerank_strategy) {
850
+ factoryOptions.rerankingStrategy = args.rerank_strategy;
851
+ }
852
+ else {
853
+ factoryOptions.rerankingStrategy = 'text-derived'; // Default for multimodal
854
+ }
855
+ // Create and run ingestion pipeline
856
+ const pipeline = await IngestionFactory.create(config.db_file, config.index_file, factoryOptions);
857
+ try {
858
+ const result = await pipeline.ingestFile(resolvedPath);
859
+ // Reset search engine initialization flag since index may have changed
860
+ this.isSearchEngineInitialized = false;
861
+ this.searchEngine = null;
862
+ // Format results for MCP response
863
+ const ingestionSummary = {
864
+ source: isUrl ? source : resolvedPath,
865
+ source_type: isUrl ? 'url' : 'file',
866
+ mode: 'multimodal',
867
+ model: args.model || 'Xenova/clip-vit-base-patch32',
868
+ reranking_strategy: args.rerank_strategy || 'text-derived',
869
+ documents_processed: result.documentsProcessed,
870
+ chunks_created: result.chunksCreated,
871
+ embeddings_generated: result.embeddingsGenerated,
872
+ document_errors: result.documentErrors,
873
+ embedding_errors: result.embeddingErrors,
874
+ processing_time_ms: result.processingTimeMs,
875
+ processing_time_seconds: Math.round(result.processingTimeMs / 1000 * 100) / 100,
876
+ content_type: 'image',
877
+ title: args.title || (isUrl ? source.split('/').pop() : resolvedPath.split(/[/\\]/).pop()),
878
+ metadata: args.metadata || {},
879
+ success: true
880
+ };
881
+ return {
882
+ content: [
883
+ {
884
+ type: 'text',
885
+ text: JSON.stringify(ingestionSummary, null, 2),
886
+ },
887
+ ],
888
+ };
889
+ }
890
+ finally {
891
+ await pipeline.cleanup();
892
+ // Clean up temporary file if it was downloaded
893
+ if (isTemporaryFile && tempFilePath) {
894
+ try {
895
+ const { promises: fs } = await import('fs');
896
+ await fs.unlink(tempFilePath);
897
+ console.error(`🧹 Cleaned up temporary file: ${tempFilePath}`);
898
+ }
899
+ catch (cleanupError) {
900
+ console.error(`⚠️ Failed to clean up temporary file: ${cleanupError}`);
901
+ }
902
+ }
903
+ }
904
+ }
905
+ catch (error) {
906
+ // Handle model mismatch errors specifically
907
+ if (error instanceof Error && error.message.includes('Model mismatch detected')) {
908
+ const modelMismatchError = {
909
+ error: 'MODEL_MISMATCH',
910
+ message: 'Cannot perform image ingestion due to model mismatch',
911
+ details: error.message,
912
+ resolution: {
913
+ action: 'manual_intervention_required',
914
+ explanation: 'The embedding model configuration does not match the indexed data. Please verify your setup before proceeding.',
915
+ options: [
916
+ 'Check if the model mismatch is intentional',
917
+ 'If you want to use a different model, manually run the rebuild_index tool',
918
+ 'Verify your model configuration matches your indexing setup'
919
+ ],
920
+ warning: 'Rebuilding will regenerate all embeddings and may take significant time'
921
+ }
922
+ };
923
+ return {
924
+ content: [
925
+ {
926
+ type: 'text',
927
+ text: JSON.stringify(modelMismatchError, null, 2),
928
+ },
929
+ ],
930
+ };
931
+ }
932
+ // Re-throw other errors to be handled by the main error handler
933
+ throw error;
934
+ }
935
+ }
936
+ /**
937
+ * Handle rebuild index tool calls
938
+ * Wraps existing rebuild functionality as MCP tool
939
+ */
940
+ async handleRebuildIndex(_args) {
941
+ try {
942
+ // Create ingestion pipeline with force rebuild using factory
943
+ const pipeline = await IngestionFactory.create(config.db_file, config.index_file, { forceRebuild: true });
944
+ try {
945
+ // Get all documents from database and re-ingest them - use shared connection
946
+ const db = await DatabaseConnectionManager.getConnection(config.db_file);
947
+ try {
948
+ const documents = await db.all('SELECT DISTINCT source FROM documents ORDER BY source');
949
+ if (documents.length === 0) {
950
+ throw new Error('No documents found in database. Nothing to rebuild.');
951
+ }
952
+ let totalResult = {
953
+ documentsProcessed: 0,
954
+ chunksCreated: 0,
955
+ embeddingsGenerated: 0,
956
+ documentErrors: 0,
957
+ embeddingErrors: 0,
958
+ processingTimeMs: 0
959
+ };
960
+ // Re-ingest each document
961
+ for (const doc of documents) {
962
+ if (existsSync(doc.source)) {
963
+ const result = await pipeline.ingestFile(doc.source);
964
+ totalResult.documentsProcessed += result.documentsProcessed;
965
+ totalResult.chunksCreated += result.chunksCreated;
966
+ totalResult.embeddingsGenerated += result.embeddingsGenerated;
967
+ totalResult.documentErrors += result.documentErrors;
968
+ totalResult.embeddingErrors += result.embeddingErrors;
969
+ totalResult.processingTimeMs += result.processingTimeMs;
970
+ }
971
+ else {
972
+ totalResult.documentErrors++;
973
+ }
974
+ }
975
+ // Reset search engine initialization flag since index was rebuilt
976
+ this.isSearchEngineInitialized = false;
977
+ this.searchEngine = null;
978
+ const rebuildSummary = {
979
+ operation: 'rebuild_index',
980
+ success: true,
981
+ message: 'Vector index has been successfully rebuilt. All embeddings have been regenerated with the current model.',
982
+ documents_processed: totalResult.documentsProcessed,
983
+ chunks_created: totalResult.chunksCreated,
984
+ embeddings_generated: totalResult.embeddingsGenerated,
985
+ document_errors: totalResult.documentErrors,
986
+ embedding_errors: totalResult.embeddingErrors,
987
+ processing_time_ms: totalResult.processingTimeMs,
988
+ processing_time_seconds: Math.round(totalResult.processingTimeMs / 1000 * 100) / 100
989
+ };
990
+ return {
991
+ content: [
992
+ {
993
+ type: 'text',
994
+ text: JSON.stringify(rebuildSummary, null, 2),
995
+ },
996
+ ],
997
+ };
998
+ }
999
+ finally {
1000
+ // Release instead of close - keeps connection alive for reuse
1001
+ await DatabaseConnectionManager.releaseConnection(config.db_file);
1002
+ }
1003
+ }
1004
+ finally {
1005
+ await pipeline.cleanup();
1006
+ }
1007
+ }
1008
+ catch (error) {
1009
+ throw new Error(`Index rebuild failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
1010
+ }
1011
+ }
1012
+ /**
1013
+ * Handle get stats tool calls
1014
+ * Provides statistics about the current search index
1015
+ */
1016
+ async handleGetStats(_args) {
1017
+ try {
1018
+ const stats = {
1019
+ database_exists: existsSync(config.db_file),
1020
+ index_exists: existsSync(config.index_file),
1021
+ search_engine_initialized: this.isSearchEngineInitialized
1022
+ };
1023
+ // Get model information and compatibility status
1024
+ const { getModelDefaults } = await import('./config.js');
1025
+ const currentModel = config.embedding_model;
1026
+ const currentDefaults = getModelDefaults(currentModel);
1027
+ stats.model_info = {
1028
+ current_model: currentModel,
1029
+ current_dimensions: currentDefaults.dimensions,
1030
+ model_specific_config: {
1031
+ chunk_size: currentDefaults.chunk_size,
1032
+ chunk_overlap: currentDefaults.chunk_overlap,
1033
+ batch_size: currentDefaults.batch_size
1034
+ }
1035
+ };
1036
+ // Check model compatibility if database exists
1037
+ if (stats.database_exists) {
1038
+ try {
1039
+ const db = await DatabaseConnectionManager.getConnection(config.db_file);
1040
+ try {
1041
+ const systemInfo = await getSystemInfo(db);
1042
+ if (systemInfo && systemInfo.modelName && systemInfo.modelDimensions) {
1043
+ stats.model_info.stored_model = systemInfo.modelName;
1044
+ stats.model_info.stored_dimensions = systemInfo.modelDimensions;
1045
+ // Check for compatibility issues
1046
+ const modelMatch = systemInfo.modelName === currentModel;
1047
+ const dimensionMatch = systemInfo.modelDimensions === currentDefaults.dimensions;
1048
+ stats.model_info.compatibility = {
1049
+ model_matches: modelMatch,
1050
+ dimensions_match: dimensionMatch,
1051
+ compatible: modelMatch && dimensionMatch
1052
+ };
1053
+ if (!stats.model_info.compatibility.compatible) {
1054
+ stats.model_info.compatibility.issue = 'Model mismatch detected - rebuild required';
1055
+ stats.model_info.compatibility.resolution = 'Run "npm run rebuild" to rebuild the index with the new model';
1056
+ }
1057
+ }
1058
+ else {
1059
+ stats.model_info.compatibility = {
1060
+ status: 'No stored model info - first run or needs rebuild'
1061
+ };
1062
+ }
1063
+ // Get basic database stats
1064
+ const docCount = await db.get('SELECT COUNT(*) as count FROM documents');
1065
+ const chunkCount = await db.get('SELECT COUNT(*) as count FROM chunks');
1066
+ stats.total_documents = docCount?.count || 0;
1067
+ stats.total_chunks = chunkCount?.count || 0;
1068
+ }
1069
+ finally {
1070
+ // Release instead of close - keeps connection alive for reuse
1071
+ await DatabaseConnectionManager.releaseConnection(config.db_file);
1072
+ }
1073
+ }
1074
+ catch (error) {
1075
+ stats.database_error = error instanceof Error ? error.message : 'Unknown error';
1076
+ stats.model_info.compatibility = {
1077
+ status: 'Error checking model compatibility',
1078
+ error: error instanceof Error ? error.message : 'Unknown error'
1079
+ };
1080
+ }
1081
+ }
1082
+ else {
1083
+ // No database exists - indicate this is a fresh setup
1084
+ stats.model_info.compatibility = {
1085
+ status: 'No database exists - fresh setup, no compatibility issues'
1086
+ };
1087
+ }
1088
+ // If search engine is initialized, get detailed stats
1089
+ if (this.isSearchEngineInitialized && this.searchEngine) {
1090
+ const searchStats = await this.searchEngine.getStats();
1091
+ stats.total_chunks = searchStats.totalChunks;
1092
+ stats.index_size = searchStats.indexSize;
1093
+ stats.reranking_enabled = searchStats.rerankingEnabled;
1094
+ }
1095
+ // Show effective configuration (with model-specific defaults applied)
1096
+ const effectiveConfig = {
1097
+ db_file: config.db_file,
1098
+ index_file: config.index_file,
1099
+ embedding_model: config.embedding_model,
1100
+ chunk_size: currentDefaults.chunk_size, // Use model-specific default
1101
+ chunk_overlap: currentDefaults.chunk_overlap, // Use model-specific default
1102
+ batch_size: currentDefaults.batch_size, // Use model-specific default
1103
+ top_k: config.top_k,
1104
+ rerank_enabled: config.rerank_enabled
1105
+ };
1106
+ stats.config = effectiveConfig;
1107
+ return {
1108
+ content: [
1109
+ {
1110
+ type: 'text',
1111
+ text: JSON.stringify(stats, null, 2),
1112
+ },
1113
+ ],
1114
+ };
1115
+ }
1116
+ catch (error) {
1117
+ throw new Error(`Failed to get stats: ${error instanceof Error ? error.message : 'Unknown error'}`);
1118
+ }
1119
+ }
1120
+ /**
1121
+ * Handle get mode info tool calls
1122
+ * Provides information about the current system mode and configuration
1123
+ */
1124
+ async handleGetModeInfo(_args) {
1125
+ try {
1126
+ const modeInfo = {
1127
+ database_exists: existsSync(config.db_file),
1128
+ index_exists: existsSync(config.index_file)
1129
+ };
1130
+ if (!modeInfo.database_exists) {
1131
+ modeInfo.mode_status = 'No database found - system not initialized';
1132
+ modeInfo.default_mode = 'text';
1133
+ modeInfo.message = 'Run ingestion first to initialize the system with a specific mode';
1134
+ return {
1135
+ content: [
1136
+ {
1137
+ type: 'text',
1138
+ text: JSON.stringify(modeInfo, null, 2),
1139
+ },
1140
+ ],
1141
+ };
1142
+ }
1143
+ // Import mode detection service
1144
+ const { ModeDetectionService } = await import('./core/mode-detection-service.js');
1145
+ const modeService = new ModeDetectionService(config.db_file);
1146
+ try {
1147
+ const systemInfo = await modeService.detectMode();
1148
+ modeInfo.mode_status = 'Mode detected from database';
1149
+ modeInfo.current_mode = systemInfo.mode;
1150
+ modeInfo.model_name = systemInfo.modelName;
1151
+ modeInfo.model_type = systemInfo.modelType;
1152
+ modeInfo.model_dimensions = systemInfo.modelDimensions;
1153
+ modeInfo.supported_content_types = systemInfo.supportedContentTypes;
1154
+ modeInfo.reranking_strategy = systemInfo.rerankingStrategy;
1155
+ if (systemInfo.rerankingModel) {
1156
+ modeInfo.reranking_model = systemInfo.rerankingModel;
1157
+ }
1158
+ if (systemInfo.rerankingConfig) {
1159
+ modeInfo.reranking_config = systemInfo.rerankingConfig;
1160
+ }
1161
+ modeInfo.created_at = systemInfo.createdAt;
1162
+ modeInfo.updated_at = systemInfo.updatedAt;
1163
+ // Add mode-specific capabilities
1164
+ if (systemInfo.mode === 'text') {
1165
+ modeInfo.capabilities = {
1166
+ text_search: true,
1167
+ image_search: false,
1168
+ multimodal_reranking: false,
1169
+ supported_file_types: ['md', 'txt', 'mdx', 'pdf', 'docx']
1170
+ };
1171
+ }
1172
+ else if (systemInfo.mode === 'multimodal') {
1173
+ modeInfo.capabilities = {
1174
+ text_search: true,
1175
+ image_search: true,
1176
+ multimodal_reranking: true,
1177
+ supported_file_types: ['md', 'txt', 'mdx', 'pdf', 'docx', 'jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp']
1178
+ };
1179
+ }
1180
+ }
1181
+ catch (error) {
1182
+ modeInfo.mode_status = 'Error detecting mode from database';
1183
+ modeInfo.error = error instanceof Error ? error.message : 'Unknown error';
1184
+ modeInfo.fallback_mode = 'text';
1185
+ modeInfo.message = 'System will fall back to text mode. Consider rebuilding the database.';
1186
+ }
1187
+ return {
1188
+ content: [
1189
+ {
1190
+ type: 'text',
1191
+ text: JSON.stringify(modeInfo, null, 2),
1192
+ },
1193
+ ],
1194
+ };
1195
+ }
1196
+ catch (error) {
1197
+ throw new Error(`Failed to get mode info: ${error instanceof Error ? error.message : 'Unknown error'}`);
1198
+ }
1199
+ }
1200
+ /**
1201
+ * Handle multimodal search tool calls with content type filtering
1202
+ * Extends regular search with multimodal capabilities and content type filtering
1203
+ */
1204
+ async handleMultimodalSearch(args) {
1205
+ try {
1206
+ // Validate arguments (same as regular search)
1207
+ if (!args.query || typeof args.query !== 'string') {
1208
+ throw new Error('Query parameter is required and must be a string');
1209
+ }
1210
+ if (args.query.trim().length === 0) {
1211
+ throw new Error('Query cannot be empty');
1212
+ }
1213
+ if (args.query.length > 500) {
1214
+ throw new Error('Query is too long (maximum 500 characters)');
1215
+ }
1216
+ // Validate optional parameters
1217
+ if (args.top_k !== undefined) {
1218
+ if (typeof args.top_k !== 'number' || args.top_k < 1 || args.top_k > 100) {
1219
+ throw new Error('top_k must be a number between 1 and 100');
1220
+ }
1221
+ }
1222
+ if (args.rerank !== undefined && typeof args.rerank !== 'boolean') {
1223
+ throw new Error('rerank must be a boolean');
1224
+ }
1225
+ if (args.content_type !== undefined) {
1226
+ const validContentTypes = ['text', 'image', 'pdf', 'docx'];
1227
+ if (!validContentTypes.includes(args.content_type)) {
1228
+ throw new Error(`content_type must be one of: ${validContentTypes.join(', ')}`);
1229
+ }
1230
+ }
1231
+ // Check if database and index exist
1232
+ if (!existsSync(config.db_file)) {
1233
+ throw new Error('No database found. You need to ingest documents first using the ingest tool.');
1234
+ }
1235
+ if (!existsSync(config.index_file)) {
1236
+ throw new Error('No vector index found. The ingestion may not have completed successfully. Try using the ingest tool or rebuild_index tool.');
1237
+ }
1238
+ // Initialize search engine if needed
1239
+ if (!this.isSearchEngineInitialized) {
1240
+ await this.initializeSearchEngine();
1241
+ }
1242
+ // Prepare search options with content type filtering
1243
+ const searchOptions = {
1244
+ top_k: args.top_k || config.top_k || 10,
1245
+ rerank: args.rerank !== undefined ? args.rerank : config.rerank_enabled,
1246
+ contentType: args.content_type // Add content type filtering
1247
+ };
1248
+ // Perform search using existing search functionality
1249
+ const startTime = Date.now();
1250
+ const results = await this.searchEngine.search(args.query, searchOptions);
1251
+ const searchTime = Date.now() - startTime;
1252
+ // Format results for MCP response with proper image content support
1253
+ const textResults = {
1254
+ query: args.query,
1255
+ content_type_filter: args.content_type || 'all',
1256
+ results_count: results.length,
1257
+ search_time_ms: searchTime,
1258
+ results: results.map((result, index) => ({
1259
+ rank: index + 1,
1260
+ score: Math.round(result.score * 100) / 100,
1261
+ content_type: result.contentType,
1262
+ document: {
1263
+ id: result.document.id,
1264
+ title: result.document.title,
1265
+ source: result.document.source,
1266
+ content_type: result.document.contentType
1267
+ },
1268
+ text: result.content,
1269
+ metadata: result.metadata,
1270
+ // Reference to image content if applicable
1271
+ has_image: result.contentType === 'image' && !!result.document.contentId
1272
+ }))
1273
+ };
1274
+ // Build MCP response content array
1275
+ const responseContent = [
1276
+ {
1277
+ type: 'text',
1278
+ text: JSON.stringify(textResults, null, 2)
1279
+ }
1280
+ ];
1281
+ // Add proper MCP image content for each image result
1282
+ for (const result of results) {
1283
+ if (result.contentType === 'image' && result.document.contentId) {
1284
+ try {
1285
+ const imageData = await this.searchEngine.getContent(result.document.contentId, 'base64');
1286
+ const mimeType = getMimeTypeFromPath(result.document.source);
1287
+ responseContent.push({
1288
+ type: 'image',
1289
+ data: imageData,
1290
+ mimeType: mimeType,
1291
+ annotations: {
1292
+ audience: ['user'],
1293
+ priority: 0.8,
1294
+ title: result.document.title,
1295
+ source: result.document.source
1296
+ }
1297
+ });
1298
+ }
1299
+ catch (error) {
1300
+ // If image retrieval fails, log but don't fail the entire search
1301
+ console.error(`Failed to retrieve image for ${result.document.source}:`, error);
1302
+ }
1303
+ }
1304
+ }
1305
+ return {
1306
+ content: responseContent
1307
+ };
1308
+ }
1309
+ catch (error) {
1310
+ // Handle the same errors as regular search
1311
+ if (error instanceof Error && error.message.includes('Model mismatch detected')) {
1312
+ const modelMismatchError = {
1313
+ error: 'MODEL_MISMATCH',
1314
+ message: 'Cannot perform multimodal search due to model mismatch',
1315
+ details: error.message,
1316
+ resolution: {
1317
+ action: 'manual_intervention_required',
1318
+ explanation: 'The embedding model configuration does not match the indexed data. Please verify your setup before proceeding.',
1319
+ options: [
1320
+ 'Check if the model mismatch is intentional',
1321
+ 'If you want to use a different model, manually run the rebuild_index tool',
1322
+ 'Verify your model configuration matches your indexing setup'
1323
+ ],
1324
+ warning: 'Rebuilding will regenerate all embeddings and may take significant time'
1325
+ }
1326
+ };
1327
+ return {
1328
+ content: [
1329
+ {
1330
+ type: 'text',
1331
+ text: JSON.stringify(modelMismatchError, null, 2),
1332
+ },
1333
+ ],
1334
+ };
1335
+ }
1336
+ // Re-throw other errors to be handled by the main error handler
1337
+ throw error;
1338
+ }
1339
+ }
1340
+ /**
1341
+ * Handle list supported models tool calls
1342
+ * Lists all supported embedding models with their capabilities
1343
+ */
1344
+ async handleListSupportedModels(args) {
1345
+ try {
1346
+ // Import model registry
1347
+ const { ModelRegistry } = await import('./core/model-registry.js');
1348
+ const { getSupportedModelsForContentType } = await import('./core/embedder-factory.js');
1349
+ let models;
1350
+ // Filter by model type if specified
1351
+ if (args.model_type) {
1352
+ models = ModelRegistry.getSupportedModels(args.model_type);
1353
+ }
1354
+ else if (args.content_type) {
1355
+ // Filter by content type if specified
1356
+ models = getSupportedModelsForContentType(args.content_type);
1357
+ }
1358
+ else {
1359
+ // Get all models
1360
+ models = ModelRegistry.getSupportedModels();
1361
+ }
1362
+ // Get detailed information for each model
1363
+ const modelDetails = models.map(modelName => {
1364
+ const info = ModelRegistry.getModelInfo(modelName);
1365
+ return {
1366
+ name: modelName,
1367
+ type: info?.type,
1368
+ dimensions: info?.dimensions,
1369
+ supported_content_types: info?.supportedContentTypes || [],
1370
+ memory_requirement: info?.requirements?.minimumMemory,
1371
+ description: `${info?.type} model for ${info?.supportedContentTypes?.join(', ')} content`,
1372
+ is_default: ModelRegistry.getDefaultModel(info?.type) === modelName,
1373
+ capabilities: {
1374
+ supports_text: info?.capabilities?.supportsText || false,
1375
+ supports_images: info?.capabilities?.supportsImages || false,
1376
+ supports_batch_processing: info?.capabilities?.supportsBatchProcessing || false,
1377
+ max_batch_size: info?.capabilities?.maxBatchSize,
1378
+ max_text_length: info?.capabilities?.maxTextLength,
1379
+ supported_image_formats: info?.capabilities?.supportedImageFormats || []
1380
+ },
1381
+ requirements: {
1382
+ transformers_js_version: info?.requirements?.transformersJsVersion,
1383
+ minimum_memory_mb: info?.requirements?.minimumMemory,
1384
+ required_features: info?.requirements?.requiredFeatures || [],
1385
+ platform_support: info?.requirements?.platformSupport || []
1386
+ }
1387
+ };
1388
+ });
1389
+ const response = {
1390
+ filter_applied: {
1391
+ model_type: args.model_type || 'all',
1392
+ content_type: args.content_type || 'all'
1393
+ },
1394
+ total_models: modelDetails.length,
1395
+ models: modelDetails,
1396
+ model_types: {
1397
+ 'sentence-transformer': 'Text-only embedding models optimized for semantic similarity',
1398
+ 'clip': 'Multimodal models supporting both text and image embeddings'
1399
+ },
1400
+ usage_examples: {
1401
+ text_only: 'Use sentence-transformer models for text documents, markdown files, and text-based search',
1402
+ multimodal: 'Use CLIP models when working with mixed content including images, diagrams, and visual content'
1403
+ }
1404
+ };
1405
+ return {
1406
+ content: [
1407
+ {
1408
+ type: 'text',
1409
+ text: JSON.stringify(response, null, 2),
1410
+ },
1411
+ ],
1412
+ };
1413
+ }
1414
+ catch (error) {
1415
+ throw new Error(`Failed to list supported models: ${error instanceof Error ? error.message : 'Unknown error'}`);
1416
+ }
1417
+ }
1418
+ /**
1419
+ * Handle list reranking strategies tool calls
1420
+ * Lists all supported reranking strategies for different modes
1421
+ */
1422
+ async handleListRerankingStrategies(args) {
1423
+ try {
1424
+ // Import reranking configuration
1425
+ const { getSupportedStrategies, getDefaultRerankingConfig } = await import('./core/reranking-config.js');
1426
+ const modes = args.mode ? [args.mode] : ['text', 'multimodal'];
1427
+ const strategiesByMode = {};
1428
+ for (const mode of modes) {
1429
+ const supportedStrategies = getSupportedStrategies(mode);
1430
+ const defaultConfig = getDefaultRerankingConfig(mode);
1431
+ strategiesByMode[mode] = {
1432
+ default_strategy: defaultConfig.strategy,
1433
+ mode_description: mode === 'text'
1434
+ ? 'Text-only mode optimized for document and text-based content'
1435
+ : 'Multimodal mode supporting mixed text and image content',
1436
+ supported_strategies: supportedStrategies.map(strategy => {
1437
+ const strategyInfo = {
1438
+ name: strategy,
1439
+ is_default: strategy === defaultConfig.strategy,
1440
+ performance_impact: 'medium'
1441
+ };
1442
+ // Add descriptions for each strategy
1443
+ switch (strategy) {
1444
+ case 'cross-encoder':
1445
+ strategyInfo.description = 'Uses cross-encoder models to rerank results based on query-document relevance';
1446
+ strategyInfo.requirements = ['Cross-encoder model (e.g., ms-marco-MiniLM-L-6-v2)'];
1447
+ strategyInfo.supported_content_types = ['text'];
1448
+ strategyInfo.performance_impact = 'high';
1449
+ strategyInfo.accuracy = 'high';
1450
+ strategyInfo.use_cases = ['Text documents', 'Academic papers', 'Technical documentation'];
1451
+ break;
1452
+ case 'text-derived':
1453
+ strategyInfo.description = 'Converts images to text descriptions using image-to-text models, then applies cross-encoder reranking';
1454
+ strategyInfo.requirements = ['Image-to-text model (e.g., vit-gpt2-image-captioning)', 'Cross-encoder model'];
1455
+ strategyInfo.supported_content_types = ['text', 'image'];
1456
+ strategyInfo.performance_impact = 'high';
1457
+ strategyInfo.accuracy = 'high';
1458
+ strategyInfo.use_cases = ['Mixed content with images', 'Visual documentation', 'Diagrams and charts'];
1459
+ break;
1460
+ case 'disabled':
1461
+ strategyInfo.description = 'No reranking applied - results ordered by vector similarity scores only';
1462
+ strategyInfo.requirements = ['None'];
1463
+ strategyInfo.supported_content_types = ['text', 'image', 'pdf', 'docx'];
1464
+ strategyInfo.performance_impact = 'none';
1465
+ strategyInfo.accuracy = 'baseline';
1466
+ strategyInfo.use_cases = ['Maximum performance', 'Simple similarity search', 'Development/testing'];
1467
+ break;
1468
+ }
1469
+ return strategyInfo;
1470
+ })
1471
+ };
1472
+ }
1473
+ const response = {
1474
+ filter_applied: {
1475
+ mode: args.mode || 'all'
1476
+ },
1477
+ strategies_by_mode: strategiesByMode,
1478
+ recommendations: {
1479
+ text_mode: 'Use cross-encoder for best accuracy, disabled for best performance',
1480
+ multimodal_mode: 'Use text-derived for best accuracy, disabled for best performance',
1481
+ development: 'Start with disabled for fast iteration, upgrade to cross-encoder/text-derived for production'
1482
+ }
1483
+ };
1484
+ return {
1485
+ content: [
1486
+ {
1487
+ type: 'text',
1488
+ text: JSON.stringify(response, null, 2),
1489
+ },
1490
+ ],
1491
+ };
1492
+ }
1493
+ catch (error) {
1494
+ throw new Error(`Failed to list reranking strategies: ${error instanceof Error ? error.message : 'Unknown error'}`);
1495
+ }
1496
+ }
1497
+ /**
1498
+ * Handle get system stats tool calls with mode-specific metrics
1499
+ * Provides comprehensive system statistics including mode-specific data
1500
+ */
1501
+ async handleGetSystemStats(args) {
1502
+ try {
1503
+ // Start with basic stats from existing get_stats handler
1504
+ const basicStats = await this.handleGetStats({});
1505
+ const basicStatsData = JSON.parse(basicStats.content[0].text);
1506
+ // Enhanced stats with mode-specific information
1507
+ const enhancedStats = {
1508
+ ...basicStatsData,
1509
+ mode_specific_metrics: {}
1510
+ };
1511
+ // Add mode detection information
1512
+ if (basicStatsData.database_exists) {
1513
+ try {
1514
+ const { ModeDetectionService } = await import('./core/mode-detection-service.js');
1515
+ const modeService = new ModeDetectionService(config.db_file);
1516
+ const systemInfo = await modeService.detectMode();
1517
+ enhancedStats.mode_specific_metrics = {
1518
+ current_mode: systemInfo.mode,
1519
+ model_name: systemInfo.modelName,
1520
+ model_type: systemInfo.modelType,
1521
+ model_dimensions: systemInfo.modelDimensions,
1522
+ supported_content_types: systemInfo.supportedContentTypes,
1523
+ reranking_strategy: systemInfo.rerankingStrategy
1524
+ };
1525
+ // Add content breakdown if requested
1526
+ if (args.include_content_breakdown) {
1527
+ const db = await DatabaseConnectionManager.getConnection(config.db_file);
1528
+ try {
1529
+ // Get document count by content type
1530
+ const docsByType = await db.all(`
1531
+ SELECT content_type, COUNT(*) as count
1532
+ FROM documents
1533
+ GROUP BY content_type
1534
+ `);
1535
+ // Get chunk count by content type
1536
+ const chunksByType = await db.all(`
1537
+ SELECT content_type, COUNT(*) as count
1538
+ FROM chunks
1539
+ GROUP BY content_type
1540
+ `);
1541
+ enhancedStats.content_breakdown = {
1542
+ documents_by_type: docsByType.reduce((acc, row) => {
1543
+ acc[row.content_type] = row.count;
1544
+ return acc;
1545
+ }, {}),
1546
+ chunks_by_type: chunksByType.reduce((acc, row) => {
1547
+ acc[row.content_type] = row.count;
1548
+ return acc;
1549
+ }, {})
1550
+ };
1551
+ }
1552
+ finally {
1553
+ // Release instead of close - keeps connection alive for reuse
1554
+ await DatabaseConnectionManager.releaseConnection(config.db_file);
1555
+ }
1556
+ }
1557
+ // Add performance metrics if requested
1558
+ if (args.include_performance && this.isSearchEngineInitialized && this.searchEngine) {
1559
+ const searchStats = await this.searchEngine.getStats();
1560
+ enhancedStats.performance_metrics = {
1561
+ search_engine_initialized: true,
1562
+ index_size: searchStats.indexSize,
1563
+ reranking_enabled: searchStats.rerankingEnabled,
1564
+ total_chunks: searchStats.totalChunks,
1565
+ // Add timing information if available
1566
+ last_search_time_ms: undefined, // Would need to track this
1567
+ average_search_time_ms: undefined // Would need to track this
1568
+ };
1569
+ }
1570
+ }
1571
+ catch (error) {
1572
+ enhancedStats.mode_specific_metrics.error = error instanceof Error ? error.message : 'Unknown error';
1573
+ }
1574
+ }
1575
+ return {
1576
+ content: [
1577
+ {
1578
+ type: 'text',
1579
+ text: JSON.stringify(enhancedStats, null, 2),
1580
+ },
1581
+ ],
1582
+ };
1583
+ }
1584
+ catch (error) {
1585
+ throw new Error(`Failed to get system stats: ${error instanceof Error ? error.message : 'Unknown error'}`);
1586
+ }
1587
+ }
1588
+ /**
1589
+ * Initialize search engine components using factory
1590
+ * Lazy initialization to avoid startup overhead when not needed
1591
+ */
1592
+ async initializeSearchEngine() {
1593
+ if (this.isSearchEngineInitialized) {
1594
+ return;
1595
+ }
1596
+ try {
1597
+ // Validate configuration
1598
+ validateCoreConfig(config);
1599
+ // Create search engine using SearchFactory (auto-detects mode)
1600
+ // This will automatically detect the mode from the database and create the appropriate engine
1601
+ console.error('🎭 MCP Server: Initializing search engine with automatic mode detection...');
1602
+ this.searchEngine = await SearchFactory.create(config.index_file, config.db_file);
1603
+ // Log successful initialization with mode information
1604
+ const stats = await this.searchEngine.getStats();
1605
+ // Try to get mode information for enhanced logging
1606
+ try {
1607
+ const { ModeDetectionService } = await import('./core/mode-detection-service.js');
1608
+ const modeService = new ModeDetectionService(config.db_file);
1609
+ const systemInfo = await modeService.detectMode();
1610
+ console.error(`✅ MCP Server: Search engine initialized successfully`);
1611
+ console.error(`🎭 Mode: ${systemInfo.mode} | Model: ${systemInfo.modelName}`);
1612
+ console.error(`📊 Total chunks: ${stats.totalChunks} | Reranking: ${stats.rerankingEnabled ? 'enabled' : 'disabled'}`);
1613
+ console.error(`🔧 Content types: ${systemInfo.supportedContentTypes.join(', ')}`);
1614
+ if (systemInfo.mode === 'multimodal') {
1615
+ console.error(`🌈 Multimodal capabilities: Text + Image processing enabled`);
1616
+ console.error(`⚡ Reranking strategy: ${systemInfo.rerankingStrategy}`);
1617
+ }
1618
+ }
1619
+ catch (modeError) {
1620
+ // Fallback to basic logging if mode detection fails
1621
+ console.error(`✅ MCP Server: Search engine initialized successfully`);
1622
+ console.error(`📊 Total chunks: ${stats.totalChunks}, Reranking: ${stats.rerankingEnabled ? 'enabled' : 'disabled'}`);
1623
+ console.error(`⚠️ Mode detection unavailable: ${modeError instanceof Error ? modeError.message : 'Unknown error'}`);
1624
+ }
1625
+ this.isSearchEngineInitialized = true;
1626
+ }
1627
+ catch (error) {
1628
+ console.error('❌ MCP Server: Search engine initialization failed');
1629
+ console.error(`❌ Error: ${error instanceof Error ? error.message : String(error)}`);
1630
+ // Check if this is a mode detection error
1631
+ if (error instanceof Error && error.message.includes('mode detection')) {
1632
+ console.error('⚠️ MCP Server: Mode detection failed, falling back to text mode');
1633
+ throw new Error(`Mode detection failed: ${error.message}. The system will attempt to fall back to text mode.`);
1634
+ }
1635
+ // Check if this is a model mismatch error and re-throw with more context
1636
+ if (error instanceof Error && (error.message.includes('Model mismatch detected') || error.message.includes('dimension mismatch'))) {
1637
+ console.error('⚠️ MCP Server: Model compatibility issue detected');
1638
+ // Re-throw the original error - it already has good formatting from factory
1639
+ throw error;
1640
+ }
1641
+ // For other initialization errors, provide a generic wrapper
1642
+ throw new Error(`Failed to initialize search engine: ${error instanceof Error ? error.message : 'Unknown error'}`);
1643
+ }
1644
+ }
1645
+ /**
1646
+ * Detect capabilities of the current database
1647
+ * Returns information about mode, content types, and features
1648
+ */
1649
+ async detectCapabilities() {
1650
+ // Default capabilities if database doesn't exist
1651
+ if (!existsSync(config.db_file)) {
1652
+ return {
1653
+ mode: 'unknown',
1654
+ contentTypes: [],
1655
+ modelName: 'none',
1656
+ hasImages: false,
1657
+ documentCount: 0
1658
+ };
1659
+ }
1660
+ try {
1661
+ const { ModeDetectionService } = await import('./core/mode-detection-service.js');
1662
+ const modeService = new ModeDetectionService(config.db_file);
1663
+ const systemInfo = await modeService.detectMode();
1664
+ // Check if database has any images - use shared connection
1665
+ const db = await DatabaseConnectionManager.getConnection(config.db_file);
1666
+ let hasImages = false;
1667
+ let documentCount = 0;
1668
+ try {
1669
+ const imageCount = await db.get("SELECT COUNT(*) as count FROM documents WHERE content_type = 'image'");
1670
+ hasImages = (imageCount?.count || 0) > 0;
1671
+ const docCount = await db.get('SELECT COUNT(*) as count FROM documents');
1672
+ documentCount = docCount?.count || 0;
1673
+ }
1674
+ finally {
1675
+ // Release instead of close - keeps connection alive for reuse
1676
+ await DatabaseConnectionManager.releaseConnection(config.db_file);
1677
+ }
1678
+ return {
1679
+ mode: systemInfo.mode,
1680
+ contentTypes: systemInfo.supportedContentTypes,
1681
+ modelName: systemInfo.modelName,
1682
+ hasImages,
1683
+ documentCount
1684
+ };
1685
+ }
1686
+ catch (error) {
1687
+ // If detection fails, return unknown
1688
+ return {
1689
+ mode: 'unknown',
1690
+ contentTypes: [],
1691
+ modelName: 'unknown',
1692
+ hasImages: false,
1693
+ documentCount: 0
1694
+ };
1695
+ }
1696
+ }
1697
+ /**
1698
+ * Generate dynamic search tool description based on actual capabilities
1699
+ */
1700
+ generateSearchDescription(capabilities) {
1701
+ const baseDesc = 'Search indexed documents using semantic similarity.';
1702
+ if (capabilities.mode === 'unknown' || capabilities.documentCount === 0) {
1703
+ return `${baseDesc} Database not initialized - ingest documents first.`;
1704
+ }
1705
+ if (capabilities.mode === 'text') {
1706
+ return `[TEXT MODE] ${baseDesc} This database contains ${capabilities.documentCount} text documents. Supports .md and .txt files only.`;
1707
+ }
1708
+ if (capabilities.mode === 'multimodal') {
1709
+ const imageInfo = capabilities.hasImages
1710
+ ? 'Contains both text and image content. Image results include base64-encoded data for display.'
1711
+ : 'Configured for multimodal but currently contains only text.';
1712
+ return `[MULTIMODAL MODE] ${baseDesc} This database contains ${capabilities.documentCount} documents. ${imageInfo} Supports cross-modal search (text queries can find images).`;
1713
+ }
1714
+ return baseDesc;
1715
+ }
1716
+ /**
1717
+ * Cleanup MCP server resources
1718
+ * Closes database connections and cleans up search engine
1719
+ */
1720
+ async cleanup() {
1721
+ console.error('🧹 MCP Server: Cleaning up resources...');
1722
+ try {
1723
+ if (this.searchEngine) {
1724
+ await this.searchEngine.cleanup();
1725
+ this.searchEngine = null;
1726
+ this.isSearchEngineInitialized = false;
1727
+ }
1728
+ // Close all database connections
1729
+ await DatabaseConnectionManager.closeAllConnections();
1730
+ console.error('✅ MCP Server: Cleanup completed successfully');
1731
+ }
1732
+ catch (error) {
1733
+ console.error('⚠️ MCP Server: Error during cleanup:', error);
1734
+ }
1735
+ }
1736
+ /**
1737
+ * Start the MCP server
1738
+ * Ensures MCP server lives in same package as CLI with dual entry points
1739
+ */
1740
+ async start() {
1741
+ const transport = new StdioServerTransport();
1742
+ await this.server.connect(transport);
1743
+ // Server will run until the transport is closed
1744
+ console.error('RAG-lite TS MCP Server started successfully');
1745
+ }
1746
+ }
1747
+ // Global server instance for cleanup
1748
+ let globalServer = null;
1749
+ /**
1750
+ * Main entry point for MCP server
1751
+ * Implements MCP protocol interface without creating REST/GraphQL endpoints
1752
+ */
1753
+ async function main() {
1754
+ try {
1755
+ globalServer = new RagLiteMCPServer();
1756
+ await globalServer.start();
1757
+ }
1758
+ catch (error) {
1759
+ if (error instanceof ConfigurationError) {
1760
+ console.error('Configuration Error:', error.message);
1761
+ process.exit(error.exitCode);
1762
+ }
1763
+ else {
1764
+ console.error('Failed to start MCP server:', error instanceof Error ? error.message : String(error));
1765
+ process.exit(1);
1766
+ }
1767
+ }
1768
+ }
1769
+ // Handle process signals for graceful shutdown
1770
+ process.on('SIGINT', async () => {
1771
+ console.error('Received SIGINT, shutting down gracefully...');
1772
+ if (globalServer) {
1773
+ await globalServer.cleanup();
1774
+ }
1775
+ process.exit(0);
1776
+ });
1777
+ process.on('SIGTERM', async () => {
1778
+ console.error('Received SIGTERM, shutting down gracefully...');
1779
+ if (globalServer) {
1780
+ await globalServer.cleanup();
1781
+ }
1782
+ process.exit(0);
1783
+ });
1784
+ // Handle unhandled promise rejections
1785
+ process.on('unhandledRejection', (reason, _promise) => {
1786
+ console.error('Unhandled Promise Rejection:', reason);
1787
+ process.exit(1);
1788
+ });
1789
+ // Handle uncaught exceptions
1790
+ process.on('uncaughtException', (error) => {
1791
+ console.error('Uncaught Exception:', error.message);
1792
+ process.exit(1);
1793
+ });
1794
+ // Start the server
1795
+ main().catch((error) => {
1796
+ console.error('Fatal error:', error instanceof Error ? error.message : String(error));
1797
+ process.exit(1);
1798
+ });
1799
+ /**
1800
+ * MCP Server Multimodal Integration Complete
1801
+ *
1802
+ * This implementation addresses task 9.3 requirements:
1803
+ * ✅ Updated MCP server configuration to support multimodal parameters
1804
+ * ✅ Added new MCP tools for mode configuration and multimodal search
1805
+ * ✅ Integrated with polymorphic runtime system and mode detection
1806
+ * ✅ Enhanced error handling for multimodal-specific errors
1807
+ * ✅ Created comprehensive documentation and examples
1808
+ * ✅ Added support for content type filtering and model selection
1809
+ * ✅ Implemented reranking strategy configuration
1810
+ * ✅ Provided detailed system information and statistics tools
1811
+ *
1812
+ * Key Features Added:
1813
+ * - Multimodal ingestion with mode and model parameters
1814
+ * - Content type filtering in search operations
1815
+ * - Comprehensive model and strategy information tools
1816
+ * - Enhanced error handling with recovery guidance
1817
+ * - Automatic mode detection and polymorphic behavior
1818
+ * - Detailed documentation and configuration examples
1819
+ */
1820
+ //# sourceMappingURL=mcp-server.js.map