@ssmeo/mcp-local-rag 0.18.6

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 (247) hide show
  1. package/LICENSE +21 -0
  2. package/README.de.md +441 -0
  3. package/README.es.md +441 -0
  4. package/README.fr.md +441 -0
  5. package/README.md +530 -0
  6. package/README.pt-BR.md +441 -0
  7. package/README.zh-CN.md +441 -0
  8. package/dist/bin/install-skills.d.ts +19 -0
  9. package/dist/bin/install-skills.d.ts.map +1 -0
  10. package/dist/bin/install-skills.js +218 -0
  11. package/dist/bin/install-skills.js.map +1 -0
  12. package/dist/chunker/index.d.ts +24 -0
  13. package/dist/chunker/index.d.ts.map +1 -0
  14. package/dist/chunker/index.js +2 -0
  15. package/dist/chunker/index.js.map +1 -0
  16. package/dist/chunker/semantic-chunker.d.ts +96 -0
  17. package/dist/chunker/semantic-chunker.d.ts.map +1 -0
  18. package/dist/chunker/semantic-chunker.js +362 -0
  19. package/dist/chunker/semantic-chunker.js.map +1 -0
  20. package/dist/chunker/sentence-splitter.d.ts +32 -0
  21. package/dist/chunker/sentence-splitter.d.ts.map +1 -0
  22. package/dist/chunker/sentence-splitter.js +231 -0
  23. package/dist/chunker/sentence-splitter.js.map +1 -0
  24. package/dist/chunker/token-containment.d.ts +39 -0
  25. package/dist/chunker/token-containment.d.ts.map +1 -0
  26. package/dist/chunker/token-containment.js +83 -0
  27. package/dist/chunker/token-containment.js.map +1 -0
  28. package/dist/cli/common.d.ts +34 -0
  29. package/dist/cli/common.d.ts.map +1 -0
  30. package/dist/cli/common.js +121 -0
  31. package/dist/cli/common.js.map +1 -0
  32. package/dist/cli/delete.d.ts +3 -0
  33. package/dist/cli/delete.d.ts.map +1 -0
  34. package/dist/cli/delete.js +169 -0
  35. package/dist/cli/delete.js.map +1 -0
  36. package/dist/cli/file-collection.d.ts +2 -0
  37. package/dist/cli/file-collection.d.ts.map +1 -0
  38. package/dist/cli/file-collection.js +53 -0
  39. package/dist/cli/file-collection.js.map +1 -0
  40. package/dist/cli/ingest.d.ts +69 -0
  41. package/dist/cli/ingest.d.ts.map +1 -0
  42. package/dist/cli/ingest.js +323 -0
  43. package/dist/cli/ingest.js.map +1 -0
  44. package/dist/cli/list.d.ts +27 -0
  45. package/dist/cli/list.d.ts.map +1 -0
  46. package/dist/cli/list.js +207 -0
  47. package/dist/cli/list.js.map +1 -0
  48. package/dist/cli/options.d.ts +89 -0
  49. package/dist/cli/options.d.ts.map +1 -0
  50. package/dist/cli/options.js +244 -0
  51. package/dist/cli/options.js.map +1 -0
  52. package/dist/cli/query.d.ts +17 -0
  53. package/dist/cli/query.d.ts.map +1 -0
  54. package/dist/cli/query.js +194 -0
  55. package/dist/cli/query.js.map +1 -0
  56. package/dist/cli/read-neighbors.d.ts +3 -0
  57. package/dist/cli/read-neighbors.d.ts.map +1 -0
  58. package/dist/cli/read-neighbors.js +192 -0
  59. package/dist/cli/read-neighbors.js.map +1 -0
  60. package/dist/cli/status.d.ts +6 -0
  61. package/dist/cli/status.d.ts.map +1 -0
  62. package/dist/cli/status.js +78 -0
  63. package/dist/cli/status.js.map +1 -0
  64. package/dist/cli/sync.d.ts +6 -0
  65. package/dist/cli/sync.d.ts.map +1 -0
  66. package/dist/cli/sync.js +271 -0
  67. package/dist/cli/sync.js.map +1 -0
  68. package/dist/cli-main.d.ts +9 -0
  69. package/dist/cli-main.d.ts.map +1 -0
  70. package/dist/cli-main.js +60 -0
  71. package/dist/cli-main.js.map +1 -0
  72. package/dist/embedder/index.d.ts +143 -0
  73. package/dist/embedder/index.d.ts.map +1 -0
  74. package/dist/embedder/index.js +429 -0
  75. package/dist/embedder/index.js.map +1 -0
  76. package/dist/embedder/remote.d.ts +51 -0
  77. package/dist/embedder/remote.d.ts.map +1 -0
  78. package/dist/embedder/remote.js +182 -0
  79. package/dist/embedder/remote.js.map +1 -0
  80. package/dist/features/list.d.ts +37 -0
  81. package/dist/features/list.d.ts.map +1 -0
  82. package/dist/features/list.js +41 -0
  83. package/dist/features/list.js.map +1 -0
  84. package/dist/features/sync.d.ts +219 -0
  85. package/dist/features/sync.d.ts.map +1 -0
  86. package/dist/features/sync.js +461 -0
  87. package/dist/features/sync.js.map +1 -0
  88. package/dist/index.d.ts +3 -0
  89. package/dist/index.d.ts.map +1 -0
  90. package/dist/index.js +56 -0
  91. package/dist/index.js.map +1 -0
  92. package/dist/ingest/compute.d.ts +63 -0
  93. package/dist/ingest/compute.d.ts.map +1 -0
  94. package/dist/ingest/compute.js +161 -0
  95. package/dist/ingest/compute.js.map +1 -0
  96. package/dist/ingest/file.d.ts +40 -0
  97. package/dist/ingest/file.d.ts.map +1 -0
  98. package/dist/ingest/file.js +74 -0
  99. package/dist/ingest/file.js.map +1 -0
  100. package/dist/ingest/visual.d.ts +52 -0
  101. package/dist/ingest/visual.d.ts.map +1 -0
  102. package/dist/ingest/visual.js +294 -0
  103. package/dist/ingest/visual.js.map +1 -0
  104. package/dist/parser/docx-parser.d.ts +12 -0
  105. package/dist/parser/docx-parser.d.ts.map +1 -0
  106. package/dist/parser/docx-parser.js +375 -0
  107. package/dist/parser/docx-parser.js.map +1 -0
  108. package/dist/parser/html-parser.d.ts +16 -0
  109. package/dist/parser/html-parser.d.ts.map +1 -0
  110. package/dist/parser/html-parser.js +119 -0
  111. package/dist/parser/html-parser.js.map +1 -0
  112. package/dist/parser/index.d.ts +125 -0
  113. package/dist/parser/index.d.ts.map +1 -0
  114. package/dist/parser/index.js +364 -0
  115. package/dist/parser/index.js.map +1 -0
  116. package/dist/parser/pdf-extract.d.ts +49 -0
  117. package/dist/parser/pdf-extract.d.ts.map +1 -0
  118. package/dist/parser/pdf-extract.js +114 -0
  119. package/dist/parser/pdf-extract.js.map +1 -0
  120. package/dist/parser/pdf-filter.d.ts +91 -0
  121. package/dist/parser/pdf-filter.d.ts.map +1 -0
  122. package/dist/parser/pdf-filter.js +522 -0
  123. package/dist/parser/pdf-filter.js.map +1 -0
  124. package/dist/parser/title-extractor.d.ts +28 -0
  125. package/dist/parser/title-extractor.d.ts.map +1 -0
  126. package/dist/parser/title-extractor.js +105 -0
  127. package/dist/parser/title-extractor.js.map +1 -0
  128. package/dist/pdf-visual/captioner.d.ts +11 -0
  129. package/dist/pdf-visual/captioner.d.ts.map +1 -0
  130. package/dist/pdf-visual/captioner.js +48 -0
  131. package/dist/pdf-visual/captioner.js.map +1 -0
  132. package/dist/pdf-visual/captioners/fast.d.ts +7 -0
  133. package/dist/pdf-visual/captioners/fast.d.ts.map +1 -0
  134. package/dist/pdf-visual/captioners/fast.js +108 -0
  135. package/dist/pdf-visual/captioners/fast.js.map +1 -0
  136. package/dist/pdf-visual/captioners/quality.d.ts +7 -0
  137. package/dist/pdf-visual/captioners/quality.d.ts.map +1 -0
  138. package/dist/pdf-visual/captioners/quality.js +101 -0
  139. package/dist/pdf-visual/captioners/quality.js.map +1 -0
  140. package/dist/pdf-visual/captioners/shared.d.ts +81 -0
  141. package/dist/pdf-visual/captioners/shared.d.ts.map +1 -0
  142. package/dist/pdf-visual/captioners/shared.js +156 -0
  143. package/dist/pdf-visual/captioners/shared.js.map +1 -0
  144. package/dist/pdf-visual/detector.d.ts +9 -0
  145. package/dist/pdf-visual/detector.d.ts.map +1 -0
  146. package/dist/pdf-visual/detector.js +269 -0
  147. package/dist/pdf-visual/detector.js.map +1 -0
  148. package/dist/pdf-visual/index.d.ts +13 -0
  149. package/dist/pdf-visual/index.d.ts.map +1 -0
  150. package/dist/pdf-visual/index.js +53 -0
  151. package/dist/pdf-visual/index.js.map +1 -0
  152. package/dist/pdf-visual/renderer.d.ts +9 -0
  153. package/dist/pdf-visual/renderer.d.ts.map +1 -0
  154. package/dist/pdf-visual/renderer.js +186 -0
  155. package/dist/pdf-visual/renderer.js.map +1 -0
  156. package/dist/pdf-visual/types.d.ts +53 -0
  157. package/dist/pdf-visual/types.d.ts.map +1 -0
  158. package/dist/pdf-visual/types.js +17 -0
  159. package/dist/pdf-visual/types.js.map +1 -0
  160. package/dist/server/error-utils.d.ts +63 -0
  161. package/dist/server/error-utils.d.ts.map +1 -0
  162. package/dist/server/error-utils.js +124 -0
  163. package/dist/server/error-utils.js.map +1 -0
  164. package/dist/server/index.d.ts +216 -0
  165. package/dist/server/index.d.ts.map +1 -0
  166. package/dist/server/index.js +1068 -0
  167. package/dist/server/index.js.map +1 -0
  168. package/dist/server/list-scanner.d.ts +30 -0
  169. package/dist/server/list-scanner.d.ts.map +1 -0
  170. package/dist/server/list-scanner.js +50 -0
  171. package/dist/server/list-scanner.js.map +1 -0
  172. package/dist/server/tool-definitions.d.ts +8 -0
  173. package/dist/server/tool-definitions.d.ts.map +1 -0
  174. package/dist/server/tool-definitions.js +181 -0
  175. package/dist/server/tool-definitions.js.map +1 -0
  176. package/dist/server/tool-input.d.ts +37 -0
  177. package/dist/server/tool-input.d.ts.map +1 -0
  178. package/dist/server/tool-input.js +211 -0
  179. package/dist/server/tool-input.js.map +1 -0
  180. package/dist/server/types.d.ts +307 -0
  181. package/dist/server/types.d.ts.map +1 -0
  182. package/dist/server/types.js +3 -0
  183. package/dist/server/types.js.map +1 -0
  184. package/dist/server-main.d.ts +40 -0
  185. package/dist/server-main.d.ts.map +1 -0
  186. package/dist/server-main.js +254 -0
  187. package/dist/server-main.js.map +1 -0
  188. package/dist/utils/base-dirs.d.ts +145 -0
  189. package/dist/utils/base-dirs.d.ts.map +1 -0
  190. package/dist/utils/base-dirs.js +344 -0
  191. package/dist/utils/base-dirs.js.map +1 -0
  192. package/dist/utils/errors.d.ts +32 -0
  193. package/dist/utils/errors.d.ts.map +1 -0
  194. package/dist/utils/errors.js +61 -0
  195. package/dist/utils/errors.js.map +1 -0
  196. package/dist/utils/limits.d.ts +26 -0
  197. package/dist/utils/limits.d.ts.map +1 -0
  198. package/dist/utils/limits.js +28 -0
  199. package/dist/utils/limits.js.map +1 -0
  200. package/dist/utils/list-sources.d.ts +38 -0
  201. package/dist/utils/list-sources.d.ts.map +1 -0
  202. package/dist/utils/list-sources.js +40 -0
  203. package/dist/utils/list-sources.js.map +1 -0
  204. package/dist/utils/raw-data-utils.d.ts +76 -0
  205. package/dist/utils/raw-data-utils.d.ts.map +1 -0
  206. package/dist/utils/raw-data-utils.js +202 -0
  207. package/dist/utils/raw-data-utils.js.map +1 -0
  208. package/dist/utils/scan.d.ts +114 -0
  209. package/dist/utils/scan.d.ts.map +1 -0
  210. package/dist/utils/scan.js +209 -0
  211. package/dist/utils/scan.js.map +1 -0
  212. package/dist/utils/scope-match.d.ts +38 -0
  213. package/dist/utils/scope-match.d.ts.map +1 -0
  214. package/dist/utils/scope-match.js +86 -0
  215. package/dist/utils/scope-match.js.map +1 -0
  216. package/dist/utils/sensitive-path.d.ts +9 -0
  217. package/dist/utils/sensitive-path.d.ts.map +1 -0
  218. package/dist/utils/sensitive-path.js +78 -0
  219. package/dist/utils/sensitive-path.js.map +1 -0
  220. package/dist/utils/sync-path-key.d.ts +14 -0
  221. package/dist/utils/sync-path-key.d.ts.map +1 -0
  222. package/dist/utils/sync-path-key.js +27 -0
  223. package/dist/utils/sync-path-key.js.map +1 -0
  224. package/dist/utils/type-guards.d.ts +18 -0
  225. package/dist/utils/type-guards.d.ts.map +1 -0
  226. package/dist/utils/type-guards.js +34 -0
  227. package/dist/utils/type-guards.js.map +1 -0
  228. package/dist/utils/visual-profile.d.ts +10 -0
  229. package/dist/utils/visual-profile.d.ts.map +1 -0
  230. package/dist/utils/visual-profile.js +15 -0
  231. package/dist/utils/visual-profile.js.map +1 -0
  232. package/dist/vectordb/index.d.ts +132 -0
  233. package/dist/vectordb/index.d.ts.map +1 -0
  234. package/dist/vectordb/index.js +617 -0
  235. package/dist/vectordb/index.js.map +1 -0
  236. package/dist/vectordb/search-filters.d.ts +15 -0
  237. package/dist/vectordb/search-filters.d.ts.map +1 -0
  238. package/dist/vectordb/search-filters.js +129 -0
  239. package/dist/vectordb/search-filters.js.map +1 -0
  240. package/dist/vectordb/types.d.ts +194 -0
  241. package/dist/vectordb/types.d.ts.map +1 -0
  242. package/dist/vectordb/types.js +237 -0
  243. package/dist/vectordb/types.js.map +1 -0
  244. package/package.json +108 -0
  245. package/skills/mcp-local-rag/SKILL.md +234 -0
  246. package/skills/mcp-local-rag/references/cli-reference.md +190 -0
  247. package/skills/mcp-local-rag/references/html-ingestion.md +78 -0
package/README.md ADDED
@@ -0,0 +1,530 @@
1
+ <p align="center">
2
+ <img src="assets/banner.jpg" alt="MCP Local RAG: Search below the surface." width="600" />
3
+ </p>
4
+
5
+ # MCP Local RAG
6
+
7
+ [![GitHub stars](https://img.shields.io/github/stars/shinpr/mcp-local-rag?style=social)](https://github.com/shinpr/mcp-local-rag)
8
+ [![npm version](https://img.shields.io/npm/v/mcp-local-rag.svg)](https://www.npmjs.com/package/mcp-local-rag)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
+ [![MCP Registry](https://img.shields.io/badge/MCP-Registry-green.svg)](https://registry.modelcontextprotocol.io/)
11
+
12
+ <p align="center">
13
+ <strong>English</strong> |
14
+ <a href="README.zh-CN.md">简体中文</a> |
15
+ <a href="README.de.md">Deutsch</a> |
16
+ <a href="README.es.md">Español</a> |
17
+ <a href="README.pt-BR.md">Português (Brasil)</a> |
18
+ <a href="README.fr.md">Français</a>
19
+ </p>
20
+
21
+ Search private documents from an MCP client or the terminal without sending them to an
22
+ embedding API.
23
+
24
+ mcp-local-rag indexes PDF, DOCX, Markdown, and text files on your machine. Search combines
25
+ semantic similarity with keyword matching, so queries can match both intent and exact technical
26
+ terms such as API names, class names, and error codes.
27
+
28
+ ## Features
29
+
30
+ - **Runs locally:** Document parsing, embeddings, storage, and search run on your machine.
31
+ After the initial model download, text ingestion and search work offline.
32
+ - **Hybrid search:** Semantic retrieval finds related concepts, while keyword matching boosts
33
+ exact technical terms.
34
+ - **Configurable embeddings:** Choose a Hugging Face embedding model that fits the language and
35
+ domain of your documents.
36
+ - **Semantic chunking:** Documents are split at topic boundaries instead of fixed character
37
+ counts. Markdown code blocks stay intact.
38
+ - **MCP and CLI:** Use the same index from an AI coding tool or directly from the terminal.
39
+
40
+ No API key, Docker, Python, or external database is required.
41
+
42
+ ## Quick Start
43
+
44
+ ### Requirements
45
+
46
+ - Node.js 22 or later
47
+ - Internet access on first use to download the npm package and embedding model
48
+ - A directory containing the documents you want to search
49
+
50
+ Set `BASE_DIR` to that directory. It is also the security boundary for file operations. Replace
51
+ `/absolute/path/to/your/documents` below with the directory's absolute path.
52
+
53
+ mcp-local-rag uses the standard MCP protocol over a local stdio server, so it works with AI
54
+ coding tools and other MCP hosts that support local MCP servers.
55
+
56
+ Use one of the examples below, or register `npx -y mcp-local-rag` and set `BASE_DIR` using your
57
+ client's MCP configuration format.
58
+
59
+ **For Claude Code:** Run this command:
60
+
61
+ ```bash
62
+ claude mcp add local-rag --scope user --env BASE_DIR=/absolute/path/to/your/documents -- npx -y mcp-local-rag
63
+ ```
64
+
65
+ **For Codex:** Add to `~/.codex/config.toml`:
66
+
67
+ ```toml
68
+ [mcp_servers.local-rag]
69
+ command = "npx"
70
+ args = ["-y", "mcp-local-rag"]
71
+
72
+ [mcp_servers.local-rag.env]
73
+ BASE_DIR = "/absolute/path/to/your/documents"
74
+ ```
75
+
76
+ **For OpenCode:** Add to `~/.config/opencode/opencode.json` (or `opencode.jsonc`):
77
+
78
+ ```json
79
+ {
80
+ "$schema": "https://opencode.ai/config.json",
81
+ "mcp": {
82
+ "local-rag": {
83
+ "type": "local",
84
+ "command": ["npx", "-y", "mcp-local-rag"],
85
+ "environment": {
86
+ "BASE_DIR": "/absolute/path/to/your/documents"
87
+ }
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+ **For Cursor:** Add to `~/.cursor/mcp.json`:
94
+
95
+ ```json
96
+ {
97
+ "mcpServers": {
98
+ "local-rag": {
99
+ "command": "npx",
100
+ "args": ["-y", "mcp-local-rag"],
101
+ "env": {
102
+ "BASE_DIR": "/absolute/path/to/your/documents"
103
+ }
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ Restart the client, then ask it to build the index:
110
+
111
+ ```text
112
+ Sync all documents in the configured root and wait until it finishes.
113
+ ```
114
+
115
+ The first sync downloads the default embedding model (about 90 MB) and may take 1–2 minutes
116
+ before ingestion starts. Later runs use the local cache.
117
+
118
+ Once the sync completes:
119
+
120
+ ```text
121
+ What does the API documentation say about authentication?
122
+ ```
123
+
124
+ ### CLI Quick Start
125
+
126
+ To use the CLI without an MCP client:
127
+
128
+ ```bash
129
+ npx mcp-local-rag ingest ./docs/
130
+ npx mcp-local-rag query "authentication API"
131
+ ```
132
+
133
+ The CLI uses the current directory as its document root by default. Run both commands from the
134
+ same directory so they use the same default index, or set `BASE_DIR` and `DB_PATH` explicitly.
135
+
136
+ ## Why This Exists
137
+
138
+ Some document sets cannot be sent to a hosted embedding service because of confidentiality or
139
+ organizational policy. Keeping the index local makes them searchable without adding a per-query
140
+ API cost.
141
+
142
+ Semantic search alone can miss exact identifiers that matter in technical documentation.
143
+ Keyword reranking keeps those terms visible without giving up natural-language retrieval.
144
+
145
+ ## Supported Content
146
+
147
+ | Input | How to ingest |
148
+ |---|---|
149
+ | PDF, DOCX, TXT, Markdown | File ingestion or directory sync |
150
+ | HTML already fetched by the client | `ingest_data`; cleaned with Readability and converted to Markdown |
151
+ | Plain text or Markdown held in memory | `ingest_data` with a stable source identifier |
152
+
153
+ HTML fetching is not built into the server. An MCP client can fetch a page and pass its HTML to
154
+ `ingest_data`.
155
+
156
+ Excel, PowerPoint, standalone images, and source-code file extensions are not supported by file
157
+ ingestion. PDFs can optionally use a local vision model to describe figures, but this is not OCR
158
+ or image search.
159
+
160
+ ## MCP Tools
161
+
162
+ | Tool | Purpose |
163
+ |---|---|
164
+ | `sync_start` | Reconcile the index with all configured roots or one path |
165
+ | `sync_status` | Poll a running sync job |
166
+ | `ingest_file` | Ingest or replace one file |
167
+ | `ingest_data` | Ingest text, Markdown, or HTML already held by the client |
168
+ | `query_documents` | Search with semantic matching and keyword boost |
169
+ | `read_chunk_neighbors` | Read surrounding chunks from a search result |
170
+ | `list_files` | Show supported files and their ingestion state |
171
+ | `delete_file` | Delete an indexed file or an `ingest_data` item |
172
+ | `status` | Show index and search status |
173
+
174
+ ### Syncing a Document Root
175
+
176
+ `sync_start` ingests new and changed files, skips byte-identical files, and removes index entries
177
+ for files that no longer exist:
178
+
179
+ ```text
180
+ Sync everything under the configured document roots and wait for completion.
181
+ ```
182
+
183
+ The tool returns a `jobId` immediately. Clients should poll `sync_status` until its state becomes
184
+ `succeeded` or `failed`. A changed PDF keeps the visual profile it was indexed with; `sync_start`
185
+ cannot change it. Set `STORE_IMAGES=true` in the MCP server environment to store supported PDF and
186
+ DOCX images for new or changed files selected by sync; unchanged files remain skipped.
187
+
188
+ Only one sync job is retained by the server process. A newer job replaces a finished record, and
189
+ restarting the server discards it.
190
+
191
+ ### Ingesting One File
192
+
193
+ `ingest_file` accepts PDF, DOCX, TXT, and Markdown. MCP file paths must be absolute and must stay
194
+ inside a configured document root:
195
+
196
+ ```text
197
+ Ingest the document at /Users/me/docs/api-spec.pdf.
198
+ ```
199
+
200
+ Re-ingesting the same path replaces its existing chunks.
201
+
202
+ ### Searching and Reading More Context
203
+
204
+ ```text
205
+ What does the API documentation say about authentication?
206
+ Find the documented behavior of ERR_CONNECTION_REFUSED.
207
+ ```
208
+
209
+ Results contain the text, source path, title, chunk index, relevance score, and any images stored
210
+ on that chunk. MCP returns each image as an image content block paired with its result identity;
211
+ CLI `query` includes an `images` array of `{ imageIndex, mimeType, data }` on every result. Pass the
212
+ `chunkIndex` and either `filePath` or `source` from a result to `read_chunk_neighbors` when the
213
+ answer needs more context:
214
+
215
+ ```text
216
+ Read the surrounding chunks for that authentication result.
217
+ ```
218
+
219
+ Both `query_documents` and `list_files` accept an optional absolute `scope` path prefix, or a
220
+ list of prefixes. A prefix matches the exact path and its descendants.
221
+
222
+ ### Ingesting HTML
223
+
224
+ Use `ingest_data` after the MCP client fetches a page:
225
+
226
+ ```text
227
+ Fetch https://example.com/docs and ingest the HTML.
228
+ ```
229
+
230
+ The server extracts the main article, converts it to Markdown, and stores it under the supplied
231
+ source identifier. Reusing the same source updates the existing content.
232
+
233
+ Respect the source site's terms and copyright when indexing external content.
234
+
235
+ ### PDF Visual Captions and Stored Images
236
+
237
+ Visual mode adds a generated caption for figure-heavy PDF pages. It is opt-in and does not load
238
+ a vision model during normal ingestion.
239
+
240
+ ```text
241
+ Ingest /Users/me/docs/research-paper.pdf with visual: true.
242
+ ```
243
+
244
+ ```bash
245
+ npx mcp-local-rag ingest ./docs/research-paper.pdf --visual
246
+ ```
247
+
248
+ Image storage is independent of visual captions. Set `STORE_IMAGES=true` for the MCP server, or
249
+ pass `--images` to CLI ingestion and sync:
250
+
251
+ ```bash
252
+ npx mcp-local-rag ingest ./docs/research-paper.pdf --images
253
+ npx mcp-local-rag sync ./docs/ --images
254
+ ```
255
+
256
+ PDF storage uses detected figure/table regions. DOCX storage includes only PNG/JPEG images that
257
+ the existing Mammoth conversion emits as `<img>`; charts, SmartArt, and shapes are not separately
258
+ rendered. Stored images follow their surrounding text into the final semantic chunk and do not
259
+ alter ranking, scores, or result count.
260
+
261
+ | `visual` / `--visual` | `STORE_IMAGES` / `--images` | PDF behavior |
262
+ |---|---|---|
263
+ | false | false | Text only; no visual captions or returned images. |
264
+ | true | false | Generated captions become searchable text; no images are stored or returned. |
265
+ | true | true | Generated captions become searchable text, and images from matched chunks are returned inline. |
266
+ | false | true | Images are attached to nearby retained PDF text and returned inline for matched chunks; the VLM is not imported, loaded, or run. |
267
+
268
+ | Profile | Model cache | Use case |
269
+ |---|---:|---|
270
+ | `fast` (default) | about 250 MB | Lightweight visual indexing |
271
+ | `quality` | about 1.7 GB | Figures containing labels, annotations, or other in-image text |
272
+
273
+ Select the larger model with `visualQuality: "quality"` over MCP or
274
+ `--visual-quality quality` over CLI. Measured CPU inference was about three times as slow as
275
+ `fast`, though results depend on hardware and model updates.
276
+
277
+ #### Updating Existing `quality` Captions
278
+
279
+ From 0.18.4 `quality` runs Qwen3.5-2B; earlier versions ran Qwen2.5-VL-3B. Captions already indexed
280
+ keep the wording the old model produced, and `sync` will not redo them, so re-ingest the files you
281
+ want refreshed:
282
+
283
+ ```bash
284
+ npx mcp-local-rag ingest ./docs/research-paper.pdf --visual --visual-quality quality
285
+ ```
286
+
287
+ Add `--images` if the file was ingested with it, because a run without it replaces the stored
288
+ images. The old model stays on disk. Once nothing else uses it, delete
289
+ `onnx-community/Qwen2.5-VL-3B-Instruct-ONNX/` from the model cache directory — `<cache-dir>`, which
290
+ defaults to `./models/`.
291
+
292
+ #### Visual Mode Across Syncs
293
+
294
+ The profile a PDF was indexed with is recorded, and `sync` reuses it: a PDF indexed with `fast` or
295
+ `quality` is re-ingested with that same profile, and a PDF with no recorded profile is ingested as
296
+ text.
297
+
298
+ ```bash
299
+ npx mcp-local-rag sync ./docs/ # keep each PDF's recorded profile
300
+ npx mcp-local-rag sync ./docs/ --visual # request fast for every PDF in scope
301
+ npx mcp-local-rag sync ./docs/ --visual --visual-quality quality
302
+ ```
303
+
304
+ `--visual` overrides recorded profiles, so it also captions PDFs that were indexed as text.
305
+ Changing a profile re-ingests the PDF even when the file itself has not changed; running the same
306
+ command again does nothing and loads no model. Image settings are never recorded, so `--images`
307
+ and `STORE_IMAGES` never cause a re-ingest.
308
+
309
+ To turn captions off for a path, run `ingest` on it: a successful normal ingest clears the
310
+ recorded profile. To retry a page whose captioning failed, run `ingest <path> --visual
311
+ --visual-quality <profile>` with the profile you want — a plain `ingest` clears it instead. If a
312
+ PDF's indexed rows disagree about the profile, sync stops before changing anything and names the
313
+ file; re-run it with `--visual` to settle the profile.
314
+
315
+ Captions are auxiliary text, not faithful transcriptions. Treat retrieved captions and document
316
+ text as untrusted input rather than instructions.
317
+
318
+ At high limits, matched chunks and their attachments can approach the model/client context ceiling;
319
+ choose the query limit with the calling model's available context in mind.
320
+
321
+ ## CLI
322
+
323
+ The CLI uses the same parser, embedder, and vector store without an MCP client:
324
+
325
+ ```bash
326
+ npx mcp-local-rag ingest ./docs/
327
+ npx mcp-local-rag sync ./docs/
328
+ npx mcp-local-rag query "authentication API"
329
+ npx mcp-local-rag query "auth" --scope /docs/api --scope /docs/guide
330
+ npx mcp-local-rag read-neighbors --file-path /abs/path.md --chunk-index 5
331
+ npx mcp-local-rag list
332
+ npx mcp-local-rag status
333
+ npx mcp-local-rag delete ./docs/old.pdf
334
+ npx mcp-local-rag delete --source "https://example.com/docs"
335
+ ```
336
+
337
+ Global options such as `--db-path`, `--cache-dir`, and `--model-name` go before the subcommand.
338
+ Subcommand options go after it:
339
+
340
+ ```bash
341
+ npx mcp-local-rag --db-path ./my-db query "authentication"
342
+ ```
343
+
344
+ Run `npx mcp-local-rag --help` for the complete command reference.
345
+
346
+ The CLI does not read MCP client configuration. Set the same environment variables or flags if
347
+ both interfaces should share an index. In particular, `MODEL_NAME` and the CLI `--model-name`
348
+ must match for a shared database.
349
+
350
+ ## Search Tuning
351
+
352
+ Keyword boost is enabled by default. Relevance-gap grouping and the distance and file filters are
353
+ optional controls for corpora that need tighter result selection.
354
+
355
+ | Variable | Default | Description |
356
+ |----------|---------|-------------|
357
+ | `RAG_HYBRID_WEIGHT` | `0.6` | Keyword boost factor (0.0–1.0). 0 disables keyword reranking; 1 applies the maximum boost. |
358
+ | `RAG_GROUPING` | (not set) | `similar` keeps the first relevance group; `related` keeps up to two, using significant vector-distance gaps as boundaries. |
359
+ | `RAG_MAX_DISTANCE` | (not set) | Filter out low-relevance results (e.g., `0.5`). |
360
+ | `RAG_MAX_FILES` | (not set) | Limit results to top N files (e.g., `1` for single best file). |
361
+
362
+ For API specifications and other documents containing many identifiers, a stronger keyword
363
+ weight can improve exact-term ranking:
364
+
365
+ ```json
366
+ "env": {
367
+ "RAG_HYBRID_WEIGHT": "0.7"
368
+ }
369
+ ```
370
+
371
+ - `0.7`: slightly stronger exact-term reranking than the default
372
+ - `1.0`: maximum keyword boost
373
+
374
+ ## How It Works
375
+
376
+ During ingestion:
377
+
378
+ 1. The parser extracts text for the input format.
379
+ 2. The semantic chunker finds topic boundaries and preserves Markdown code blocks.
380
+ 3. Transformers.js creates embeddings locally.
381
+ 4. LanceDB stores the chunks, metadata, vectors, and full-text index.
382
+
383
+ During search:
384
+
385
+ 1. The query is embedded with the same model.
386
+ 2. Vector search retrieves semantically related chunks.
387
+ 3. Optional distance and relevance-group filters narrow the candidates when configured.
388
+ 4. Full-text matches boost exact query terms.
389
+
390
+ ## Agent Skills
391
+
392
+ [Agent Skills](https://agentskills.io/) provide query and ingestion guidance for AI assistants:
393
+
394
+ ```bash
395
+ npx mcp-local-rag skills install --claude-code
396
+ npx mcp-local-rag skills install --claude-code --global
397
+ npx mcp-local-rag skills install --codex
398
+ ```
399
+
400
+ Installed skills cover query formulation, result refinement, and HTML ingestion. Ask the
401
+ assistant to use the mcp-local-rag skill explicitly if it does not activate automatically.
402
+
403
+ ## Configuration
404
+
405
+ The MCP server reads environment variables. The CLI accepts the listed global environment
406
+ variables and flags; image storage on CLI ingestion and sync is enabled only with `--images`.
407
+
408
+ | Environment Variable | CLI Flag | Default | Description |
409
+ |---------------------|----------|---------|-------------|
410
+ | `BASE_DIR` | `--base-dir` | Current directory | One document root; the CLI flag is repeatable on `ingest`, `list`, and `sync` |
411
+ | `BASE_DIRS` | N/A | (unset) | JSON array of document roots; takes precedence over `BASE_DIR` |
412
+ | `DB_PATH` | `--db-path` | `./lancedb/` | Vector database location |
413
+ | `CACHE_DIR` | `--cache-dir` | `./models/` | Model cache directory |
414
+ | `HF_ENDPOINT` | N/A | `https://huggingface.co` | Hugging Face model download endpoint; use a mirror URL when direct downloads are blocked |
415
+ | `MODEL_NAME` | `--model-name` | `Xenova/all-MiniLM-L6-v2` | Hugging Face embedding model |
416
+ | `MAX_FILE_SIZE` | `--max-file-size` | `104857600` (100MB) | Maximum file size in bytes |
417
+ | `CHUNK_MIN_LENGTH` | `--chunk-min-length` | `50` | Minimum length in characters (1–10000) for ordinary chunks; a fragment of content split to fit the model's token limit can be shorter |
418
+ | `STORE_IMAGES` | N/A | `false` | MCP server only: store supported PDF/DOCX images and return them with matched chunks. CLI uses `--images`. |
419
+ | `RAG_DEVICE` | N/A | `cpu` | ONNX Runtime execution device |
420
+ | `RAG_DTYPE` | N/A | `fp32` | Embedding dtype passed to the selected model |
421
+
422
+ ### Document Roots (`BASE_DIR` and `BASE_DIRS`)
423
+
424
+ mcp-local-rag only allows file operations inside configured roots. For multiple roots,
425
+ `BASE_DIRS` must be a JSON array of non-empty paths:
426
+
427
+ ```bash
428
+ export BASE_DIRS='["/Users/me/Documents/work","/Users/me/Projects/specs"]'
429
+ ```
430
+
431
+ Root configuration is resolved in this order:
432
+
433
+ 1. CLI `--base-dir <path>` flags (repeatable on `ingest`, `list`, and `sync`)
434
+ 2. `BASE_DIRS`
435
+ 3. `BASE_DIR`
436
+ 4. Current directory
437
+
438
+ Each source replaces the lower-priority source rather than merging with it. Invalid `BASE_DIRS`
439
+ configuration fails instead of falling back to `BASE_DIR` or the current directory. `status`
440
+ remains available in MCP so the client can report the configuration error.
441
+
442
+ ```bash
443
+ npx mcp-local-rag ingest --base-dir /Users/me/work --base-dir /Users/me/specs /Users/me/work/readme.md
444
+ npx mcp-local-rag list --base-dir /Users/me/work --base-dir /Users/me/specs
445
+ npx mcp-local-rag sync --base-dir /Users/me/work --base-dir /Users/me/specs
446
+ BASE_DIRS='["/Users/me/work","/Users/me/specs"]' npx mcp-local-rag list
447
+ ```
448
+
449
+ ### Storage and Models
450
+
451
+ `DB_PATH` and `CACHE_DIR` are relative to the process working directory by default. Set absolute
452
+ paths when the MCP client may start the server from different project directories.
453
+
454
+ Set `MODEL_NAME` or pass `--model-name` to choose a Hugging Face embedding model that fits the
455
+ language and domain of your documents.
456
+
457
+ mcp-local-rag generates embeddings with mean pooling and L2 normalization. When choosing a
458
+ model, check whether these settings match its recommended inference setup, since the pooling
459
+ method can affect retrieval quality.
460
+
461
+ Changing `MODEL_NAME`, `RAG_DEVICE`, or `RAG_DTYPE` can make existing vectors incompatible.
462
+ Use a new `DB_PATH` or delete the existing index and re-ingest after changing the embedding
463
+ configuration.
464
+
465
+ An example model for English documents is `Xenova/bge-small-en-v1.5`.
466
+
467
+ ## Security and Operation
468
+
469
+ - File access is restricted to `BASE_DIR`, `BASE_DIRS`, or CLI `--base-dir` roots.
470
+ - Symlinks that resolve outside every configured root are rejected.
471
+ - Document processing and search make no network requests after the required models are cached.
472
+ - The server is designed for one local user and does not provide authentication or access control.
473
+ - Do not run multiple CLI or MCP writers against the same `DB_PATH`. Read-only queries can run
474
+ while a sync is active.
475
+ - Back up an index by copying its `DB_PATH` directory while no writer is active.
476
+
477
+ <details>
478
+ <summary><strong>Troubleshooting</strong></summary>
479
+
480
+ ### "No results found"
481
+
482
+ Documents must be ingested first. Run `"List all ingested files"` to verify.
483
+
484
+ ### Model download failed
485
+
486
+ Check internet connection. If behind a proxy, configure network settings. The model can also be [downloaded manually](https://huggingface.co/Xenova/all-MiniLM-L6-v2).
487
+
488
+ ### "File too large"
489
+
490
+ Default limit is 100MB. Split large files or increase `MAX_FILE_SIZE`.
491
+
492
+ ### Slow queries
493
+
494
+ Check chunk count with `status`. Large documents with many chunks may slow queries. Consider splitting very large files.
495
+
496
+ ### "Path outside BASE_DIR"
497
+
498
+ Ensure file paths are within one of the configured roots (`BASE_DIR`, any `BASE_DIRS` entry, or any CLI `--base-dir`). Use absolute paths.
499
+
500
+ ### "BASE_DIRS must be a JSON array..."
501
+
502
+ `BASE_DIRS` accepts a JSON array of one or more non-empty path strings:
503
+
504
+ - Valid: `BASE_DIRS='["/Users/me/work","/Users/me/specs"]'`
505
+ - Invalid: `BASE_DIRS=/a:/b` (delimiter syntax not supported)
506
+ - Invalid: `BASE_DIRS='[]'` (empty array)
507
+
508
+ ### MCP client doesn't see tools
509
+
510
+ 1. Verify config file syntax
511
+ 2. Restart client completely (Cmd+Q on Mac for Cursor)
512
+ 3. Test directly: `npx mcp-local-rag` should run without errors
513
+
514
+ </details>
515
+
516
+ ## Contributing
517
+
518
+ Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and guidelines.
519
+
520
+ ## License
521
+
522
+ MIT License. Free for personal and commercial use.
523
+
524
+ ## Blog Posts
525
+
526
+ - [Building a Local RAG for Agentic Coding](https://www.norsica.jp/blog/local-rag-agentic-coding): Technical deep-dive into the semantic chunking and hybrid search design.
527
+
528
+ ## Acknowledgments
529
+
530
+ Built with [Model Context Protocol](https://modelcontextprotocol.io/) by Anthropic, [LanceDB](https://lancedb.com/), and [Transformers.js](https://huggingface.co/docs/transformers.js).