├── .gitignore ├── LICENSE ├── README.md ├── llms-install.md ├── package-lock.json ├── package.json ├── src ├── embeddings.ts ├── index.ts └── types │ └── ollama.d.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | .pnp/ 4 | .pnp.js 5 | 6 | # Build output 7 | build/ 8 | dist/ 9 | *.tsbuildinfo 10 | 11 | # Environment variables 12 | .env 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | # Logs 19 | logs/ 20 | *.log 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | # Editor directories and files 26 | .idea/ 27 | .vscode/ 28 | *.swp 29 | *.swo 30 | .DS_Store 31 | 32 | # Qdrant storage 33 | qdrant_storage/ 34 | 35 | # Test coverage 36 | coverage/ 37 | 38 | # Local documentation files 39 | MCPguide.txt 40 | MCPspecification.txt 41 | 42 | # Local development folders 43 | mcp-server-qdrant/ 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MCP-Ragdocs 2 | 3 | A Model Context Protocol (MCP) server that enables semantic search and retrieval of documentation using a vector database (Qdrant). This server allows you to add documentation from URLs or local files and then search through them using natural language queries. 4 | 5 | ## Quick Install Guide 6 | 7 | 1. Install the package globally: 8 | ```bash 9 | npm install -g @qpd-v/mcp-server-ragdocs 10 | ``` 11 | 12 | 2. Start Qdrant (using Docker): 13 | ```bash 14 | docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant 15 | ``` 16 | 17 | 3. Ensure Ollama is running with the default embedding model: 18 | ```bash 19 | ollama pull nomic-embed-text 20 | ``` 21 | 22 | 4. Add to your configuration file: 23 | - For Cline: `%AppData%\Roaming\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json` 24 | - For Roo-Code: `%AppData%\Roaming\Code\User\globalStorage\rooveterinaryinc.roo-cline\settings\cline_mcp_settings.json` 25 | - For Claude Desktop: `%AppData%\Claude\claude_desktop_config.json` 26 | 27 | ```json 28 | { 29 | "mcpServers": { 30 | "ragdocs": { 31 | "command": "node", 32 | "args": ["C:/Users/YOUR_USERNAME/AppData/Roaming/npm/node_modules/@qpd-v/mcp-server-ragdocs/build/index.js"], 33 | "env": { 34 | "QDRANT_URL": "http://127.0.0.1:6333", 35 | "EMBEDDING_PROVIDER": "ollama", 36 | "OLLAMA_URL": "http://localhost:11434" 37 | } 38 | } 39 | } 40 | } 41 | ``` 42 | 43 | 5. Verify installation: 44 | ```bash 45 | # Check Qdrant is running 46 | curl http://localhost:6333/collections 47 | 48 | # Check Ollama has the model 49 | ollama list | grep nomic-embed-text 50 | ``` 51 | 52 | ## Version 53 | 54 | Current version: 0.1.6 55 | 56 | ## Features 57 | 58 | - Add documentation from URLs or local files 59 | - Store documentation in a vector database for semantic search 60 | - Search through documentation using natural language 61 | - List all documentation sources 62 | 63 | ## Installation 64 | 65 | Install globally using npm: 66 | 67 | ```bash 68 | npm install -g @qpd-v/mcp-server-ragdocs 69 | ``` 70 | 71 | This will install the server in your global npm directory, which you'll need for the configuration steps below. 72 | 73 | ## Requirements 74 | 75 | - Node.js 16 or higher 76 | - Qdrant (either local or cloud) 77 | - One of the following for embeddings: 78 | - Ollama running locally (default, free) 79 | - OpenAI API key (optional, paid) 80 | 81 | ## Qdrant Setup Options 82 | 83 | ### Option 1: Local Qdrant 84 | 85 | 1. Using Docker (recommended): 86 | ```bash 87 | docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant 88 | ``` 89 | 90 | 2. Or download from [Qdrant's website](https://qdrant.tech/documentation/quick-start/) 91 | 92 | ### Option 2: Qdrant Cloud 93 | 94 | 1. Create an account at [Qdrant Cloud](https://cloud.qdrant.io/) 95 | 2. Create a new cluster 96 | 3. Get your cluster URL and API key from the dashboard 97 | 4. Use these in your configuration (see Configuration section below) 98 | 99 | ## Configuration 100 | 101 | The server can be used with both Cline/Roo and Claude Desktop. Configuration differs slightly between them: 102 | 103 | ### Cline Configuration 104 | 105 | Add to your Cline settings file (`%AppData%\Roaming\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json`) 106 | AND/OR 107 | Add to your Roo-Code settings file (`%AppData%\Roaming\Code\User\globalStorage\rooveterinaryinc.roo-cline\settings\cline_mcp_settings.json`): 108 | 109 | 1. Using npm global install (recommended): 110 | ```json 111 | { 112 | "mcpServers": { 113 | "ragdocs": { 114 | "command": "node", 115 | "args": ["C:/Users/YOUR_USERNAME/AppData/Roaming/npm/node_modules/@qpd-v/mcp-server-ragdocs/build/index.js"], 116 | "env": { 117 | "QDRANT_URL": "http://127.0.0.1:6333", 118 | "EMBEDDING_PROVIDER": "ollama", 119 | "OLLAMA_URL": "http://localhost:11434" 120 | } 121 | } 122 | } 123 | } 124 | ``` 125 | 126 | For OpenAI instead of Ollama: 127 | ```json 128 | { 129 | "mcpServers": { 130 | "ragdocs": { 131 | "command": "node", 132 | "args": ["C:/Users/YOUR_USERNAME/AppData/Roaming/npm/node_modules/@qpd-v/mcp-server-ragdocs/build/index.js"], 133 | "env": { 134 | "QDRANT_URL": "http://127.0.0.1:6333", 135 | "EMBEDDING_PROVIDER": "openai", 136 | "OPENAI_API_KEY": "your-openai-api-key" 137 | } 138 | } 139 | } 140 | } 141 | ``` 142 | 143 | 2. Using local development setup: 144 | ```json 145 | { 146 | "mcpServers": { 147 | "ragdocs": { 148 | "command": "node", 149 | "args": ["PATH_TO_PROJECT/mcp-ragdocs/build/index.js"], 150 | "env": { 151 | "QDRANT_URL": "http://127.0.0.1:6333", 152 | "EMBEDDING_PROVIDER": "ollama", 153 | "OLLAMA_URL": "http://localhost:11434" 154 | } 155 | } 156 | } 157 | } 158 | ``` 159 | 160 | ### Claude Desktop Configuration 161 | 162 | Add to your Claude Desktop config file: 163 | - Windows: `%AppData%\Claude\claude_desktop_config.json` 164 | - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` 165 | 166 | 1. Windows Setup with Ollama (using full paths): 167 | ```json 168 | { 169 | "mcpServers": { 170 | "ragdocs": { 171 | "command": "C:\\Program Files\\nodejs\\node.exe", 172 | "args": [ 173 | "C:\\Users\\YOUR_USERNAME\\AppData\\Roaming\\npm\\node_modules\\@qpd-v/mcp-server-ragdocs\\build\\index.js" 174 | ], 175 | "env": { 176 | "QDRANT_URL": "http://127.0.0.1:6333", 177 | "EMBEDDING_PROVIDER": "ollama", 178 | "OLLAMA_URL": "http://localhost:11434" 179 | } 180 | } 181 | } 182 | } 183 | ``` 184 | 185 | Windows Setup with OpenAI: 186 | ```json 187 | { 188 | "mcpServers": { 189 | "ragdocs": { 190 | "command": "C:\\Program Files\\nodejs\\node.exe", 191 | "args": [ 192 | "C:\\Users\\YOUR_USERNAME\\AppData\\Roaming\\npm\\node_modules\\@qpd-v/mcp-server-ragdocs\\build\\index.js" 193 | ], 194 | "env": { 195 | "QDRANT_URL": "http://127.0.0.1:6333", 196 | "EMBEDDING_PROVIDER": "openai", 197 | "OPENAI_API_KEY": "your-openai-api-key" 198 | } 199 | } 200 | } 201 | } 202 | ``` 203 | 204 | 2. macOS Setup with Ollama: 205 | ```json 206 | { 207 | "mcpServers": { 208 | "ragdocs": { 209 | "command": "/usr/local/bin/node", 210 | "args": [ 211 | "/usr/local/lib/node_modules/@qpd-v/mcp-server-ragdocs/build/index.js" 212 | ], 213 | "env": { 214 | "QDRANT_URL": "http://127.0.0.1:6333", 215 | "EMBEDDING_PROVIDER": "ollama", 216 | "OLLAMA_URL": "http://localhost:11434" 217 | } 218 | } 219 | } 220 | } 221 | ``` 222 | 223 | ### Qdrant Cloud Configuration 224 | 225 | For either Cline or Claude Desktop, when using Qdrant Cloud, modify the env section: 226 | 227 | With Ollama: 228 | ```json 229 | { 230 | "env": { 231 | "QDRANT_URL": "https://your-cluster-url.qdrant.tech", 232 | "QDRANT_API_KEY": "your-qdrant-api-key", 233 | "EMBEDDING_PROVIDER": "ollama", 234 | "OLLAMA_URL": "http://localhost:11434" 235 | } 236 | } 237 | ``` 238 | 239 | With OpenAI: 240 | ```json 241 | { 242 | "env": { 243 | "QDRANT_URL": "https://your-cluster-url.qdrant.tech", 244 | "QDRANT_API_KEY": "your-qdrant-api-key", 245 | "EMBEDDING_PROVIDER": "openai", 246 | "OPENAI_API_KEY": "your-openai-api-key" 247 | } 248 | } 249 | ``` 250 | 251 | ### Environment Variables 252 | 253 | #### Qdrant Configuration 254 | - `QDRANT_URL` (required): URL of your Qdrant instance 255 | - For local: http://localhost:6333 256 | - For cloud: https://your-cluster-url.qdrant.tech 257 | - `QDRANT_API_KEY` (required for cloud): Your Qdrant Cloud API key 258 | 259 | #### Embeddings Configuration 260 | - `EMBEDDING_PROVIDER` (optional): Choose between 'ollama' (default) or 'openai' 261 | - `EMBEDDING_MODEL` (optional): 262 | - For Ollama: defaults to 'nomic-embed-text' 263 | - For OpenAI: defaults to 'text-embedding-3-small' 264 | - `OLLAMA_URL` (optional): URL of your Ollama instance (defaults to http://localhost:11434) 265 | - `OPENAI_API_KEY` (required if using OpenAI): Your OpenAI API key 266 | 267 | ## Available Tools 268 | 269 | 1. `add_documentation` 270 | - Add documentation from a URL to the RAG database 271 | - Parameters: 272 | - `url`: URL of the documentation to fetch 273 | 274 | 2. `search_documentation` 275 | - Search through stored documentation 276 | - Parameters: 277 | - `query`: Search query 278 | - `limit` (optional): Maximum number of results to return (default: 5) 279 | 280 | 3. `list_sources` 281 | - List all documentation sources currently stored 282 | - No parameters required 283 | 284 | ## Example Usage 285 | 286 | In Claude Desktop or any other MCP-compatible client: 287 | 288 | 1. Add documentation: 289 | ``` 290 | Add this documentation: https://docs.example.com/api 291 | ``` 292 | 293 | 2. Search documentation: 294 | ``` 295 | Search the documentation for information about authentication 296 | ``` 297 | 298 | 3. List sources: 299 | ``` 300 | What documentation sources are available? 301 | ``` 302 | 303 | ## Development 304 | 305 | 1. Clone the repository: 306 | ```bash 307 | git clone https://github.com/qpd-v/mcp-server-ragdocs.git 308 | cd mcp-server-ragdocs 309 | ``` 310 | 311 | 2. Install dependencies: 312 | ```bash 313 | npm install 314 | ``` 315 | 316 | 3. Build the project: 317 | ```bash 318 | npm run build 319 | ``` 320 | 321 | 4. Run locally: 322 | ```bash 323 | npm start 324 | ``` 325 | 326 | ## License 327 | 328 | MIT 329 | 330 | ## Troubleshooting 331 | 332 | ### Common Issues 333 | 334 | 1. **Qdrant Connection Error** 335 | ``` 336 | Error: Failed to connect to Qdrant at http://localhost:6333 337 | ``` 338 | - Check if Docker is running 339 | - Verify Qdrant container is running: `docker ps | grep qdrant` 340 | - Try restarting the container 341 | 342 | 2. **Ollama Model Missing** 343 | ``` 344 | Error: Model nomic-embed-text not found 345 | ``` 346 | - Run: `ollama pull nomic-embed-text` 347 | - Verify model is installed: `ollama list` 348 | 349 | 3. **Configuration Path Issues** 350 | - Windows: Replace `YOUR_USERNAME` with your actual Windows username 351 | - Check file permissions 352 | - Verify the paths exist 353 | 354 | 4. **npm Global Install Issues** 355 | - Try installing with admin privileges 356 | - Check npm is in PATH: `npm -v` 357 | - Verify global installation: `npm list -g @qpd-v/mcp-server-ragdocs` 358 | 359 | For other issues, please check: 360 | - Docker logs: `docker logs $(docker ps -q --filter ancestor=qdrant/qdrant)` 361 | - Ollama status: `ollama list` 362 | - Node.js version: `node -v` (should be 16 or higher) 363 | 364 | ## Contributing 365 | 366 | Contributions are welcome! Please feel free to submit a Pull Request. 367 | -------------------------------------------------------------------------------- /llms-install.md: -------------------------------------------------------------------------------- 1 | --- 2 | mcp_server_name: ragdocs 3 | runtime: nodejs 4 | capabilities: 5 | tools: 6 | - add_documentation 7 | - search_documentation 8 | - list_sources 9 | dependencies: 10 | docker: required 11 | nodejs: ">=16" 12 | npm: required 13 | ollama: required 14 | --- 15 | 16 | # LLM Installation Guide for mcp-ragdocs 17 | 18 | This guide provides step-by-step instructions for AI agents to install and configure the mcp-ragdocs server. 19 | 20 | ## Prerequisites Check 21 | 22 | 1. **Node.js:** 23 | ```bash 24 | node -v 25 | ``` 26 | *Expected Output:* Version 16 or higher (e.g., `v16.0.0`) 27 | *Validation:* Output version number must be >= 16.0.0 28 | 29 | 2. **Docker:** 30 | ```bash 31 | docker --version 32 | ``` 33 | *Expected Output:* Docker version information (e.g., `Docker version 24.0.0`) 34 | *Validation:* Command should return version without error 35 | 36 | 3. **Ollama:** 37 | ```bash 38 | ollama --version 39 | ``` 40 | *Expected Output:* Ollama version information 41 | *Validation:* Command should return version without error 42 | 43 | 4. **npm:** 44 | ```bash 45 | npm -v 46 | ``` 47 | *Expected Output:* npm version information 48 | *Validation:* Command should return version without error 49 | 50 | ## Installation Steps 51 | 52 | 1. **Install Package:** 53 | ```bash 54 | npm install -g @qpd-v/mcp-server-ragdocs 55 | ``` 56 | *Expected Output:* Success message indicating package was added 57 | *Validation:* No error messages, package added to global modules 58 | 59 | 2. **Verify Global Installation:** 60 | ```bash 61 | npm list -g @qpd-v/mcp-server-ragdocs 62 | ``` 63 | *Expected Output:* Should show @qpd-v/mcp-server-ragdocs@ 64 | *Validation:* Package is listed in global modules 65 | 66 | 3. **Start Qdrant:** 67 | ```bash 68 | docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant 69 | ``` 70 | *Expected Output:* Qdrant startup messages, including "Access web UI at http://localhost:6333/dashboard" 71 | *Validation:* Server starts without errors, ports 6333 and 6334 are bound 72 | 73 | 4. **Verify Qdrant:** 74 | ```bash 75 | curl http://localhost:6333/collections 76 | ``` 77 | *Expected Output:* HTTP 200 status code with JSON response: `{"result":{"collections":[]}}` 78 | *Validation:* Response is valid JSON and includes "collections" key 79 | 80 | 5. **Install Ollama Model:** 81 | ```bash 82 | ollama pull nomic-embed-text 83 | ``` 84 | *Expected Output:* Download progress followed by completion message 85 | *Validation:* Model is downloaded without errors 86 | 87 | 6. **Verify Ollama Model:** 88 | ```bash 89 | ollama list | grep nomic-embed-text 90 | ``` 91 | *Expected Output:* Line containing "nomic-embed-text" with size and modification date 92 | *Validation:* Model is listed in available models 93 | 94 | ## Configuration 95 | 96 | ### Configuration File Paths 97 | **Select the appropriate path based on your environment:** 98 | 99 | 1. **Cline:** 100 | ``` 101 | %AppData%\Roaming\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json 102 | ``` 103 | *Validation:* File should exist and be writable 104 | 105 | 2. **Roo-Code:** 106 | ``` 107 | %AppData%\Roaming\Code\User\globalStorage\rooveterinaryinc.roo-cline\settings\cline_mcp_settings.json 108 | ``` 109 | *Validation:* File should exist and be writable 110 | 111 | 3. **Claude Desktop:** 112 | ``` 113 | %AppData%\Claude\claude_desktop_config.json 114 | ``` 115 | *Validation:* File should exist and be writable 116 | 117 | ### Configuration Content 118 | Add this configuration to the selected file(s): 119 | 120 | ```json 121 | { 122 | "mcpServers": { 123 | "ragdocs": { 124 | "command": "node", 125 | "args": ["C:/Users/YOUR_USERNAME/AppData/Roaming/npm/node_modules/@qpd-v/mcp-server-ragdocs/build/index.js"], 126 | "env": { 127 | "QDRANT_URL": "http://127.0.0.1:6333", 128 | "EMBEDDING_PROVIDER": "ollama", 129 | "OLLAMA_URL": "http://localhost:11434" 130 | }, 131 | "disabled": false, 132 | "autoApprove": [] 133 | } 134 | } 135 | } 136 | ``` 137 | *Required Actions:* 138 | 1. Replace `YOUR_USERNAME` with actual Windows username 139 | 2. Ensure `disabled` is set to `false` 140 | 3. Ensure `autoApprove` is set to `[]` 141 | *Validation:* JSON should be valid and parseable 142 | 143 | ## Verification Steps 144 | 145 | 1. **Verify Qdrant API:** 146 | ```bash 147 | curl http://localhost:6333/collections 148 | ``` 149 | *Expected Output:* HTTP 200 status code with JSON response: `{"result":{"collections":[]}}` 150 | *Validation:* Response is valid JSON and service is responsive 151 | 152 | 2. **Verify Ollama Service:** 153 | ```bash 154 | ollama list 155 | ``` 156 | *Expected Output:* List including "nomic-embed-text" entry 157 | *Validation:* Model is available and service is running 158 | 159 | 3. **Test Documentation Import:** 160 | ``` 161 | Add this documentation: https://docs.qdrant.tech/ 162 | ``` 163 | *Expected Output:* Success message indicating documentation was added 164 | *Validation:* Documentation appears in sources list 165 | 166 | 4. **Test Search Functionality:** 167 | ``` 168 | Search the documentation for "what is Qdrant?" 169 | ``` 170 | *Expected Output:* Relevant search results from the added documentation 171 | *Validation:* Results contain information about Qdrant 172 | 173 | ## Troubleshooting Guide 174 | 175 | ### Diagnostic Checks 176 | 177 | 1. **Verify Node.js Environment:** 178 | ```bash 179 | node -v && npm -v 180 | ``` 181 | *Expected Output:* Two version numbers (Node.js >= 16.0.0) 182 | *Validation:* Both commands return version numbers without errors 183 | *Resolution if Failed:* Reinstall Node.js from nodejs.org 184 | 185 | 2. **Check Docker Container:** 186 | ```bash 187 | docker ps | grep qdrant 188 | ``` 189 | *Expected Output:* Line containing "qdrant/qdrant" with port mappings 190 | *Validation:* Container is running and ports are mapped correctly 191 | *Resolution if Failed:* Run `docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant` 192 | 193 | 3. **Verify Ollama Service:** 194 | ```bash 195 | ollama list 196 | ``` 197 | *Expected Output:* List of models including nomic-embed-text 198 | *Validation:* Service is running and model is available 199 | *Resolution if Failed:* Run `ollama pull nomic-embed-text` 200 | 201 | 4. **Check Configuration Files:** 202 | ```bash 203 | # For each config file path 204 | cat "" 205 | ``` 206 | *Expected Output:* Valid JSON with ragdocs configuration 207 | *Validation:* JSON is valid and contains required fields 208 | *Resolution if Failed:* Copy configuration template from above 209 | 210 | ### Common Error Solutions 211 | 212 | 1. **Qdrant Connection Error:** 213 | ```bash 214 | docker restart $(docker ps -q --filter ancestor=qdrant/qdrant) 215 | ``` 216 | *Expected Output:* Container ID 217 | *Validation:* Qdrant service becomes available at http://localhost:6333 218 | 219 | 2. **Ollama Model Missing:** 220 | ```bash 221 | ollama pull nomic-embed-text 222 | ``` 223 | *Expected Output:* Download progress and completion message 224 | *Validation:* Model appears in `ollama list` output 225 | 226 | 3. **npm Global Install Error:** 227 | ```bash 228 | npm cache clean --force && npm install -g @qpd-v/mcp-server-ragdocs 229 | ``` 230 | *Expected Output:* Success message from npm install 231 | *Validation:* Package appears in `npm list -g` 232 | 233 | 4. **Path Resolution Issues:** 234 | - Windows Username: Replace `YOUR_USERNAME` with output of `echo %USERNAME%` 235 | - Path Separators: Use `/` for npm paths in configuration 236 | - Verify paths exist: Check each directory in the path exists 237 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qpd-v/mcp-server-ragdocs", 3 | "version": "0.1.6", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@qpd-v/mcp-server-ragdocs", 9 | "version": "0.1.6", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@modelcontextprotocol/sdk": "0.6.0", 13 | "@qdrant/js-client-rest": "^1.8.0", 14 | "axios": "^1.7.9", 15 | "cheerio": "^1.0.0", 16 | "ollama": "^0.5.11", 17 | "openai": "^4.76.2", 18 | "playwright": "^1.49.1" 19 | }, 20 | "bin": { 21 | "mcp-server-ragdocs": "build/index.js" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^20.11.24", 25 | "typescript": "^5.3.3" 26 | } 27 | }, 28 | "node_modules/@fastify/busboy": { 29 | "version": "2.1.1", 30 | "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", 31 | "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", 32 | "license": "MIT", 33 | "engines": { 34 | "node": ">=14" 35 | } 36 | }, 37 | "node_modules/@modelcontextprotocol/sdk": { 38 | "version": "0.6.0", 39 | "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.6.0.tgz", 40 | "integrity": "sha512-9rsDudGhDtMbvxohPoMMyAUOmEzQsOK+XFchh6gZGqo8sx9sBuZQs+CUttXqa8RZXKDaJRCN2tUtgGof7jRkkw==", 41 | "license": "MIT", 42 | "dependencies": { 43 | "content-type": "^1.0.5", 44 | "raw-body": "^3.0.0", 45 | "zod": "^3.23.8" 46 | } 47 | }, 48 | "node_modules/@qdrant/js-client-rest": { 49 | "version": "1.8.0", 50 | "resolved": "https://registry.npmjs.org/@qdrant/js-client-rest/-/js-client-rest-1.8.0.tgz", 51 | "integrity": "sha512-W6VfCVenWVAXH/PBSbUEMqsGvsrNK5aBJlugYjZxK7Rm7aX96C13XGLIO5jMxa+B9dULwgoXT3kO+SdOSAEXFw==", 52 | "license": "Apache-2.0", 53 | "dependencies": { 54 | "@qdrant/openapi-typescript-fetch": "1.2.5", 55 | "@sevinf/maybe": "0.5.0", 56 | "undici": "5.28.3" 57 | }, 58 | "engines": { 59 | "node": ">=18.0.0", 60 | "pnpm": ">=8" 61 | }, 62 | "peerDependencies": { 63 | "typescript": ">=4.7" 64 | } 65 | }, 66 | "node_modules/@qdrant/openapi-typescript-fetch": { 67 | "version": "1.2.5", 68 | "resolved": "https://registry.npmjs.org/@qdrant/openapi-typescript-fetch/-/openapi-typescript-fetch-1.2.5.tgz", 69 | "integrity": "sha512-gy9k3C/cZWlc6PoH8iry3d+zOAAuRwxY7dfx856orwhEpp2EFvI/jhZ+nP+uajR7soD9UcrzYTQkWMMD4xVIWg==", 70 | "license": "MIT", 71 | "engines": { 72 | "node": ">=18.0.0", 73 | "pnpm": ">=8" 74 | } 75 | }, 76 | "node_modules/@sevinf/maybe": { 77 | "version": "0.5.0", 78 | "resolved": "https://registry.npmjs.org/@sevinf/maybe/-/maybe-0.5.0.tgz", 79 | "integrity": "sha512-ARhyoYDnY1LES3vYI0fiG6e9esWfTNcXcO6+MPJJXcnyMV3bim4lnFt45VXouV7y82F4x3YH8nOQ6VztuvUiWg==", 80 | "license": "MIT" 81 | }, 82 | "node_modules/@types/node": { 83 | "version": "20.17.10", 84 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz", 85 | "integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==", 86 | "license": "MIT", 87 | "dependencies": { 88 | "undici-types": "~6.19.2" 89 | } 90 | }, 91 | "node_modules/@types/node-fetch": { 92 | "version": "2.6.12", 93 | "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", 94 | "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", 95 | "license": "MIT", 96 | "dependencies": { 97 | "@types/node": "*", 98 | "form-data": "^4.0.0" 99 | } 100 | }, 101 | "node_modules/abort-controller": { 102 | "version": "3.0.0", 103 | "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", 104 | "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", 105 | "license": "MIT", 106 | "dependencies": { 107 | "event-target-shim": "^5.0.0" 108 | }, 109 | "engines": { 110 | "node": ">=6.5" 111 | } 112 | }, 113 | "node_modules/agentkeepalive": { 114 | "version": "4.5.0", 115 | "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", 116 | "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", 117 | "license": "MIT", 118 | "dependencies": { 119 | "humanize-ms": "^1.2.1" 120 | }, 121 | "engines": { 122 | "node": ">= 8.0.0" 123 | } 124 | }, 125 | "node_modules/asynckit": { 126 | "version": "0.4.0", 127 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 128 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 129 | "license": "MIT" 130 | }, 131 | "node_modules/axios": { 132 | "version": "1.7.9", 133 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", 134 | "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", 135 | "license": "MIT", 136 | "dependencies": { 137 | "follow-redirects": "^1.15.6", 138 | "form-data": "^4.0.0", 139 | "proxy-from-env": "^1.1.0" 140 | } 141 | }, 142 | "node_modules/boolbase": { 143 | "version": "1.0.0", 144 | "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", 145 | "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", 146 | "license": "ISC" 147 | }, 148 | "node_modules/bytes": { 149 | "version": "3.1.2", 150 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 151 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 152 | "license": "MIT", 153 | "engines": { 154 | "node": ">= 0.8" 155 | } 156 | }, 157 | "node_modules/cheerio": { 158 | "version": "1.0.0", 159 | "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", 160 | "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", 161 | "license": "MIT", 162 | "dependencies": { 163 | "cheerio-select": "^2.1.0", 164 | "dom-serializer": "^2.0.0", 165 | "domhandler": "^5.0.3", 166 | "domutils": "^3.1.0", 167 | "encoding-sniffer": "^0.2.0", 168 | "htmlparser2": "^9.1.0", 169 | "parse5": "^7.1.2", 170 | "parse5-htmlparser2-tree-adapter": "^7.0.0", 171 | "parse5-parser-stream": "^7.1.2", 172 | "undici": "^6.19.5", 173 | "whatwg-mimetype": "^4.0.0" 174 | }, 175 | "engines": { 176 | "node": ">=18.17" 177 | }, 178 | "funding": { 179 | "url": "https://github.com/cheeriojs/cheerio?sponsor=1" 180 | } 181 | }, 182 | "node_modules/cheerio-select": { 183 | "version": "2.1.0", 184 | "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", 185 | "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", 186 | "license": "BSD-2-Clause", 187 | "dependencies": { 188 | "boolbase": "^1.0.0", 189 | "css-select": "^5.1.0", 190 | "css-what": "^6.1.0", 191 | "domelementtype": "^2.3.0", 192 | "domhandler": "^5.0.3", 193 | "domutils": "^3.0.1" 194 | }, 195 | "funding": { 196 | "url": "https://github.com/sponsors/fb55" 197 | } 198 | }, 199 | "node_modules/cheerio/node_modules/undici": { 200 | "version": "6.21.0", 201 | "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.0.tgz", 202 | "integrity": "sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw==", 203 | "license": "MIT", 204 | "engines": { 205 | "node": ">=18.17" 206 | } 207 | }, 208 | "node_modules/combined-stream": { 209 | "version": "1.0.8", 210 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 211 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 212 | "license": "MIT", 213 | "dependencies": { 214 | "delayed-stream": "~1.0.0" 215 | }, 216 | "engines": { 217 | "node": ">= 0.8" 218 | } 219 | }, 220 | "node_modules/content-type": { 221 | "version": "1.0.5", 222 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 223 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 224 | "license": "MIT", 225 | "engines": { 226 | "node": ">= 0.6" 227 | } 228 | }, 229 | "node_modules/css-select": { 230 | "version": "5.1.0", 231 | "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", 232 | "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", 233 | "license": "BSD-2-Clause", 234 | "dependencies": { 235 | "boolbase": "^1.0.0", 236 | "css-what": "^6.1.0", 237 | "domhandler": "^5.0.2", 238 | "domutils": "^3.0.1", 239 | "nth-check": "^2.0.1" 240 | }, 241 | "funding": { 242 | "url": "https://github.com/sponsors/fb55" 243 | } 244 | }, 245 | "node_modules/css-what": { 246 | "version": "6.1.0", 247 | "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", 248 | "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", 249 | "license": "BSD-2-Clause", 250 | "engines": { 251 | "node": ">= 6" 252 | }, 253 | "funding": { 254 | "url": "https://github.com/sponsors/fb55" 255 | } 256 | }, 257 | "node_modules/delayed-stream": { 258 | "version": "1.0.0", 259 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 260 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 261 | "license": "MIT", 262 | "engines": { 263 | "node": ">=0.4.0" 264 | } 265 | }, 266 | "node_modules/depd": { 267 | "version": "2.0.0", 268 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 269 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 270 | "license": "MIT", 271 | "engines": { 272 | "node": ">= 0.8" 273 | } 274 | }, 275 | "node_modules/dom-serializer": { 276 | "version": "2.0.0", 277 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", 278 | "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", 279 | "license": "MIT", 280 | "dependencies": { 281 | "domelementtype": "^2.3.0", 282 | "domhandler": "^5.0.2", 283 | "entities": "^4.2.0" 284 | }, 285 | "funding": { 286 | "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" 287 | } 288 | }, 289 | "node_modules/domelementtype": { 290 | "version": "2.3.0", 291 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", 292 | "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", 293 | "funding": [ 294 | { 295 | "type": "github", 296 | "url": "https://github.com/sponsors/fb55" 297 | } 298 | ], 299 | "license": "BSD-2-Clause" 300 | }, 301 | "node_modules/domhandler": { 302 | "version": "5.0.3", 303 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", 304 | "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", 305 | "license": "BSD-2-Clause", 306 | "dependencies": { 307 | "domelementtype": "^2.3.0" 308 | }, 309 | "engines": { 310 | "node": ">= 4" 311 | }, 312 | "funding": { 313 | "url": "https://github.com/fb55/domhandler?sponsor=1" 314 | } 315 | }, 316 | "node_modules/domutils": { 317 | "version": "3.1.0", 318 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", 319 | "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", 320 | "license": "BSD-2-Clause", 321 | "dependencies": { 322 | "dom-serializer": "^2.0.0", 323 | "domelementtype": "^2.3.0", 324 | "domhandler": "^5.0.3" 325 | }, 326 | "funding": { 327 | "url": "https://github.com/fb55/domutils?sponsor=1" 328 | } 329 | }, 330 | "node_modules/encoding-sniffer": { 331 | "version": "0.2.0", 332 | "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", 333 | "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", 334 | "license": "MIT", 335 | "dependencies": { 336 | "iconv-lite": "^0.6.3", 337 | "whatwg-encoding": "^3.1.1" 338 | }, 339 | "funding": { 340 | "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" 341 | } 342 | }, 343 | "node_modules/entities": { 344 | "version": "4.5.0", 345 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 346 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 347 | "license": "BSD-2-Clause", 348 | "engines": { 349 | "node": ">=0.12" 350 | }, 351 | "funding": { 352 | "url": "https://github.com/fb55/entities?sponsor=1" 353 | } 354 | }, 355 | "node_modules/event-target-shim": { 356 | "version": "5.0.1", 357 | "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", 358 | "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", 359 | "license": "MIT", 360 | "engines": { 361 | "node": ">=6" 362 | } 363 | }, 364 | "node_modules/follow-redirects": { 365 | "version": "1.15.9", 366 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", 367 | "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", 368 | "funding": [ 369 | { 370 | "type": "individual", 371 | "url": "https://github.com/sponsors/RubenVerborgh" 372 | } 373 | ], 374 | "license": "MIT", 375 | "engines": { 376 | "node": ">=4.0" 377 | }, 378 | "peerDependenciesMeta": { 379 | "debug": { 380 | "optional": true 381 | } 382 | } 383 | }, 384 | "node_modules/form-data": { 385 | "version": "4.0.1", 386 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", 387 | "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", 388 | "license": "MIT", 389 | "dependencies": { 390 | "asynckit": "^0.4.0", 391 | "combined-stream": "^1.0.8", 392 | "mime-types": "^2.1.12" 393 | }, 394 | "engines": { 395 | "node": ">= 6" 396 | } 397 | }, 398 | "node_modules/form-data-encoder": { 399 | "version": "1.7.2", 400 | "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", 401 | "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", 402 | "license": "MIT" 403 | }, 404 | "node_modules/formdata-node": { 405 | "version": "4.4.1", 406 | "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", 407 | "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", 408 | "license": "MIT", 409 | "dependencies": { 410 | "node-domexception": "1.0.0", 411 | "web-streams-polyfill": "4.0.0-beta.3" 412 | }, 413 | "engines": { 414 | "node": ">= 12.20" 415 | } 416 | }, 417 | "node_modules/fsevents": { 418 | "version": "2.3.2", 419 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 420 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 421 | "hasInstallScript": true, 422 | "license": "MIT", 423 | "optional": true, 424 | "os": [ 425 | "darwin" 426 | ], 427 | "engines": { 428 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 429 | } 430 | }, 431 | "node_modules/htmlparser2": { 432 | "version": "9.1.0", 433 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", 434 | "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", 435 | "funding": [ 436 | "https://github.com/fb55/htmlparser2?sponsor=1", 437 | { 438 | "type": "github", 439 | "url": "https://github.com/sponsors/fb55" 440 | } 441 | ], 442 | "license": "MIT", 443 | "dependencies": { 444 | "domelementtype": "^2.3.0", 445 | "domhandler": "^5.0.3", 446 | "domutils": "^3.1.0", 447 | "entities": "^4.5.0" 448 | } 449 | }, 450 | "node_modules/http-errors": { 451 | "version": "2.0.0", 452 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 453 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 454 | "license": "MIT", 455 | "dependencies": { 456 | "depd": "2.0.0", 457 | "inherits": "2.0.4", 458 | "setprototypeof": "1.2.0", 459 | "statuses": "2.0.1", 460 | "toidentifier": "1.0.1" 461 | }, 462 | "engines": { 463 | "node": ">= 0.8" 464 | } 465 | }, 466 | "node_modules/humanize-ms": { 467 | "version": "1.2.1", 468 | "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", 469 | "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", 470 | "license": "MIT", 471 | "dependencies": { 472 | "ms": "^2.0.0" 473 | } 474 | }, 475 | "node_modules/iconv-lite": { 476 | "version": "0.6.3", 477 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 478 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 479 | "license": "MIT", 480 | "dependencies": { 481 | "safer-buffer": ">= 2.1.2 < 3.0.0" 482 | }, 483 | "engines": { 484 | "node": ">=0.10.0" 485 | } 486 | }, 487 | "node_modules/inherits": { 488 | "version": "2.0.4", 489 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 490 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 491 | "license": "ISC" 492 | }, 493 | "node_modules/mime-db": { 494 | "version": "1.52.0", 495 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 496 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 497 | "license": "MIT", 498 | "engines": { 499 | "node": ">= 0.6" 500 | } 501 | }, 502 | "node_modules/mime-types": { 503 | "version": "2.1.35", 504 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 505 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 506 | "license": "MIT", 507 | "dependencies": { 508 | "mime-db": "1.52.0" 509 | }, 510 | "engines": { 511 | "node": ">= 0.6" 512 | } 513 | }, 514 | "node_modules/ms": { 515 | "version": "2.1.3", 516 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 517 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 518 | "license": "MIT" 519 | }, 520 | "node_modules/node-domexception": { 521 | "version": "1.0.0", 522 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 523 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", 524 | "funding": [ 525 | { 526 | "type": "github", 527 | "url": "https://github.com/sponsors/jimmywarting" 528 | }, 529 | { 530 | "type": "github", 531 | "url": "https://paypal.me/jimmywarting" 532 | } 533 | ], 534 | "license": "MIT", 535 | "engines": { 536 | "node": ">=10.5.0" 537 | } 538 | }, 539 | "node_modules/node-fetch": { 540 | "version": "2.7.0", 541 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 542 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 543 | "license": "MIT", 544 | "dependencies": { 545 | "whatwg-url": "^5.0.0" 546 | }, 547 | "engines": { 548 | "node": "4.x || >=6.0.0" 549 | }, 550 | "peerDependencies": { 551 | "encoding": "^0.1.0" 552 | }, 553 | "peerDependenciesMeta": { 554 | "encoding": { 555 | "optional": true 556 | } 557 | } 558 | }, 559 | "node_modules/nth-check": { 560 | "version": "2.1.1", 561 | "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", 562 | "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", 563 | "license": "BSD-2-Clause", 564 | "dependencies": { 565 | "boolbase": "^1.0.0" 566 | }, 567 | "funding": { 568 | "url": "https://github.com/fb55/nth-check?sponsor=1" 569 | } 570 | }, 571 | "node_modules/ollama": { 572 | "version": "0.5.11", 573 | "resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.11.tgz", 574 | "integrity": "sha512-lDAKcpmBU3VAOGF05NcQipHNKTdpKfAHpZ7bjCsElkUkmX7SNZImi6lwIxz/l1zQtLq0S3wuLneRuiXxX2KIew==", 575 | "license": "MIT", 576 | "dependencies": { 577 | "whatwg-fetch": "^3.6.20" 578 | } 579 | }, 580 | "node_modules/openai": { 581 | "version": "4.76.2", 582 | "resolved": "https://registry.npmjs.org/openai/-/openai-4.76.2.tgz", 583 | "integrity": "sha512-T9ZyxAFwLNZz3onC+SFvSR0POF18egIsY8lLze9e2YBe1wzQNf8IHcIgFPWizGPpoCGv/9i3IdTAx3EnLmTL4A==", 584 | "license": "Apache-2.0", 585 | "dependencies": { 586 | "@types/node": "^18.11.18", 587 | "@types/node-fetch": "^2.6.4", 588 | "abort-controller": "^3.0.0", 589 | "agentkeepalive": "^4.2.1", 590 | "form-data-encoder": "1.7.2", 591 | "formdata-node": "^4.3.2", 592 | "node-fetch": "^2.6.7" 593 | }, 594 | "bin": { 595 | "openai": "bin/cli" 596 | }, 597 | "peerDependencies": { 598 | "zod": "^3.23.8" 599 | }, 600 | "peerDependenciesMeta": { 601 | "zod": { 602 | "optional": true 603 | } 604 | } 605 | }, 606 | "node_modules/openai/node_modules/@types/node": { 607 | "version": "18.19.68", 608 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.68.tgz", 609 | "integrity": "sha512-QGtpFH1vB99ZmTa63K4/FU8twThj4fuVSBkGddTp7uIL/cuoLWIUSL2RcOaigBhfR+hg5pgGkBnkoOxrTVBMKw==", 610 | "license": "MIT", 611 | "dependencies": { 612 | "undici-types": "~5.26.4" 613 | } 614 | }, 615 | "node_modules/openai/node_modules/undici-types": { 616 | "version": "5.26.5", 617 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 618 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 619 | "license": "MIT" 620 | }, 621 | "node_modules/parse5": { 622 | "version": "7.2.1", 623 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", 624 | "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", 625 | "license": "MIT", 626 | "dependencies": { 627 | "entities": "^4.5.0" 628 | }, 629 | "funding": { 630 | "url": "https://github.com/inikulin/parse5?sponsor=1" 631 | } 632 | }, 633 | "node_modules/parse5-htmlparser2-tree-adapter": { 634 | "version": "7.1.0", 635 | "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", 636 | "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", 637 | "license": "MIT", 638 | "dependencies": { 639 | "domhandler": "^5.0.3", 640 | "parse5": "^7.0.0" 641 | }, 642 | "funding": { 643 | "url": "https://github.com/inikulin/parse5?sponsor=1" 644 | } 645 | }, 646 | "node_modules/parse5-parser-stream": { 647 | "version": "7.1.2", 648 | "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", 649 | "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", 650 | "license": "MIT", 651 | "dependencies": { 652 | "parse5": "^7.0.0" 653 | }, 654 | "funding": { 655 | "url": "https://github.com/inikulin/parse5?sponsor=1" 656 | } 657 | }, 658 | "node_modules/playwright": { 659 | "version": "1.49.1", 660 | "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", 661 | "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", 662 | "license": "Apache-2.0", 663 | "dependencies": { 664 | "playwright-core": "1.49.1" 665 | }, 666 | "bin": { 667 | "playwright": "cli.js" 668 | }, 669 | "engines": { 670 | "node": ">=18" 671 | }, 672 | "optionalDependencies": { 673 | "fsevents": "2.3.2" 674 | } 675 | }, 676 | "node_modules/playwright-core": { 677 | "version": "1.49.1", 678 | "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", 679 | "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", 680 | "license": "Apache-2.0", 681 | "bin": { 682 | "playwright-core": "cli.js" 683 | }, 684 | "engines": { 685 | "node": ">=18" 686 | } 687 | }, 688 | "node_modules/proxy-from-env": { 689 | "version": "1.1.0", 690 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 691 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", 692 | "license": "MIT" 693 | }, 694 | "node_modules/raw-body": { 695 | "version": "3.0.0", 696 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 697 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 698 | "license": "MIT", 699 | "dependencies": { 700 | "bytes": "3.1.2", 701 | "http-errors": "2.0.0", 702 | "iconv-lite": "0.6.3", 703 | "unpipe": "1.0.0" 704 | }, 705 | "engines": { 706 | "node": ">= 0.8" 707 | } 708 | }, 709 | "node_modules/safer-buffer": { 710 | "version": "2.1.2", 711 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 712 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 713 | "license": "MIT" 714 | }, 715 | "node_modules/setprototypeof": { 716 | "version": "1.2.0", 717 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 718 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 719 | "license": "ISC" 720 | }, 721 | "node_modules/statuses": { 722 | "version": "2.0.1", 723 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 724 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 725 | "license": "MIT", 726 | "engines": { 727 | "node": ">= 0.8" 728 | } 729 | }, 730 | "node_modules/toidentifier": { 731 | "version": "1.0.1", 732 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 733 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 734 | "license": "MIT", 735 | "engines": { 736 | "node": ">=0.6" 737 | } 738 | }, 739 | "node_modules/tr46": { 740 | "version": "0.0.3", 741 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 742 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", 743 | "license": "MIT" 744 | }, 745 | "node_modules/typescript": { 746 | "version": "5.7.2", 747 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", 748 | "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", 749 | "license": "Apache-2.0", 750 | "bin": { 751 | "tsc": "bin/tsc", 752 | "tsserver": "bin/tsserver" 753 | }, 754 | "engines": { 755 | "node": ">=14.17" 756 | } 757 | }, 758 | "node_modules/undici": { 759 | "version": "5.28.3", 760 | "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz", 761 | "integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==", 762 | "license": "MIT", 763 | "dependencies": { 764 | "@fastify/busboy": "^2.0.0" 765 | }, 766 | "engines": { 767 | "node": ">=14.0" 768 | } 769 | }, 770 | "node_modules/undici-types": { 771 | "version": "6.19.8", 772 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 773 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 774 | "license": "MIT" 775 | }, 776 | "node_modules/unpipe": { 777 | "version": "1.0.0", 778 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 779 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 780 | "license": "MIT", 781 | "engines": { 782 | "node": ">= 0.8" 783 | } 784 | }, 785 | "node_modules/web-streams-polyfill": { 786 | "version": "4.0.0-beta.3", 787 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", 788 | "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", 789 | "license": "MIT", 790 | "engines": { 791 | "node": ">= 14" 792 | } 793 | }, 794 | "node_modules/webidl-conversions": { 795 | "version": "3.0.1", 796 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 797 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", 798 | "license": "BSD-2-Clause" 799 | }, 800 | "node_modules/whatwg-encoding": { 801 | "version": "3.1.1", 802 | "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", 803 | "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", 804 | "license": "MIT", 805 | "dependencies": { 806 | "iconv-lite": "0.6.3" 807 | }, 808 | "engines": { 809 | "node": ">=18" 810 | } 811 | }, 812 | "node_modules/whatwg-fetch": { 813 | "version": "3.6.20", 814 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", 815 | "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", 816 | "license": "MIT" 817 | }, 818 | "node_modules/whatwg-mimetype": { 819 | "version": "4.0.0", 820 | "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", 821 | "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", 822 | "license": "MIT", 823 | "engines": { 824 | "node": ">=18" 825 | } 826 | }, 827 | "node_modules/whatwg-url": { 828 | "version": "5.0.0", 829 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 830 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 831 | "license": "MIT", 832 | "dependencies": { 833 | "tr46": "~0.0.3", 834 | "webidl-conversions": "^3.0.0" 835 | } 836 | }, 837 | "node_modules/zod": { 838 | "version": "3.24.1", 839 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", 840 | "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", 841 | "license": "MIT", 842 | "funding": { 843 | "url": "https://github.com/sponsors/colinhacks" 844 | } 845 | } 846 | } 847 | } 848 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qpd-v/mcp-server-ragdocs", 3 | "version": "0.1.6", 4 | "description": "A Model Context Protocol server for fetching and storing documentation in a vector database, enabling semantic search and retrieval to augment LLM capabilities with relevant documentation context.", 5 | "private": false, 6 | "type": "module", 7 | "bin": { 8 | "@qpd-v/mcp-server-ragdocs": "./build/index.js" 9 | }, 10 | "files": [ 11 | "build", 12 | "README.md", 13 | "LICENSE" 14 | ], 15 | "scripts": { 16 | "build": "tsc", 17 | "prepare": "npm run build", 18 | "watch": "tsc --watch", 19 | "inspector": "npx @modelcontextprotocol/inspector build/index.js", 20 | "start": "node build/index.js" 21 | }, 22 | "keywords": [ 23 | "mcp", 24 | "model-context-protocol", 25 | "rag", 26 | "documentation", 27 | "vector-database", 28 | "qdrant", 29 | "claude", 30 | "llm" 31 | ], 32 | "author": "qpd-v", 33 | "license": "MIT", 34 | "repository": { 35 | "type": "git", 36 | "url": "git+https://github.com/qpd-v/mcp-server-ragdocs.git" 37 | }, 38 | "bugs": { 39 | "url": "https://github.com/qpd-v/mcp-server-ragdocs/issues" 40 | }, 41 | "homepage": "https://github.com/qpd-v/mcp-server-ragdocs#readme", 42 | "dependencies": { 43 | "@modelcontextprotocol/sdk": "0.6.0", 44 | "@qdrant/js-client-rest": "^1.8.0", 45 | "axios": "^1.7.9", 46 | "cheerio": "^1.0.0", 47 | "ollama": "^0.5.11", 48 | "openai": "^4.76.2", 49 | "playwright": "^1.49.1" 50 | }, 51 | "devDependencies": { 52 | "@types/node": "^20.11.24", 53 | "typescript": "^5.3.3" 54 | }, 55 | "publishConfig": { 56 | "access": "public" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/embeddings.ts: -------------------------------------------------------------------------------- 1 | import ollama from 'ollama'; 2 | import OpenAI from 'openai'; 3 | import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; 4 | 5 | export interface EmbeddingProvider { 6 | generateEmbeddings(text: string): Promise; 7 | getVectorSize(): number; 8 | } 9 | 10 | export class OllamaProvider implements EmbeddingProvider { 11 | private model: string; 12 | 13 | constructor(model: string = 'nomic-embed-text') { 14 | this.model = model; 15 | } 16 | 17 | async generateEmbeddings(text: string): Promise { 18 | try { 19 | console.error('Generating Ollama embeddings for text:', text.substring(0, 50) + '...'); 20 | const response = await ollama.embeddings({ 21 | model: this.model, 22 | prompt: text 23 | }); 24 | console.error('Successfully generated Ollama embeddings with size:', response.embedding.length); 25 | return response.embedding; 26 | } catch (error) { 27 | console.error('Ollama embedding error:', error); 28 | throw new McpError( 29 | ErrorCode.InternalError, 30 | `Failed to generate embeddings with Ollama: ${error}` 31 | ); 32 | } 33 | } 34 | 35 | getVectorSize(): number { 36 | // nomic-embed-text produces 768-dimensional vectors 37 | return 768; 38 | } 39 | } 40 | 41 | export class OpenAIProvider implements EmbeddingProvider { 42 | private client: OpenAI; 43 | private model: string; 44 | 45 | constructor(apiKey: string, model: string = 'text-embedding-3-small') { 46 | this.client = new OpenAI({ apiKey }); 47 | this.model = model; 48 | } 49 | 50 | async generateEmbeddings(text: string): Promise { 51 | try { 52 | console.error('Generating OpenAI embeddings for text:', text.substring(0, 50) + '...'); 53 | const response = await this.client.embeddings.create({ 54 | model: this.model, 55 | input: text, 56 | }); 57 | const embedding = response.data[0].embedding; 58 | console.error('Successfully generated OpenAI embeddings with size:', embedding.length); 59 | return embedding; 60 | } catch (error) { 61 | console.error('OpenAI embedding error:', error); 62 | throw new McpError( 63 | ErrorCode.InternalError, 64 | `Failed to generate embeddings with OpenAI: ${error}` 65 | ); 66 | } 67 | } 68 | 69 | getVectorSize(): number { 70 | // text-embedding-3-small produces 1536-dimensional vectors 71 | return 1536; 72 | } 73 | } 74 | 75 | export class EmbeddingService { 76 | private provider: EmbeddingProvider; 77 | 78 | constructor(provider: EmbeddingProvider) { 79 | this.provider = provider; 80 | } 81 | 82 | async generateEmbeddings(text: string): Promise { 83 | return this.provider.generateEmbeddings(text); 84 | } 85 | 86 | getVectorSize(): number { 87 | return this.provider.getVectorSize(); 88 | } 89 | 90 | static createFromConfig(config: { 91 | provider: 'ollama' | 'openai'; 92 | apiKey?: string; 93 | model?: string; 94 | }): EmbeddingService { 95 | switch (config.provider) { 96 | case 'ollama': 97 | return new EmbeddingService(new OllamaProvider(config.model)); 98 | case 'openai': 99 | if (!config.apiKey) { 100 | throw new McpError( 101 | ErrorCode.InvalidParams, 102 | 'OpenAI API key is required' 103 | ); 104 | } 105 | return new EmbeddingService(new OpenAIProvider(config.apiKey, config.model)); 106 | default: 107 | throw new McpError( 108 | ErrorCode.InvalidParams, 109 | `Unknown embedding provider: ${config.provider}` 110 | ); 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; 3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 4 | import { 5 | CallToolRequestSchema, 6 | ErrorCode, 7 | ListToolsRequestSchema, 8 | McpError, 9 | } from '@modelcontextprotocol/sdk/types.js'; 10 | import { QdrantClient } from '@qdrant/js-client-rest'; 11 | import { chromium } from 'playwright'; 12 | import * as cheerio from 'cheerio'; 13 | import axios from 'axios'; 14 | import crypto from 'crypto'; 15 | import { EmbeddingService } from './embeddings.js'; 16 | 17 | // Environment variables for configuration 18 | const OLLAMA_URL = process.env.OLLAMA_URL || 'http://localhost:11434'; 19 | // Force using IP address to avoid hostname resolution issues 20 | const QDRANT_URL = 'http://127.0.0.1:6333'; 21 | const COLLECTION_NAME = 'documentation'; 22 | const EMBEDDING_PROVIDER = process.env.EMBEDDING_PROVIDER || 'ollama'; 23 | const EMBEDDING_MODEL = process.env.EMBEDDING_MODEL; 24 | const OPENAI_API_KEY = process.env.OPENAI_API_KEY; 25 | 26 | interface QdrantCollectionConfig { 27 | params: { 28 | vectors: { 29 | size: number; 30 | distance: string; 31 | }; 32 | }; 33 | } 34 | 35 | interface QdrantCollectionInfo { 36 | config: QdrantCollectionConfig; 37 | } 38 | 39 | interface DocumentChunk { 40 | text: string; 41 | url: string; 42 | title: string; 43 | timestamp: string; 44 | } 45 | 46 | interface DocumentPayload extends DocumentChunk { 47 | _type: 'DocumentChunk'; 48 | [key: string]: unknown; 49 | } 50 | 51 | function isDocumentPayload(payload: unknown): payload is DocumentPayload { 52 | if (!payload || typeof payload !== 'object') return false; 53 | const p = payload as Partial; 54 | return ( 55 | p._type === 'DocumentChunk' && 56 | typeof p.text === 'string' && 57 | typeof p.url === 'string' && 58 | typeof p.title === 'string' && 59 | typeof p.timestamp === 'string' 60 | ); 61 | } 62 | 63 | class RagDocsServer { 64 | private server: Server; 65 | private qdrantClient!: QdrantClient; 66 | private browser: any; 67 | private embeddingService!: EmbeddingService; 68 | 69 | private async testQdrantConnection() { 70 | try { 71 | const response = await this.qdrantClient.getCollections(); 72 | console.error('Successfully connected to Qdrant. Collections:', response.collections); 73 | } catch (error) { 74 | console.error('Failed initial Qdrant connection test:', error); 75 | if (error instanceof Error) { 76 | throw new McpError( 77 | ErrorCode.InternalError, 78 | `Failed to establish initial connection to Qdrant server: ${error.message}` 79 | ); 80 | } 81 | throw new McpError( 82 | ErrorCode.InternalError, 83 | 'Failed to establish initial connection to Qdrant server: Unknown error' 84 | ); 85 | } 86 | } 87 | 88 | private async init() { 89 | // Test connection with direct axios call 90 | const axiosInstance = axios.create({ 91 | baseURL: 'http://127.0.0.1:6333', 92 | headers: { 93 | 'Content-Type': 'application/json', 94 | 'Accept': 'application/json' 95 | } 96 | }); 97 | 98 | // Test connection 99 | try { 100 | const response = await axiosInstance.get('/collections'); 101 | console.error('Successfully connected to Qdrant:', response.data); 102 | } catch (error) { 103 | console.error('Failed to connect to Qdrant:', error); 104 | throw new McpError( 105 | ErrorCode.InternalError, 106 | 'Failed to establish initial connection to Qdrant server' 107 | ); 108 | } 109 | 110 | // Initialize Qdrant client with minimal configuration 111 | this.qdrantClient = new QdrantClient({ 112 | url: 'http://127.0.0.1:6333' 113 | }); 114 | 115 | // Initialize embedding service from environment configuration 116 | this.embeddingService = EmbeddingService.createFromConfig({ 117 | provider: EMBEDDING_PROVIDER as 'ollama' | 'openai', 118 | model: EMBEDDING_MODEL, 119 | apiKey: OPENAI_API_KEY 120 | }); 121 | 122 | this.setupToolHandlers(); 123 | } 124 | 125 | constructor() { 126 | this.server = new Server( 127 | { 128 | name: 'mcp-ragdocs', 129 | version: '0.1.0', 130 | }, 131 | { 132 | capabilities: { 133 | tools: {}, 134 | }, 135 | } 136 | ); 137 | 138 | // Error handling 139 | this.server.onerror = (error) => console.error('[MCP Error]', error); 140 | process.on('SIGINT', async () => { 141 | await this.cleanup(); 142 | process.exit(0); 143 | }); 144 | } 145 | 146 | private async cleanup() { 147 | if (this.browser) { 148 | await this.browser.close(); 149 | } 150 | await this.server.close(); 151 | } 152 | 153 | private async initBrowser() { 154 | if (!this.browser) { 155 | this.browser = await chromium.launch(); 156 | } 157 | } 158 | 159 | private async getEmbeddings(text: string): Promise { 160 | return this.embeddingService.generateEmbeddings(text); 161 | } 162 | 163 | private async initCollection() { 164 | try { 165 | // First ensure we can connect to Qdrant 166 | await this.testQdrantConnection(); 167 | 168 | const requiredVectorSize = this.embeddingService.getVectorSize(); 169 | 170 | try { 171 | // Check if collection exists 172 | const collections = await this.qdrantClient.getCollections(); 173 | const collection = collections.collections.find(c => c.name === COLLECTION_NAME); 174 | 175 | if (!collection) { 176 | console.error(`Creating new collection with vector size ${requiredVectorSize}`); 177 | await this.qdrantClient.createCollection(COLLECTION_NAME, { 178 | vectors: { 179 | size: requiredVectorSize, 180 | distance: 'Cosine', 181 | }, 182 | }); 183 | return; 184 | } 185 | 186 | // Get collection info to check vector size 187 | const collectionInfo = await this.qdrantClient.getCollection(COLLECTION_NAME) as QdrantCollectionInfo; 188 | const currentVectorSize = collectionInfo.config?.params?.vectors?.size; 189 | 190 | if (!currentVectorSize) { 191 | console.error('Could not determine current vector size, recreating collection...'); 192 | await this.recreateCollection(requiredVectorSize); 193 | return; 194 | } 195 | 196 | if (currentVectorSize !== requiredVectorSize) { 197 | console.error(`Vector size mismatch: collection=${currentVectorSize}, required=${requiredVectorSize}`); 198 | await this.recreateCollection(requiredVectorSize); 199 | } 200 | } catch (error) { 201 | console.error('Failed to initialize collection:', error); 202 | throw new McpError( 203 | ErrorCode.InternalError, 204 | 'Failed to initialize Qdrant collection. Please check server logs for details.' 205 | ); 206 | } 207 | } catch (error) { 208 | if (error instanceof McpError) { 209 | throw error; 210 | } 211 | throw new McpError( 212 | ErrorCode.InternalError, 213 | `Unexpected error initializing Qdrant: ${error}` 214 | ); 215 | } 216 | } 217 | 218 | private async recreateCollection(vectorSize: number) { 219 | try { 220 | console.error('Recreating collection with new vector size...'); 221 | await this.qdrantClient.deleteCollection(COLLECTION_NAME); 222 | await this.qdrantClient.createCollection(COLLECTION_NAME, { 223 | vectors: { 224 | size: vectorSize, 225 | distance: 'Cosine', 226 | }, 227 | }); 228 | console.error(`Collection recreated with new vector size ${vectorSize}`); 229 | } catch (error) { 230 | throw new McpError( 231 | ErrorCode.InternalError, 232 | `Failed to recreate collection: ${error}` 233 | ); 234 | } 235 | } 236 | 237 | private async fetchAndProcessUrl(url: string): Promise { 238 | await this.initBrowser(); 239 | const page = await this.browser.newPage(); 240 | 241 | try { 242 | await page.goto(url, { waitUntil: 'networkidle' }); 243 | const content = await page.content(); 244 | const $ = cheerio.load(content); 245 | 246 | // Remove script tags, style tags, and comments 247 | $('script').remove(); 248 | $('style').remove(); 249 | $('noscript').remove(); 250 | 251 | // Extract main content 252 | const title = $('title').text() || url; 253 | const mainContent = $('main, article, .content, .documentation, body').text(); 254 | 255 | // Split content into chunks 256 | const chunks = this.chunkText(mainContent, 1000); 257 | 258 | return chunks.map(chunk => ({ 259 | text: chunk, 260 | url, 261 | title, 262 | timestamp: new Date().toISOString(), 263 | })); 264 | } catch (error) { 265 | throw new McpError( 266 | ErrorCode.InternalError, 267 | `Failed to fetch URL ${url}: ${error}` 268 | ); 269 | } finally { 270 | await page.close(); 271 | } 272 | } 273 | 274 | private chunkText(text: string, maxChunkSize: number): string[] { 275 | const words = text.split(/\s+/); 276 | const chunks: string[] = []; 277 | let currentChunk: string[] = []; 278 | 279 | for (const word of words) { 280 | currentChunk.push(word); 281 | const currentLength = currentChunk.join(' ').length; 282 | 283 | if (currentLength >= maxChunkSize) { 284 | chunks.push(currentChunk.join(' ')); 285 | currentChunk = []; 286 | } 287 | } 288 | 289 | if (currentChunk.length > 0) { 290 | chunks.push(currentChunk.join(' ')); 291 | } 292 | 293 | return chunks; 294 | } 295 | 296 | private generatePointId(): string { 297 | return crypto.randomBytes(16).toString('hex'); 298 | } 299 | 300 | private setupToolHandlers() { 301 | this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ 302 | tools: [ 303 | { 304 | name: 'add_documentation', 305 | description: 'Add documentation from a URL to the RAG database', 306 | inputSchema: { 307 | type: 'object', 308 | properties: { 309 | url: { 310 | type: 'string', 311 | description: 'URL of the documentation to fetch', 312 | }, 313 | }, 314 | required: ['url'], 315 | }, 316 | }, 317 | { 318 | name: 'search_documentation', 319 | description: 'Search through stored documentation', 320 | inputSchema: { 321 | type: 'object', 322 | properties: { 323 | query: { 324 | type: 'string', 325 | description: 'Search query', 326 | }, 327 | limit: { 328 | type: 'number', 329 | description: 'Maximum number of results to return', 330 | default: 5, 331 | }, 332 | }, 333 | required: ['query'], 334 | }, 335 | }, 336 | { 337 | name: 'list_sources', 338 | description: 'List all documentation sources currently stored', 339 | inputSchema: { 340 | type: 'object', 341 | properties: {}, 342 | }, 343 | }, 344 | { 345 | name: 'test_ollama', 346 | description: 'Test embeddings functionality', 347 | inputSchema: { 348 | type: 'object', 349 | properties: { 350 | text: { 351 | type: 'string', 352 | description: 'Text to generate embeddings for', 353 | }, 354 | provider: { 355 | type: 'string', 356 | description: 'Embedding provider to use (ollama or openai)', 357 | enum: ['ollama', 'openai'], 358 | default: 'ollama' 359 | }, 360 | apiKey: { 361 | type: 'string', 362 | description: 'OpenAI API key (required if provider is openai)', 363 | }, 364 | model: { 365 | type: 'string', 366 | description: 'Model to use for embeddings', 367 | }, 368 | }, 369 | required: ['text'], 370 | }, 371 | }, 372 | ], 373 | })); 374 | 375 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => { 376 | switch (request.params.name) { 377 | case 'add_documentation': 378 | case 'search_documentation': 379 | case 'list_sources': 380 | await this.initCollection(); 381 | break; 382 | } 383 | 384 | switch (request.params.name) { 385 | case 'add_documentation': 386 | return this.handleAddDocumentation(request.params.arguments); 387 | case 'search_documentation': 388 | return this.handleSearchDocumentation(request.params.arguments); 389 | case 'list_sources': 390 | return this.handleListSources(); 391 | case 'test_ollama': 392 | return this.handleTestEmbeddings(request.params.arguments); 393 | default: 394 | throw new McpError( 395 | ErrorCode.MethodNotFound, 396 | `Unknown tool: ${request.params.name}` 397 | ); 398 | } 399 | }); 400 | } 401 | 402 | private async handleTestEmbeddings(args: any) { 403 | if (!args.text || typeof args.text !== 'string') { 404 | throw new McpError(ErrorCode.InvalidParams, 'Text is required'); 405 | } 406 | 407 | try { 408 | // Create a new embedding service instance with the requested configuration 409 | const tempEmbeddingService = EmbeddingService.createFromConfig({ 410 | provider: args.provider || 'ollama', 411 | apiKey: args.apiKey, 412 | model: args.model 413 | }); 414 | 415 | const embedding = await tempEmbeddingService.generateEmbeddings(args.text); 416 | const provider = args.provider || 'ollama'; 417 | const model = args.model || (provider === 'ollama' ? 'nomic-embed-text' : 'text-embedding-3-small'); 418 | 419 | // If test is successful, update the server's embedding service 420 | this.embeddingService = tempEmbeddingService; 421 | 422 | // Reinitialize collection with new vector size 423 | await this.initCollection(); 424 | 425 | return { 426 | content: [ 427 | { 428 | type: 'text', 429 | text: `Successfully configured ${provider} embeddings (${model}).\nVector size: ${embedding.length}\nQdrant collection updated to match new vector size.`, 430 | }, 431 | ], 432 | }; 433 | } catch (error) { 434 | return { 435 | content: [ 436 | { 437 | type: 'text', 438 | text: `Failed to test embeddings: ${error}`, 439 | }, 440 | ], 441 | isError: true, 442 | }; 443 | } 444 | } 445 | 446 | private async handleAddDocumentation(args: any) { 447 | if (!args.url || typeof args.url !== 'string') { 448 | throw new McpError(ErrorCode.InvalidParams, 'URL is required'); 449 | } 450 | 451 | try { 452 | const chunks = await this.fetchAndProcessUrl(args.url); 453 | 454 | for (const chunk of chunks) { 455 | const embedding = await this.getEmbeddings(chunk.text); 456 | const payload = { 457 | ...chunk, 458 | _type: 'DocumentChunk' as const, 459 | }; 460 | 461 | await this.qdrantClient.upsert(COLLECTION_NAME, { 462 | wait: true, 463 | points: [ 464 | { 465 | id: this.generatePointId(), 466 | vector: embedding, 467 | payload: payload as Record, 468 | }, 469 | ], 470 | }); 471 | } 472 | 473 | return { 474 | content: [ 475 | { 476 | type: 'text', 477 | text: `Successfully added documentation from ${args.url} (${chunks.length} chunks processed)`, 478 | }, 479 | ], 480 | }; 481 | } catch (error) { 482 | return { 483 | content: [ 484 | { 485 | type: 'text', 486 | text: `Failed to add documentation: ${error}`, 487 | }, 488 | ], 489 | isError: true, 490 | }; 491 | } 492 | } 493 | 494 | private async handleSearchDocumentation(args: any) { 495 | if (!args.query || typeof args.query !== 'string') { 496 | throw new McpError(ErrorCode.InvalidParams, 'Query is required'); 497 | } 498 | 499 | const limit = args.limit || 5; 500 | 501 | try { 502 | const queryEmbedding = await this.getEmbeddings(args.query); 503 | 504 | const searchResults = await this.qdrantClient.search(COLLECTION_NAME, { 505 | vector: queryEmbedding, 506 | limit, 507 | with_payload: true, 508 | }); 509 | 510 | const formattedResults = searchResults.map(result => { 511 | if (!isDocumentPayload(result.payload)) { 512 | throw new Error('Invalid payload type'); 513 | } 514 | return `[${result.payload.title}](${result.payload.url})\nScore: ${result.score}\nContent: ${result.payload.text}\n`; 515 | }).join('\n---\n'); 516 | 517 | return { 518 | content: [ 519 | { 520 | type: 'text', 521 | text: formattedResults || 'No results found.', 522 | }, 523 | ], 524 | }; 525 | } catch (error) { 526 | return { 527 | content: [ 528 | { 529 | type: 'text', 530 | text: `Search failed: ${error}`, 531 | }, 532 | ], 533 | isError: true, 534 | }; 535 | } 536 | } 537 | 538 | private async handleListSources() { 539 | try { 540 | const scroll = await this.qdrantClient.scroll(COLLECTION_NAME, { 541 | with_payload: true, 542 | }); 543 | 544 | const sources = new Set(); 545 | for (const point of scroll.points) { 546 | if (isDocumentPayload(point.payload)) { 547 | sources.add(`${point.payload.title} (${point.payload.url})`); 548 | } 549 | } 550 | 551 | return { 552 | content: [ 553 | { 554 | type: 'text', 555 | text: Array.from(sources).join('\n') || 'No documentation sources found.', 556 | }, 557 | ], 558 | }; 559 | } catch (error) { 560 | return { 561 | content: [ 562 | { 563 | type: 'text', 564 | text: `Failed to list sources: ${error}`, 565 | }, 566 | ], 567 | isError: true, 568 | }; 569 | } 570 | } 571 | 572 | async run() { 573 | try { 574 | await this.init(); 575 | const transport = new StdioServerTransport(); 576 | await this.server.connect(transport); 577 | console.error('RAG Docs MCP server running on stdio'); 578 | } catch (error) { 579 | console.error('Failed to initialize server:', error); 580 | process.exit(1); 581 | } 582 | } 583 | } 584 | 585 | const server = new RagDocsServer(); 586 | server.run().catch(console.error); 587 | -------------------------------------------------------------------------------- /src/types/ollama.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'ollama' { 2 | export interface EmbeddingsRequest { 3 | model: string; 4 | prompt: string; 5 | options?: Record; 6 | } 7 | 8 | export interface EmbeddingsResponse { 9 | embedding: number[]; 10 | } 11 | 12 | const ollama: { 13 | embeddings(request: EmbeddingsRequest): Promise; 14 | }; 15 | 16 | export default ollama; 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "moduleResolution": "node", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "typeRoots": [ 13 | "./node_modules/@types", 14 | "./src/types" 15 | ] 16 | }, 17 | "include": [ 18 | "src/**/*" 19 | ], 20 | "exclude": [ 21 | "node_modules", 22 | "build" 23 | ] 24 | } 25 | --------------------------------------------------------------------------------