#
tokens: 30635/50000 30/31 files (page 1/2)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 2. Use http://codebase.md/devlimelabs/meilisearch-ts-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .env.example
├── .eslintrc.json
├── .gitignore
├── .node-loader.mjs
├── CONTRIBUTING.md
├── directory-structure.md
├── docker-compose.yml
├── Dockerfile
├── examples
│   └── movies-demo.js
├── implementation-plan.md
├── jest.config.js
├── LICENSE
├── meilisearch.open-api.json
├── package-lock.json
├── package.json
├── README.md
├── scripts
│   ├── claude-desktop-setup.js
│   └── setup-dev.sh
├── smithery.yaml
├── src
│   ├── __tests__
│   │   └── api-client.test.ts
│   ├── config.ts
│   ├── index.ts
│   ├── tools
│   │   ├── document-tools.ts
│   │   ├── index-tools.ts
│   │   ├── search-tools.ts
│   │   ├── settings-tools.ts
│   │   ├── system-tools.ts
│   │   ├── task-tools.ts
│   │   └── vector-tools.ts
│   ├── types
│   │   └── global.d.ts
│   └── utils
│       ├── api-client.ts
│       └── error-handler.ts
├── tsconfig.json
└── vector-search-guide.md
```

# Files

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
 1 | # Meilisearch MCP Server Environment Variables
 2 | 
 3 | # URL of the Meilisearch instance
 4 | MEILISEARCH_HOST=http://localhost:7700
 5 | # API key for authenticating with Meilisearch
 6 | # Leave empty if no key is required for your Meilisearch instance
 7 | MEILISEARCH_API_KEY=
 8 | 
 9 | # Timeout for API requests in milliseconds
10 | MEILISEARCH_TIMEOUT=5000 
11 | 
```

--------------------------------------------------------------------------------
/.node-loader.mjs:
--------------------------------------------------------------------------------

```
 1 | export const resolve = (specifier, context, nextResolve) => {
 2 |   return nextResolve(specifier, context);
 3 | };
 4 | 
 5 | export const load = (url, context, nextLoad) => {
 6 |   return nextLoad(url, context);
 7 | };
 8 | 
 9 | export const getFormat = (url, context, defaultGetFormat) => {
10 |   if (url.endsWith('.ts')) {
11 |     return { format: 'module' };
12 |   }
13 |   return defaultGetFormat(url, context);
14 | };
15 | 
```

--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "env": {
 3 |     "es2022": true,
 4 |     "node": true
 5 |   },
 6 |   "extends": [
 7 |     "eslint:recommended",
 8 |     "plugin:@typescript-eslint/recommended"
 9 |   ],
10 |   "parser": "@typescript-eslint/parser",
11 |   "parserOptions": {
12 |     "ecmaVersion": "latest",
13 |     "sourceType": "module"
14 |   },
15 |   "plugins": [
16 |     "@typescript-eslint"
17 |   ],
18 |   "rules": {
19 |     "indent": ["error", 2],
20 |     "linebreak-style": ["error", "unix"],
21 |     "quotes": ["error", "single", { "avoidEscape": true }],
22 |     "semi": ["error", "always"],
23 |     "@typescript-eslint/no-explicit-any": "warn",
24 |     "@typescript-eslint/explicit-function-return-type": "off",
25 |     "@typescript-eslint/explicit-module-boundary-types": "off"
26 |   }
27 | } 
28 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
  1 | # Logs
  2 | logs
  3 | *.log
  4 | npm-debug.log*
  5 | yarn-debug.log*
  6 | yarn-error.log*
  7 | lerna-debug.log*
  8 | .pnpm-debug.log*
  9 | 
 10 | # Diagnostic reports (https://nodejs.org/api/report.html)
 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
 12 | 
 13 | # Runtime data
 14 | pids
 15 | *.pid
 16 | *.seed
 17 | *.pid.lock
 18 | 
 19 | # Directory for instrumented libs generated by jscoverage/JSCover
 20 | lib-cov
 21 | 
 22 | # Coverage directory used by tools like istanbul
 23 | coverage
 24 | *.lcov
 25 | 
 26 | # nyc test coverage
 27 | .nyc_output
 28 | 
 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
 30 | .grunt
 31 | 
 32 | # Bower dependency directory (https://bower.io/)
 33 | bower_components
 34 | 
 35 | # node-waf configuration
 36 | .lock-wscript
 37 | 
 38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
 39 | build/Release
 40 | 
 41 | # Dependency directories
 42 | node_modules/
 43 | jspm_packages/
 44 | 
 45 | # Snowpack dependency directory (https://snowpack.dev/)
 46 | web_modules/
 47 | 
 48 | # TypeScript cache
 49 | *.tsbuildinfo
 50 | 
 51 | # Optional npm cache directory
 52 | .npm
 53 | 
 54 | # Optional eslint cache
 55 | .eslintcache
 56 | 
 57 | # Optional stylelint cache
 58 | .stylelintcache
 59 | 
 60 | # Microbundle cache
 61 | .rpt2_cache/
 62 | .rts2_cache_cjs/
 63 | .rts2_cache_es/
 64 | .rts2_cache_umd/
 65 | 
 66 | # Optional REPL history
 67 | .node_repl_history
 68 | 
 69 | # Output of 'npm pack'
 70 | *.tgz
 71 | 
 72 | # Yarn Integrity file
 73 | .yarn-integrity
 74 | 
 75 | # dotenv environment variable files
 76 | .env
 77 | .env.development.local
 78 | .env.test.local
 79 | .env.production.local
 80 | .env.local
 81 | 
 82 | # parcel-bundler cache (https://parceljs.org/)
 83 | .cache
 84 | .parcel-cache
 85 | 
 86 | # Next.js build output
 87 | .next
 88 | out
 89 | 
 90 | # Nuxt.js build / generate output
 91 | .nuxt
 92 | dist
 93 | 
 94 | # Gatsby files
 95 | .cache/
 96 | # Comment in the public line in if your project uses Gatsby and not Next.js
 97 | # https://nextjs.org/blog/next-9-1#public-directory-support
 98 | # public
 99 | 
100 | # vuepress build output
101 | .vuepress/dist
102 | 
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 | 
107 | # Docusaurus cache and generated files
108 | .docusaurus
109 | 
110 | # Serverless directories
111 | .serverless/
112 | 
113 | # FuseBox cache
114 | .fusebox/
115 | 
116 | # DynamoDB Local files
117 | .dynamodb/
118 | 
119 | # TernJS port file
120 | .tern-port
121 | 
122 | # Stores VSCode versions used for testing VSCode extensions
123 | .vscode-test
124 | 
125 | # yarn v2
126 | .yarn/cache
127 | .yarn/unplugged
128 | .yarn/build-state.yml
129 | .yarn/install-state.gz
130 | .pnp.*
131 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Meilisearch MCP Server
  2 | 
  3 | [![smithery badge](https://smithery.ai/badge/@devlimelabs/meilisearch-ts-mcp)](https://smithery.ai/server/@devlimelabs/meilisearch-ts-mcp)
  4 | 
  5 | A Model Context Protocol (MCP) server implementation for Meilisearch, enabling AI assistants to interact with Meilisearch through a standardized interface.
  6 | 
  7 | ## Features
  8 | 
  9 | - **Index Management**: Create, update, and delete indexes
 10 | - **Document Management**: Add, update, and delete documents
 11 | - **Search Capabilities**: Perform searches with various parameters and filters
 12 | - **Settings Management**: Configure index settings
 13 | - **Task Management**: Monitor and manage asynchronous tasks
 14 | - **System Operations**: Health checks, version information, and statistics
 15 | - **Vector Search**: Experimental vector search capabilities
 16 | 
 17 | ## Installation
 18 | 
 19 | ### Installing via Smithery
 20 | 
 21 | To install Meilisearch MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@devlimelabs/meilisearch-ts-mcp):
 22 | 
 23 | ```bash
 24 | npx -y @smithery/cli install @devlimelabs/meilisearch-ts-mcp --client claude
 25 | ```
 26 | 
 27 | ### Manual Installation
 28 | 1. Clone the repository:
 29 |    ```bash
 30 |    git clone https://github.com/devlimelabs/meilisearch-ts-mcp.git
 31 |    cd meilisearch-ts-mcp
 32 |    ```
 33 | 
 34 | 2. Install dependencies:
 35 |    ```bash
 36 |    npm install
 37 |    ```
 38 | 
 39 | 3. Create a `.env` file based on the example:
 40 |    ```bash
 41 |    cp .env.example .env
 42 |    ```
 43 |    
 44 | 4. Edit the `.env` file to configure your Meilisearch connection.
 45 | 
 46 | ## Docker Setup
 47 | 
 48 | The Meilisearch MCP Server can be run in a Docker container for easier deployment and isolation.
 49 | 
 50 | ### Using Docker Compose
 51 | 
 52 | The easiest way to get started with Docker is to use Docker Compose:
 53 | 
 54 | ```bash
 55 | # Start the Meilisearch MCP Server
 56 | docker-compose up -d
 57 | 
 58 | # View logs
 59 | docker-compose logs -f
 60 | 
 61 | # Stop the server
 62 | docker-compose down
 63 | ```
 64 | 
 65 | ### Building and Running the Docker Image Manually
 66 | 
 67 | You can also build and run the Docker image manually:
 68 | 
 69 | ```bash
 70 | # Build the Docker image
 71 | docker build -t meilisearch-ts-mcp .
 72 | 
 73 | # Run the container
 74 | docker run -p 3000:3000 --env-file .env meilisearch-ts-mcp
 75 | ```
 76 | 
 77 | ## Development Setup
 78 | 
 79 | For developers who want to contribute to the Meilisearch MCP Server, we provide a convenient setup script:
 80 | 
 81 | ```bash
 82 | # Clone the repository
 83 | git clone https://github.com/devlimelabs-ts-mcp/meilisearch-ts-mcp.git
 84 | cd meilisearch-ts-mcp
 85 | 
 86 | # Run the development setup script
 87 | ./scripts/setup-dev.sh
 88 | ```
 89 | 
 90 | The setup script will:
 91 | 1. Create a `.env` file from `.env.example` if it doesn't exist
 92 | 2. Install dependencies
 93 | 3. Build the project
 94 | 4. Run tests to ensure everything is working correctly
 95 | 
 96 | After running the setup script, you can start the server in development mode:
 97 | 
 98 | ```bash
 99 | npm run dev
100 | ```
101 | 
102 | ## Usage
103 | 
104 | ### Building the Project
105 | 
106 | ```bash
107 | npm run build
108 | ```
109 | 
110 | ### Running the Server
111 | 
112 | ```bash
113 | npm start
114 | ```
115 | 
116 | ### Development Mode
117 | 
118 | ```bash
119 | npm run dev
120 | ```
121 | 
122 | ## Claude Desktop Integration
123 | 
124 | The Meilisearch MCP Server can be integrated with Claude for Desktop, allowing you to interact with your Meilisearch instance directly through Claude.
125 | 
126 | ### Automated Setup
127 | 
128 | We provide a setup script that automatically configures Claude for Desktop to work with the Meilisearch MCP Server:
129 | 
130 | ```bash
131 | # First build the project
132 | npm run build
133 | 
134 | # Then run the setup script
135 | node scripts/claude-desktop-setup.js
136 | ```
137 | 
138 | The script will:
139 | 1. Detect your operating system and locate the Claude for Desktop configuration file
140 | 2. Read your Meilisearch configuration from the `.env` file
141 | 3. Generate the necessary configuration for Claude for Desktop
142 | 4. Provide instructions for updating your Claude for Desktop configuration
143 | 
144 | ### Manual Setup
145 | 
146 | If you prefer to manually configure Claude for Desktop:
147 | 
148 | 1. Locate your Claude for Desktop configuration file:
149 |    - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
150 |    - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
151 |    - **Linux**: `~/.config/Claude/claude_desktop_config.json`
152 | 
153 | 2. Add the following configuration (adjust paths as needed):
154 | 
155 | ```json
156 | {
157 |   "mcpServers": {
158 |     "meilisearch": {
159 |       "command": "node",
160 |       "args": ["/path/to/meilisearch-ts-mcp/dist/index.js"],
161 |       "env": {
162 |         "MEILISEARCH_HOST": "http://localhost:7700",
163 |         "MEILISEARCH_API_KEY": "your-api-key"
164 |       }
165 |     }
166 |   }
167 | }
168 | ```
169 | 
170 | 3. Restart Claude for Desktop to apply the changes.
171 | 
172 | 4. In Claude, type: "I want to use the Meilisearch MCP server" to activate the integration.
173 | 
174 | ## Cursor Integration
175 | 
176 | The Meilisearch MCP Server can also be integrated with [Cursor](https://cursor.com), an AI-powered code editor.
177 | 
178 | ### Setting Up MCP in Cursor
179 | 
180 | 1. Install and set up the Meilisearch MCP Server:
181 |    ```bash
182 |    git clone https://github.com/devlimelabs/meilisearch-ts-mcp.git
183 |    cd meilisearch-ts-mcp
184 |    npm install
185 |    npm run build
186 |    ```
187 | 
188 | 2. Start the MCP server:
189 |    ```bash
190 |    npm start
191 |    ```
192 | 
193 | 3. In Cursor, open the Command Palette (Cmd/Ctrl+Shift+P) and search for "MCP: Connect to MCP Server".
194 | 
195 | 4. Select "Connect to a local MCP server" and enter the following details:
196 |    - **Name**: Meilisearch
197 |    - **Command**: node
198 |    - **Arguments**: /absolute/path/to/meilisearch-ts-mcp/dist/index.js
199 |    - **Environment Variables**: 
200 |      ```
201 |      MEILISEARCH_HOST=http://localhost:7700
202 |      MEILISEARCH_API_KEY=your-api-key
203 |      ```
204 | 
205 | 5. Click "Connect" to establish the connection.
206 | 
207 | 6. You can now interact with your Meilisearch instance through Cursor by typing commands like "Search my Meilisearch index for documents about..."
208 | 
209 | ## Available Tools
210 | 
211 | The Meilisearch MCP Server provides the following tools:
212 | 
213 | ### Index Tools
214 | - `create-index`: Create a new index
215 | - `get-index`: Get information about an index
216 | - `list-indexes`: List all indexes
217 | - `update-index`: Update an index
218 | - `delete-index`: Delete an index
219 | 
220 | ### Document Tools
221 | - `add-documents`: Add documents to an index
222 | - `get-document`: Get a document by ID
223 | - `get-documents`: Get multiple documents
224 | - `update-documents`: Update documents
225 | - `delete-document`: Delete a document by ID
226 | - `delete-documents`: Delete multiple documents
227 | - `delete-all-documents`: Delete all documents in an index
228 | 
229 | ### Search Tools
230 | - `search`: Search for documents
231 | - `multi-search`: Perform multiple searches in a single request
232 | 
233 | ### Settings Tools
234 | - `get-settings`: Get index settings
235 | - `update-settings`: Update index settings
236 | - `reset-settings`: Reset index settings to default
237 | - Various specific settings tools (synonyms, stop words, ranking rules, etc.)
238 | 
239 | ### Task Tools
240 | - `list-tasks`: List tasks with optional filtering
241 | - `get-task`: Get information about a specific task
242 | - `cancel-tasks`: Cancel tasks based on provided filters
243 | - `wait-for-task`: Wait for a specific task to complete
244 | 
245 | ### System Tools
246 | - `health`: Check the health status of the Meilisearch server
247 | - `version`: Get version information
248 | - `info`: Get system information
249 | - `stats`: Get statistics about indexes
250 | 
251 | ### Vector Tools (Experimental)
252 | - `enable-vector-search`: Enable vector search
253 | - `get-experimental-features`: Get experimental features status
254 | - `update-embedders`: Configure embedders
255 | - `get-embedders`: Get embedders configuration
256 | - `reset-embedders`: Reset embedders configuration
257 | - `vector-search`: Perform vector search
258 | 
259 | ## License
260 | 
261 | MIT
262 | 
```

--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Contributing to Meilisearch MCP Server
 2 | 
 3 | Thank you for your interest in contributing to the Meilisearch MCP Server! This document provides guidelines and instructions for contributing.
 4 | 
 5 | ## Code of Conduct
 6 | 
 7 | Please be respectful and considerate of others when contributing to this project. We aim to foster an inclusive and welcoming community.
 8 | 
 9 | ## Getting Started
10 | 
11 | 1. Fork the repository
12 | 2. Clone your fork: `git clone https://github.com/devlimelabs/meilisearch-ts-mcp.git`
13 | 3. Navigate to the project directory: `cd meilisearch-ts-mcp`
14 | 4. Install dependencies: `npm install`
15 | 5. Create a new branch for your feature or bugfix: `git checkout -b feature/your-feature-name`
16 | 
17 | ## Development Workflow
18 | 
19 | 1. Make your changes
20 | 2. Run the linter: `npm run lint`
21 | 3. Run tests: `npm test`
22 | 4. Build the project: `npm run build`
23 | 5. Test your changes with a local Meilisearch instance
24 | 
25 | ## Pull Request Process
26 | 
27 | 1. Ensure your code passes all tests and linting
28 | 2. Update documentation if necessary
29 | 3. Submit a pull request to the `main` branch
30 | 4. Describe your changes in detail in the pull request description
31 | 5. Reference any related issues
32 | 
33 | ## Adding New Tools
34 | 
35 | When adding new tools to the MCP server:
36 | 
37 | 1. Create a new file in the `src/tools` directory if appropriate
38 | 2. Follow the existing pattern for tool registration
39 | 3. Use Zod for parameter validation
40 | 4. Add proper error handling
41 | 5. Update the README.md to document the new tool
42 | 
43 | ## Coding Standards
44 | 
45 | - Use TypeScript for all new code
46 | - Follow the existing code style
47 | - Write meaningful commit messages
48 | - Add comments for complex logic
49 | - Write tests for new functionality
50 | 
51 | ## Testing
52 | 
53 | - Write unit tests for new functionality
54 | - Ensure all tests pass before submitting a pull request
55 | - Test with a real Meilisearch instance when possible
56 | 
57 | ## Documentation
58 | 
59 | - Update the README.md file with any new features or changes
60 | - Document all new tools and parameters
61 | - Provide examples for complex functionality
62 | 
63 | ## License
64 | 
65 | By contributing to this project, you agree that your contributions will be licensed under the project's MIT license. 
66 | 
```

--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------

```javascript
 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */
 2 | export default {
 3 |   preset: 'ts-jest',
 4 |   testEnvironment: 'node',
 5 |   extensionsToTreatAsEsm: ['.ts'],
 6 |   moduleNameMapper: {
 7 |     '^(\\.{1,2}/.*)\\.js$': '$1',
 8 |   },
 9 |   transform: {
10 |     '^.+\\.tsx?$': [
11 |       'ts-jest',
12 |       {
13 |         useESM: true,
14 |       },
15 |     ],
16 |   },
17 | };
18 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "NodeNext",
 5 |     "moduleResolution": "NodeNext",
 6 |     "esModuleInterop": true,
 7 |     "forceConsistentCasingInFileNames": true,
 8 |     "strict": true,
 9 |     "skipLibCheck": true,
10 |     "outDir": "dist",
11 |     "sourceMap": true,
12 |     "declaration": true,
13 |     "resolveJsonModule": true
14 |   },
15 |   "include": ["src/**/*"],
16 |   "exclude": ["node_modules", "dist", "**/*.test.ts"],
17 |   "ts-node": {
18 |     "esm": true,
19 |     "experimentalSpecifierResolution": "node"
20 |   }
21 | }
22 | 
```

--------------------------------------------------------------------------------
/src/types/global.d.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Global type declarations for external modules
 3 |  */
 4 | 
 5 | declare module '@modelcontextprotocol/sdk/server/mcp.js' {
 6 |   export class McpServer {
 7 |     constructor(options?: { name?: string; version?: string });
 8 |     
 9 |     tool(
10 |       name: string,
11 |       description: string,
12 |       parameters: Record<string, any>,
13 |       handler: (args: any, extra: any) => any | Promise<any>
14 |     ): void;
15 |     
16 |     connect(transport: any): Promise<void>;
17 |   }
18 | }
19 | 
20 | declare module '@modelcontextprotocol/sdk/server/stdio.js' {
21 |   export class StdioServerTransport {
22 |     constructor();
23 |   }
24 | } 
25 | 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | FROM node:20-alpine AS builder
 2 | 
 3 | WORKDIR /app
 4 | 
 5 | # Copy package files and install dependencies
 6 | COPY package*.json ./
 7 | RUN npm ci
 8 | 
 9 | # Copy source code
10 | COPY . .
11 | 
12 | # Build the application
13 | RUN npm run build
14 | 
15 | # Production stage
16 | FROM node:20-alpine
17 | 
18 | WORKDIR /app
19 | 
20 | # Copy package files and install production dependencies only
21 | COPY package*.json ./
22 | RUN npm ci --omit=dev
23 | 
24 | # Copy built application from builder stage
25 | COPY --from=builder /app/dist ./dist
26 | 
27 | # Copy .env.example file
28 | COPY .env.example .env.example
29 | 
30 | # Set environment variables
31 | ENV NODE_ENV=production
32 | 
33 | # Expose port if needed (for health checks, etc.)
34 | # EXPOSE 8080
35 | 
36 | # Start the application
37 | CMD ["node", "dist/index.js"] 
38 | 
```

--------------------------------------------------------------------------------
/scripts/setup-dev.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | 
 3 | # Setup Development Environment for Meilisearch MCP Server
 4 | 
 5 | # Create .env file if it doesn't exist
 6 | if [ ! -f .env ]; then
 7 |   echo "Creating .env file from .env.example..."
 8 |   cp .env.example .env
 9 |   echo "Done! Please edit .env file with your Meilisearch configuration."
10 | else
11 |   echo ".env file already exists."
12 | fi
13 | 
14 | # Install dependencies
15 | echo "Installing dependencies..."
16 | npm install
17 | 
18 | # Build the project
19 | echo "Building the project..."
20 | npm run build
21 | 
22 | # Run tests
23 | echo "Running tests..."
24 | if npm test; then
25 |   echo "All tests passed!"
26 | else
27 |   echo "Warning: Some tests failed. You may need to fix them before proceeding."
28 |   echo "You can continue with development, but be aware that some functionality may not work as expected."
29 | fi
30 | 
31 | echo "Development environment setup complete!"
32 | echo "To start the server in development mode, run: npm run dev" 
33 | 
```

--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------

```yaml
 1 | version: '3.8'
 2 | 
 3 | services:
 4 |   meilisearch:
 5 |     image: getmeili/meilisearch:latest
 6 |     container_name: meilisearch
 7 |     environment:
 8 |       - MEILI_MASTER_KEY=${MEILI_MASTER_KEY:-masterKey}
 9 |       - MEILI_NO_ANALYTICS=true
10 |       - MEILI_ENV=development
11 |     ports:
12 |       - '7700:7700'
13 |     volumes:
14 |       - meilisearch_data:/meili_data
15 |     restart: unless-stopped
16 |     networks:
17 |       - meilisearch-network
18 | 
19 |   meilisearch-ts-mcp:
20 |     build:
21 |       context: .
22 |       dockerfile: Dockerfile
23 |     container_name: meilisearch-ts-mcp
24 |     environment:
25 |       - MEILISEARCH_HOST=http://meilisearch:7700
26 |       - MEILISEARCH_API_KEY=${MEILI_MASTER_KEY:-masterKey}
27 |       - MEILISEARCH_TIMEOUT=5000
28 |     depends_on:
29 |       - meilisearch
30 |     networks:
31 |       - meilisearch-network
32 | 
33 | volumes:
34 |   meilisearch_data:
35 |     driver: local
36 | 
37 | networks:
38 |   meilisearch-network:
39 |     driver: bridge
40 | 
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
 2 | 
 3 | startCommand:
 4 |   type: stdio
 5 |   configSchema:
 6 |     # JSON Schema defining the configuration options for the MCP.
 7 |     type: object
 8 |     properties:
 9 |       MEILISEARCH_HOST:
10 |         type: string
11 |         default: http://localhost:7700
12 |         description: URL for the Meilisearch instance
13 |       MEILISEARCH_API_KEY:
14 |         type: string
15 |         default: ""
16 |         description: API key for Meilisearch, if required
17 |   commandFunction:
18 |     # A function that produces the CLI command to start the MCP on stdio.
19 |     |-
20 |     (config) => ({
21 |       command: 'node',
22 |       args: ['dist/index.js'],
23 |       env: {
24 |         NODE_ENV: 'production',
25 |         MEILISEARCH_HOST: config.MEILISEARCH_HOST,
26 |         MEILISEARCH_API_KEY: config.MEILISEARCH_API_KEY
27 |       }
28 |     })
29 |   exampleConfig:
30 |     MEILISEARCH_HOST: http://localhost:7700
31 |     MEILISEARCH_API_KEY: your-api-key
32 | 
```

--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Meilisearch MCP Server Configuration
 3 |  * 
 4 |  * This file contains the configuration settings for connecting to the Meilisearch server.
 5 |  * Configuration is loaded from environment variables with sensible defaults.
 6 |  */
 7 | 
 8 | // Server configuration interface
 9 | export interface ServerConfig {
10 |   /** The URL of the Meilisearch instance */
11 |   host: string;
12 |   /** The API key for authenticating with Meilisearch */
13 |   apiKey: string;
14 |   /** The timeout for API requests in milliseconds */
15 |   timeout: number;
16 | }
17 | 
18 | /**
19 |  * Load and initialize configuration from environment variables
20 |  */
21 | export const loadConfig = (): ServerConfig => {
22 |   return {
23 |     host: process.env.MEILISEARCH_HOST || "http://localhost:7700",
24 |     apiKey: process.env.MEILISEARCH_API_KEY || "",
25 |     timeout: parseInt(process.env.MEILISEARCH_TIMEOUT || "5000", 10),
26 |   };
27 | };
28 | 
29 | // Export the config instance
30 | export const config = loadConfig();
31 | 
32 | // Re-export for direct use
33 | export default config; 
34 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "meilisearch-ts-mcp",
 3 |   "version": "0.1.0",
 4 |   "description": "Meilisearch MCP Server (Typescript) - Model Context Protocol implementation for Meilisearch",
 5 |   "main": "dist/index.js",
 6 |   "type": "module",
 7 |   "scripts": {
 8 |     "build": "tsc",
 9 |     "start": "node dist/index.js",
10 |     "dev": "tsx src/index.ts",
11 |     "lint": "eslint src/**/*.ts",
12 |     "test": "jest"
13 |   },
14 |   "keywords": [
15 |     "meilisearch",
16 |     "mcp",
17 |     "search",
18 |     "model-context-protocol"
19 |   ],
20 |   "author": "",
21 |   "license": "MIT",
22 |   "dependencies": {
23 |     "@modelcontextprotocol/sdk": "^1.6.0",
24 |     "axios": "^1.6.2",
25 |     "dotenv": "^16.3.1",
26 |     "zod": "^3.22.4"
27 |   },
28 |   "devDependencies": {
29 |     "@types/jest": "^29.5.10",
30 |     "@types/node": "^20.10.0",
31 |     "@typescript-eslint/eslint-plugin": "^6.12.0",
32 |     "@typescript-eslint/parser": "^6.12.0",
33 |     "eslint": "^8.54.0",
34 |     "jest": "^29.7.0",
35 |     "ts-jest": "^29.1.1",
36 |     "ts-node": "^10.9.1",
37 |     "tsx": "^4.19.3",
38 |     "typescript": "^5.3.2"
39 |   }
40 | }
41 | 
```

--------------------------------------------------------------------------------
/src/utils/error-handler.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Error handling utilities for Meilisearch API responses
 3 |  */
 4 | 
 5 | 
 6 | /**
 7 |  * Formats Meilisearch API errors for consistent error messaging
 8 |  * 
 9 |  * @param error - The error from the API request
10 |  * @returns A formatted error message
11 |  */
12 | export const handleApiError = (error: any): string => {
13 |   // If it's an Axios error with a response
14 |   if (error.isAxiosError && error.response) {
15 |     const { status, data } = error.response;
16 |     // Return formatted error with status code and response data
17 |     return `Meilisearch API error (${status}): ${JSON.stringify(data)}`;
18 |   }
19 |   
20 |   // If it's a network error or other error
21 |   return `Error connecting to Meilisearch: ${error.message}`;
22 | };
23 | 
24 | /**
25 |  * Creates a standardized error response object for MCP tools
26 |  * 
27 |  * @param error - The error from the API request
28 |  * @returns An MCP tool response object with error flag
29 |  */
30 | export const createErrorResponse = (error: any) => {
31 |   return {
32 |     isError: true,
33 |     content: [{ type: "text", text: handleApiError(error) }],
34 |   };
35 | };
36 | 
37 | export default {
38 |   handleApiError,
39 |   createErrorResponse,
40 | }; 
41 | 
```

--------------------------------------------------------------------------------
/src/utils/api-client.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
 2 | 
 3 | import config from '../config.js';
 4 | 
 5 | /**
 6 |  * Meilisearch API client
 7 |  * 
 8 |  * This module provides a configured Axios instance for making requests to the Meilisearch API.
 9 |  */
10 | 
11 | /**
12 |  * Creates a configured Axios instance for Meilisearch API requests
13 |  * 
14 |  * @returns An Axios instance with base configuration
15 |  */
16 | export const createApiClient = () => {
17 |   const instance = axios.create({
18 |     baseURL: config.host,
19 |     headers: {
20 |       Authorization: `Bearer ${config.apiKey}`,
21 |       'Content-Type': 'application/json',
22 |     },
23 |     timeout: config.timeout,
24 |   });
25 | 
26 |   return {
27 |     get: <T = any>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> => 
28 |       instance.get(url, config),
29 |     post: <T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> => 
30 |       instance.post(url, data, config),
31 |     put: <T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> => 
32 |       instance.put(url, data, config),
33 |     patch: <T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> => 
34 |       instance.patch(url, data, config),
35 |     delete: <T = any>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> => 
36 |       instance.delete(url, config),
37 |   };
38 | };
39 | 
40 | // Create and export a singleton instance of the API client
41 | export const apiClient = createApiClient();
42 | 
43 | // Re-export for direct use
44 | export default apiClient; 
45 | 
```

--------------------------------------------------------------------------------
/src/__tests__/api-client.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * API Client Tests
 3 |  * 
 4 |  * This file contains tests for the API client utility.
 5 |  */
 6 | 
 7 | 
 8 | // Mock the API client
 9 | jest.mock('../utils/api-client', () => {
10 |   const mockGet = jest.fn();
11 |   const mockPost = jest.fn();
12 |   const mockPut = jest.fn();
13 |   const mockPatch = jest.fn();
14 |   const mockDelete = jest.fn();
15 |   
16 |   return {
17 |     createApiClient: jest.fn(() => ({
18 |       get: mockGet,
19 |       post: mockPost,
20 |       put: mockPut,
21 |       patch: mockPatch,
22 |       delete: mockDelete
23 |     })),
24 |     apiClient: {
25 |       get: mockGet,
26 |       post: mockPost,
27 |       put: mockPut,
28 |       patch: mockPatch,
29 |       delete: mockDelete
30 |     },
31 |     __esModule: true,
32 |     default: {
33 |       get: mockGet,
34 |       post: mockPost,
35 |       put: mockPut,
36 |       patch: mockPatch,
37 |       delete: mockDelete
38 |     }
39 |   };
40 | });
41 | 
42 | // Get the mocked functions
43 | const { apiClient } = require('../utils/api-client');
44 | const mockGet = apiClient.get;
45 | const mockPost = apiClient.post;
46 | 
47 | describe('API Client', () => {
48 |   beforeEach(() => {
49 |     jest.clearAllMocks();
50 |   });
51 | 
52 |   it('should make GET requests correctly', async () => {
53 |     // Setup
54 |     mockGet.mockResolvedValueOnce({ data: { result: 'success' } });
55 | 
56 |     // Execute
57 |     await apiClient.get('/test-endpoint');
58 | 
59 |     // Verify
60 |     expect(mockGet).toHaveBeenCalledWith('/test-endpoint');
61 |   });
62 | 
63 |   it('should include configuration when provided', async () => {
64 |     // Setup
65 |     mockGet.mockResolvedValueOnce({ data: { result: 'success' } });
66 |     const config = { params: { filter: 'test' } };
67 | 
68 |     // Execute
69 |     await apiClient.get('/test-endpoint', config);
70 | 
71 |     // Verify
72 |     expect(mockGet).toHaveBeenCalledWith('/test-endpoint', config);
73 |   });
74 | 
75 |   it('should handle errors appropriately', async () => {
76 |     // Setup
77 |     const errorResponse = {
78 |       response: {
79 |         status: 404,
80 |         data: { message: 'Not found' }
81 |       }
82 |     };
83 |     mockGet.mockRejectedValueOnce(errorResponse);
84 | 
85 |     // Execute & Verify
86 |     await expect(apiClient.get('/non-existent')).rejects.toEqual(errorResponse);
87 |   });
88 | }); 
89 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
 2 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
 3 | import axios from 'axios';
 4 | 
 5 | import registerDocumentTools from './tools/document-tools.js';
 6 | import registerIndexTools from './tools/index-tools.js';
 7 | import registerSearchTools from './tools/search-tools.js';
 8 | import registerSettingsTools from './tools/settings-tools.js';
 9 | import registerSystemTools from './tools/system-tools.js';
10 | import registerTaskTools from './tools/task-tools.js';
11 | import registerVectorTools from './tools/vector-tools.js';
12 | 
13 | /**
14 |  * Meilisearch MCP Server
15 |  * 
16 |  * This is the main entry point for the Meilisearch MCP server.
17 |  * It integrates with Meilisearch to provide search capabilities to LLMs via the Model Context Protocol.
18 |  */
19 | 
20 | // Import tool registration functions
21 | // Server configuration
22 | interface ServerConfig {
23 |   host: string;
24 |   apiKey: string;
25 | }
26 | 
27 | // Initialize configuration from environment variables
28 | const config: ServerConfig = {
29 |   host: process.env.MEILISEARCH_HOST || "http://localhost:7700",
30 |   apiKey: process.env.MEILISEARCH_API_KEY || "",
31 | };
32 | 
33 | // Create Axios instance with base configuration
34 | const api = axios.create({
35 |   baseURL: config.host,
36 |   headers: {
37 |     Authorization: `Bearer ${config.apiKey}`,
38 |     "Content-Type": "application/json",
39 |   },
40 | });
41 | 
42 | // Helper function to handle API errors
43 | const handleApiError = (error: any): string => {
44 |   if (error.response) {
45 |     const { status, data } = error.response;
46 |     return `Meilisearch API error (${status}): ${JSON.stringify(data)}`;
47 |   }
48 |   return `Error connecting to Meilisearch: ${error.message}`;
49 | };
50 | 
51 | /**
52 |  * Main function to initialize and start the MCP server
53 |  */
54 | async function main() {
55 |   console.error("Starting Meilisearch MCP Server...");
56 |   
57 |   // Create the MCP server instance
58 |   const server = new McpServer({
59 |     name: "meilisearch",
60 |     version: "1.0.0",
61 |   });
62 |   
63 |   // Register all tools
64 |   registerIndexTools(server);
65 |   registerDocumentTools(server);
66 |   registerSearchTools(server);
67 |   registerSettingsTools(server);
68 |   registerVectorTools(server);
69 |   registerSystemTools(server);
70 |   registerTaskTools(server);
71 |   
72 |   // Connect to the stdio transport
73 |   const transport = new StdioServerTransport();
74 |   await server.connect(transport);
75 |   
76 |   console.error("Meilisearch MCP Server is running on stdio transport");
77 | }
78 | 
79 | // Start the server
80 | main().catch(error => {
81 |   console.error("Fatal error:", error);
82 |   process.exit(1);
83 | });
84 | 
```

--------------------------------------------------------------------------------
/implementation-plan.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Meilisearch MCP Server Implementation Plan
  2 | 
  3 | ## Overview
  4 | This document outlines the implementation plan for the Meilisearch MCP Server, which integrates with Meilisearch to provide search capabilities to LLMs via the Model Context Protocol (MCP).
  5 | 
  6 | ## Project Structure
  7 | 
  8 | ```
  9 | meilisearch-ts-mcp/
 10 | ├── src/
 11 | │   ├── index.ts                 # Main entry point
 12 | │   ├── config.ts                # Server configuration
 13 | │   ├── tools/                   # MCP tools implementation
 14 | │   │   ├── index-tools.ts       # Index management tools
 15 | │   │   ├── document-tools.ts    # Document management tools
 16 | │   │   ├── search-tools.ts      # Search tools
 17 | │   │   ├── settings-tools.ts    # Settings management tools
 18 | │   │   ├── task-tools.ts        # Task management tools
 19 | │   │   ├── vector-tools.ts      # Vector search tools 
 20 | │   │   └── system-tools.ts      # System tools (health, stats, etc.)
 21 | │   └── utils/                   # Utility functions
 22 | │       ├── api-client.ts        # Meilisearch API client
 23 | │       └── error-handler.ts     # Error handling utilities
 24 | ```
 25 | 
 26 | ## Implementation Phases
 27 | 
 28 | ### Phase 1: Project Setup and Refactoring
 29 | 1. Create the directory structure
 30 | 2. Extract configuration into config.ts
 31 | 3. Create utility modules for API client and error handling
 32 | 4. Refactor the existing monolithic implementation into separate modules
 33 | 
 34 | ### Phase 2: Core Functionality Implementation
 35 | 1. **Index Management Tools**
 36 |    - List indexes
 37 |    - Get index information
 38 |    - Create index
 39 |    - Update index
 40 |    - Delete index
 41 |    - Get index stats
 42 | 
 43 | 2. **Document Management Tools**
 44 |    - Add/update documents
 45 |    - Get documents
 46 |    - Delete documents
 47 |    - Batch document operations
 48 | 
 49 | 3. **Search Tools**
 50 |    - Basic search
 51 |    - Search with filters
 52 |    - Search with sorting
 53 |    - Faceted search
 54 | 
 55 | ### Phase 3: Advanced Functionality
 56 | 1. **Settings Management Tools**
 57 |    - Get index settings
 58 |    - Update settings
 59 |    - Reset settings
 60 |    - Configure specific settings (synonyms, stop words, etc.)
 61 | 
 62 | 2. **Task Management Tools**
 63 |    - Get tasks
 64 |    - Get task by ID
 65 |    - Cancel tasks
 66 | 
 67 | 3. **System Tools**
 68 |    - Health check
 69 |    - Version information
 70 |    - Stats
 71 | 
 72 | 4. **Vector Search Tools**
 73 |    - Configure embedders
 74 |    - Perform vector search
 75 |    - Hybrid search
 76 |    - Vector search with filters
 77 | 
 78 | ## Testing and Documentation
 79 | 1. Create unit tests for each module
 80 | 2. Add integration tests for end-to-end functionality
 81 | 3. Update README with detailed usage instructions
 82 | 4. Create example scripts and configurations
 83 | 
 84 | ## Timeline
 85 | - Phase 1: 1 day
 86 | - Phase 2: 2-3 days
 87 | - Phase 3: 2-3 days
 88 | - Testing and Documentation: 1-2 days
 89 | 
 90 | Total estimated time: 6-9 days
 91 | 
 92 | ## Dependencies
 93 | - @modelcontextprotocol/sdk: For MCP server implementation
 94 | - axios: For HTTP requests to Meilisearch API
 95 | - zod: For parameter validation
 96 | 
 97 | ## Notes
 98 | - Vector search functionality requires Meilisearch to have vector search enabled as an experimental feature
 99 | - The implementation will be backward compatible with existing clients
100 | - All tools will include detailed error handling and descriptive responses 
101 | 
```

--------------------------------------------------------------------------------
/scripts/claude-desktop-setup.js:
--------------------------------------------------------------------------------

```javascript
  1 | import fs from 'fs';
  2 | import os from 'os';
  3 | import path from 'path';
  4 | import { fileURLToPath } from 'url';
  5 | 
  6 | /**
  7 |  * Claude for Desktop Setup Helper
  8 |  *
  9 |  * This script helps users set up the Meilisearch MCP server for use with Claude for Desktop.
 10 |  * It generates the necessary configuration and provides instructions.
 11 |  */
 12 | 
 13 | // Get the directory name
 14 | const __filename = fileURLToPath(import.meta.url);
 15 | const __dirname = path.dirname(__filename);
 16 | const projectRoot = path.resolve(__dirname, '..');
 17 | 
 18 | // Get the absolute path to the built index.js file
 19 | const indexPath = path.resolve(projectRoot, 'dist', 'index.js');
 20 | 
 21 | // Get user's home directory
 22 | const homeDir = os.homedir();
 23 | 
 24 | // Default Claude for Desktop config path based on OS
 25 | let claudeConfigPath;
 26 | if (process.platform === 'darwin') {
 27 |   claudeConfigPath = path.join(
 28 |     homeDir,
 29 |     'Library',
 30 |     'Application Support',
 31 |     'Claude',
 32 |     'claude_desktop_config.json'
 33 |   );
 34 | } else if (process.platform === 'win32') {
 35 |   claudeConfigPath = path.join(
 36 |     homeDir,
 37 |     'AppData',
 38 |     'Roaming',
 39 |     'Claude',
 40 |     'claude_desktop_config.json'
 41 |   );
 42 | } else if (process.platform === 'linux') {
 43 |   claudeConfigPath = path.join(
 44 |     homeDir,
 45 |     '.config',
 46 |     'Claude',
 47 |     'claude_desktop_config.json'
 48 |   );
 49 | } else {
 50 |   console.error(
 51 |     'Unsupported platform. Please manually configure Claude for Desktop.'
 52 |   );
 53 |   process.exit(1);
 54 | }
 55 | 
 56 | // Read environment variables from .env file
 57 | let meilisearchHost = process.env.MEILISEARCH_HOST;
 58 | let meilisearchApiKey = process.env.MEILISEARCH_API_KEY;
 59 | 
 60 | try {
 61 |   const envPath = path.resolve(projectRoot, '.env');
 62 |   if (fs.existsSync(envPath)) {
 63 |     const envContent = fs.readFileSync(envPath, 'utf8');
 64 |     const envLines = envContent.split('\n');
 65 | 
 66 |     for (const line of envLines) {
 67 |       if (line.trim() && !line.startsWith('#')) {
 68 |         const [key, value] = line.split('=');
 69 |         if (key === 'MEILISEARCH_HOST' && value) {
 70 |           meilisearchHost = value.trim();
 71 |         } else if (key === 'MEILISEARCH_API_KEY' && value) {
 72 |           meilisearchApiKey = value.trim();
 73 |         }
 74 |       }
 75 |     }
 76 |   }
 77 | } catch (error) {
 78 |   console.warn('Could not read .env file:', error.message);
 79 | }
 80 | 
 81 | // Generate Claude for Desktop configuration
 82 | const claudeConfig = {
 83 |   mcpServers: {
 84 |     meilisearch: {
 85 |       command: 'node',
 86 |       args: [indexPath],
 87 |       env: {
 88 |         MEILISEARCH_HOST: meilisearchHost,
 89 |         MEILISEARCH_API_KEY: meilisearchApiKey,
 90 |       },
 91 |     },
 92 |   },
 93 | };
 94 | 
 95 | // Check if Claude config file exists
 96 | let existingConfig = {};
 97 | try {
 98 |   if (fs.existsSync(claudeConfigPath)) {
 99 |     const configContent = fs.readFileSync(claudeConfigPath, 'utf8');
100 |     existingConfig = JSON.parse(configContent);
101 |     console.log('Found existing Claude for Desktop configuration.');
102 |   }
103 | } catch (error) {
104 |   console.warn(
105 |     'Could not read existing Claude for Desktop configuration:',
106 |     error.message
107 |   );
108 | }
109 | 
110 | // Merge configurations
111 | const mergedConfig = {
112 |   ...existingConfig,
113 |   mcpServers: {
114 |     ...(existingConfig.mcpServers || {}),
115 |     ...claudeConfig.mcpServers,
116 |   },
117 | };
118 | 
119 | // Output the configuration
120 | console.log('\n=== Claude for Desktop Configuration ===\n');
121 | console.log(JSON.stringify(mergedConfig, null, 2));
122 | console.log('\n');
123 | 
124 | // Ask if user wants to update the configuration
125 | console.log(
126 |   'To use this configuration with Claude for Desktop, you can either:'
127 | );
128 | console.log(
129 |   `1. Manually update your configuration file at: ${claudeConfigPath}`
130 | );
131 | console.log('2. Run the following command to automatically update it:');
132 | console.log(
133 |   `\n   node -e "require('fs').writeFileSync('${claudeConfigPath.replace(
134 |     /\\/g,
135 |     '\\\\'
136 |   )}', JSON.stringify(${JSON.stringify(mergedConfig)}, null, 2))"\n`
137 | );
138 | console.log(
139 |   'After updating the configuration, restart Claude for Desktop to apply the changes.'
140 | );
141 | console.log(
142 |   '\nYou can then use the Meilisearch MCP server with Claude by typing: "I want to use the Meilisearch MCP server."'
143 | );
144 | 
```

--------------------------------------------------------------------------------
/vector-search-guide.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Vector Search with Meilisearch MCP
  2 | 
  3 | This guide explains how to use the vector search capabilities in the Meilisearch MCP server. Vector search allows for semantic similarity matching, enabling more sophisticated search experiences.
  4 | 
  5 | ## Overview
  6 | 
  7 | Vector search in Meilisearch enables:
  8 | - Semantic search based on the meaning of content
  9 | - Similar document recommendations
 10 | - Hybrid search combining keyword and semantic results
 11 | - Multi-modal search experiences
 12 | 
 13 | ## Enabling Vector Search
 14 | 
 15 | Vector search is an experimental feature in Meilisearch. Before using it, you must enable it:
 16 | 
 17 | ```
 18 | # Enable vector search experimental feature
 19 | enable-vector-search
 20 | ```
 21 | 
 22 | ## Setting Up Vector Search
 23 | 
 24 | ### 1. Configure Embedders
 25 | 
 26 | First, configure an embedder for your index:
 27 | 
 28 | ```
 29 | # Example: Configure OpenAI embedder
 30 | update-embedders {
 31 |   "indexUid": "my-index",
 32 |   "embedders": {
 33 |     "openai-embedder": {
 34 |       "source": "openAi",
 35 |       "model": "text-embedding-3-small",
 36 |       "dimensions": 1536
 37 |     }
 38 |   }
 39 | }
 40 | ```
 41 | 
 42 | Common embedder sources include:
 43 | - `openAi` - OpenAI embeddings
 44 | - `huggingFace` - HuggingFace models
 45 | - `ollama` - Ollama local models
 46 | - `rest` - Custom REST API endpoint
 47 | - `userProvided` - Pre-computed embeddings
 48 | 
 49 | ### 2. Add Documents with Vectors
 50 | 
 51 | You can add documents with pre-computed vectors:
 52 | 
 53 | ```
 54 | # Add documents with vector embeddings
 55 | add-documents {
 56 |   "indexUid": "my-index",
 57 |   "documents": [
 58 |     {
 59 |       "id": "1",
 60 |       "title": "Vector search guide",
 61 |       "content": "This is about vector search...",
 62 |       "_vectors": {
 63 |         "openai-embedder": [0.123, 0.456, ...]
 64 |       }
 65 |     }
 66 |   ]
 67 | }
 68 | ```
 69 | 
 70 | Alternatively, if you've configured an embedder, Meilisearch can generate the embeddings automatically from your text fields.
 71 | 
 72 | ## Performing Vector Searches
 73 | 
 74 | ### Basic Vector Search
 75 | 
 76 | If you have a vector representation of your query:
 77 | 
 78 | ```
 79 | # Vector search
 80 | search {
 81 |   "indexUid": "my-index",
 82 |   "vector": [0.123, 0.456, ...],
 83 |   "limit": 10
 84 | }
 85 | ```
 86 | 
 87 | ### Hybrid Search
 88 | 
 89 | Combine traditional keyword search with vector search:
 90 | 
 91 | ```
 92 | # Hybrid search
 93 | search {
 94 |   "indexUid": "my-index",
 95 |   "q": "machine learning techniques",
 96 |   "vector": [0.123, 0.456, ...],
 97 |   "hybridEmbedder": "openai-embedder",
 98 |   "hybridSemanticRatio": 0.7
 99 | }
100 | ```
101 | 
102 | The `hybridSemanticRatio` controls the balance between semantic (vector) and lexical (keyword) search:
103 | - 0.0: Only keyword search
104 | - 1.0: Only vector search
105 | - 0.5: Equal weight to both
106 | 
107 | ### Finding Similar Documents
108 | 
109 | Find documents similar to an existing document:
110 | 
111 | ```
112 | # Similar documents search
113 | similar-documents {
114 |   "indexUid": "my-index",
115 |   "id": "doc123",
116 |   "embedder": "openai-embedder",
117 |   "limit": 5
118 | }
119 | ```
120 | 
121 | ## Multi-Index Vector Search
122 | 
123 | Perform vector searches across multiple indexes:
124 | 
125 | ```
126 | # Multi-index vector search
127 | multi-search {
128 |   "queries": [
129 |     {
130 |       "indexUid": "products", 
131 |       "vector": [0.1, 0.2, ...],
132 |       "hybridEmbedder": "openai-embedder",
133 |       "limit": 5
134 |     },
135 |     {
136 |       "indexUid": "articles",
137 |       "vector": [0.1, 0.2, ...],
138 |       "hybridEmbedder": "openai-embedder",
139 |       "limit": 5
140 |     }
141 |   ],
142 |   "federation": {
143 |     "limit": 10
144 |   }
145 | }
146 | ```
147 | 
148 | ## Best Practices
149 | 
150 | 1. **Choose the right embedder**: Different models have different strengths and capabilities.
151 | 
152 | 2. **Experiment with hybrid ratios**: The ideal balance between vector and keyword search depends on your content and use case.
153 | 
154 | 3. **Pre-compute embeddings** when possible to improve indexing performance.
155 | 
156 | 4. **Use filters** with vector search to constrain results to relevant subsets.
157 | 
158 | 5. **Consider reranking** for critical applications to improve result quality.
159 | 
160 | ## Potential Use Cases
161 | 
162 | - **Semantic code search**: Find code examples by describing functionality
163 | - **Similar product recommendations**: "Show me products like this one"
164 | - **Research document similarity**: Find related academic papers or reports
165 | - **Natural language queries**: Search for concepts rather than exact keywords
166 | - **Content discovery**: Find content with similar themes or topics
167 | 
168 | ## Limitations
169 | 
170 | - Vector search is an experimental feature and may change in future Meilisearch releases
171 | - Vector search performs best with larger datasets where semantic similarity matters
172 | - Compute requirements increase with vector dimensions and dataset size
173 | 
```

--------------------------------------------------------------------------------
/examples/movies-demo.js:
--------------------------------------------------------------------------------

```javascript
  1 | import axios from 'axios';
  2 | import path from 'path';
  3 | import { fileURLToPath } from 'url';
  4 | 
  5 | /**
  6 |  * Meilisearch MCP Server - Movies Demo
  7 |  *
  8 |  * This script demonstrates how to use the Meilisearch MCP server with a sample movie dataset.
  9 |  * It creates an index, adds documents, configures settings, and performs searches.
 10 |  */
 11 | 
 12 | // Get the directory name
 13 | const __filename = fileURLToPath(import.meta.url);
 14 | const __dirname = path.dirname(__filename);
 15 | 
 16 | // Configuration
 17 | const MEILISEARCH_HOST =
 18 |   process.env.MEILISEARCH_HOST || 'http://localhost:7700';
 19 | const MEILISEARCH_API_KEY = process.env.MEILISEARCH_API_KEY || '';
 20 | 
 21 | // Create an axios instance for Meilisearch
 22 | const meilisearch = axios.create({
 23 |   baseURL: MEILISEARCH_HOST,
 24 |   headers: MEILISEARCH_API_KEY
 25 |     ? { Authorization: `Bearer ${MEILISEARCH_API_KEY}` }
 26 |     : {},
 27 |   timeout: 5000,
 28 | });
 29 | 
 30 | // Sample movie data
 31 | const movies = [
 32 |   {
 33 |     id: 1,
 34 |     title: 'The Shawshank Redemption',
 35 |     director: 'Frank Darabont',
 36 |     genres: ['Drama'],
 37 |     year: 1994,
 38 |     rating: 9.3,
 39 |   },
 40 |   {
 41 |     id: 2,
 42 |     title: 'The Godfather',
 43 |     director: 'Francis Ford Coppola',
 44 |     genres: ['Crime', 'Drama'],
 45 |     year: 1972,
 46 |     rating: 9.2,
 47 |   },
 48 |   {
 49 |     id: 3,
 50 |     title: 'The Dark Knight',
 51 |     director: 'Christopher Nolan',
 52 |     genres: ['Action', 'Crime', 'Drama'],
 53 |     year: 2008,
 54 |     rating: 9.0,
 55 |   },
 56 |   {
 57 |     id: 4,
 58 |     title: 'Pulp Fiction',
 59 |     director: 'Quentin Tarantino',
 60 |     genres: ['Crime', 'Drama'],
 61 |     year: 1994,
 62 |     rating: 8.9,
 63 |   },
 64 |   {
 65 |     id: 5,
 66 |     title: 'The Lord of the Rings: The Return of the King',
 67 |     director: 'Peter Jackson',
 68 |     genres: ['Action', 'Adventure', 'Drama'],
 69 |     year: 2003,
 70 |     rating: 8.9,
 71 |   },
 72 | ];
 73 | 
 74 | /**
 75 |  * Create a movies index
 76 |  */
 77 | async function createMoviesIndex() {
 78 |   try {
 79 |     const response = await meilisearch.post('/indexes', {
 80 |       uid: 'movies',
 81 |       primaryKey: 'id',
 82 |     });
 83 |     console.log('Index created:', response.data);
 84 |     return response.data;
 85 |   } catch (error) {
 86 |     console.error(
 87 |       'Error creating index:',
 88 |       error.response?.data || error.message
 89 |     );
 90 |     throw error;
 91 |   }
 92 | }
 93 | 
 94 | /**
 95 |  * Add movies to the index
 96 |  */
 97 | async function addMovies() {
 98 |   try {
 99 |     const response = await meilisearch.post(
100 |       '/indexes/movies/documents',
101 |       movies
102 |     );
103 |     console.log('Movies added:', response.data);
104 |     return response.data;
105 |   } catch (error) {
106 |     console.error(
107 |       'Error adding movies:',
108 |       error.response?.data || error.message
109 |     );
110 |     throw error;
111 |   }
112 | }
113 | 
114 | /**
115 |  * Update index settings
116 |  */
117 | async function updateSettings() {
118 |   try {
119 |     const settings = {
120 |       searchableAttributes: ['title', 'director', 'genres'],
121 |       filterableAttributes: ['genres', 'year', 'rating'],
122 |       sortableAttributes: ['year', 'rating'],
123 |     };
124 | 
125 |     const response = await meilisearch.patch(
126 |       '/indexes/movies/settings',
127 |       settings
128 |     );
129 |     console.log('Settings updated:', response.data);
130 |     return response.data;
131 |   } catch (error) {
132 |     console.error(
133 |       'Error updating settings:',
134 |       error.response?.data || error.message
135 |     );
136 |     throw error;
137 |   }
138 | }
139 | 
140 | /**
141 |  * Search for movies
142 |  */
143 | async function searchMovies(query, filters = null) {
144 |   try {
145 |     const params = { q: query };
146 |     if (filters) {
147 |       params.filter = filters;
148 |     }
149 | 
150 |     const response = await meilisearch.post('/indexes/movies/search', params);
151 |     console.log(`Search results for "${query}":`, response.data.hits);
152 |     return response.data;
153 |   } catch (error) {
154 |     console.error(
155 |       'Error searching movies:',
156 |       error.response?.data || error.message
157 |     );
158 |     throw error;
159 |   }
160 | }
161 | 
162 | /**
163 |  * Wait for a task to complete
164 |  */
165 | async function waitForTask(taskId) {
166 |   try {
167 |     let task;
168 |     do {
169 |       const response = await meilisearch.get(`/tasks/${taskId}`);
170 |       task = response.data;
171 | 
172 |       if (['succeeded', 'failed', 'canceled'].includes(task.status)) {
173 |         break;
174 |       }
175 | 
176 |       console.log(`Task ${taskId} is ${task.status}. Waiting...`);
177 |       await new Promise((resolve) => setTimeout(resolve, 500));
178 |     } while (true);
179 | 
180 |     console.log(`Task ${taskId} ${task.status}`);
181 |     return task;
182 |   } catch (error) {
183 |     console.error(
184 |       'Error waiting for task:',
185 |       error.response?.data || error.message
186 |     );
187 |     throw error;
188 |   }
189 | }
190 | 
191 | /**
192 |  * Run the demo
193 |  */
194 | async function runDemo() {
195 |   try {
196 |     console.log('Starting Meilisearch Movies Demo...');
197 | 
198 |     // Create index
199 |     const createIndexTask = await createMoviesIndex();
200 |     await waitForTask(createIndexTask.taskUid);
201 | 
202 |     // Add movies
203 |     const addMoviesTask = await addMovies();
204 |     await waitForTask(addMoviesTask.taskUid);
205 | 
206 |     // Update settings
207 |     const updateSettingsTask = await updateSettings();
208 |     await waitForTask(updateSettingsTask.taskUid);
209 | 
210 |     // Perform searches
211 |     await searchMovies('dark');
212 |     await searchMovies('', 'genres = Drama AND year > 2000');
213 |     await searchMovies('', 'rating > 9');
214 | 
215 |     console.log('Demo completed successfully!');
216 |   } catch (error) {
217 |     console.error('Demo failed:', error);
218 |   }
219 | }
220 | 
221 | // Run the demo
222 | runDemo();
223 | 
```

--------------------------------------------------------------------------------
/src/tools/index-tools.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
  2 | import { z } from 'zod';
  3 | 
  4 | import apiClient from '../utils/api-client.js';
  5 | import { createErrorResponse } from '../utils/error-handler.js';
  6 | 
  7 | /**
  8 |  * Meilisearch Index Management Tools
  9 |  * 
 10 |  * This module implements MCP tools for managing Meilisearch indexes.
 11 |  */
 12 | 
 13 | // Define types for index parameters
 14 | interface ListIndexesParams {
 15 |   limit?: number;
 16 |   offset?: number;
 17 | }
 18 | 
 19 | interface GetIndexParams {
 20 |   indexUid: string;
 21 | }
 22 | 
 23 | interface CreateIndexParams {
 24 |   indexUid: string;
 25 |   primaryKey?: string;
 26 | }
 27 | 
 28 | interface UpdateIndexParams {
 29 |   indexUid: string;
 30 |   primaryKey: string;
 31 | }
 32 | 
 33 | interface DeleteIndexParams {
 34 |   indexUid: string;
 35 | }
 36 | 
 37 | interface SwapIndexesParams {
 38 |   indexes: string;
 39 | }
 40 | 
 41 | /**
 42 |  * Register index management tools with the MCP server
 43 |  * 
 44 |  * @param server - The MCP server instance
 45 |  */
 46 | export const registerIndexTools = (server: McpServer) => {
 47 |   // List all indexes
 48 |   server.tool(
 49 |     'list-indexes',
 50 |     'List all indexes in the Meilisearch instance',
 51 |     {
 52 |       limit: z.number().min(1).max(100).optional().describe('Maximum number of indexes to return'),
 53 |       offset: z.number().min(0).optional().describe('Number of indexes to skip'),
 54 |     },
 55 |     async ({ limit, offset }: ListIndexesParams) => {
 56 |       try {
 57 |         const response = await apiClient.get('/indexes', {
 58 |           params: {
 59 |             limit,
 60 |             offset,
 61 |           },
 62 |         });
 63 |         return {
 64 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
 65 |         };
 66 |       } catch (error) {
 67 |         return createErrorResponse(error);
 68 |       }
 69 |     }
 70 |   );
 71 | 
 72 |   // Get index information
 73 |   server.tool(
 74 |     'get-index',
 75 |     'Get information about a specific Meilisearch index',
 76 |     {
 77 |       indexUid: z.string().describe('Unique identifier of the index'),
 78 |     },
 79 |     async ({ indexUid }: GetIndexParams) => {
 80 |       try {
 81 |         const response = await apiClient.get(`/indexes/${indexUid}`);
 82 |         return {
 83 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
 84 |         };
 85 |       } catch (error) {
 86 |         return createErrorResponse(error);
 87 |       }
 88 |     }
 89 |   );
 90 | 
 91 |   // Create a new index
 92 |   server.tool(
 93 |     'create-index',
 94 |     'Create a new Meilisearch index',
 95 |     {
 96 |       indexUid: z.string().describe('Unique identifier for the new index'),
 97 |       primaryKey: z.string().optional().describe('Primary key for the index'),
 98 |     },
 99 |     async ({ indexUid, primaryKey }: CreateIndexParams) => {
100 |       try {
101 |         const response = await apiClient.post('/indexes', {
102 |           uid: indexUid,
103 |           primaryKey,
104 |         });
105 |         return {
106 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
107 |         };
108 |       } catch (error) {
109 |         return createErrorResponse(error);
110 |       }
111 |     }
112 |   );
113 | 
114 |   // Update an index
115 |   server.tool(
116 |     'update-index',
117 |     'Update a Meilisearch index (currently only supports updating the primary key)',
118 |     {
119 |       indexUid: z.string().describe('Unique identifier of the index'),
120 |       primaryKey: z.string().describe('New primary key for the index'),
121 |     },
122 |     async ({ indexUid, primaryKey }: UpdateIndexParams) => {
123 |       try {
124 |         const response = await apiClient.patch(`/indexes/${indexUid}`, {
125 |           primaryKey,
126 |         });
127 |         return {
128 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
129 |         };
130 |       } catch (error) {
131 |         return createErrorResponse(error);
132 |       }
133 |     }
134 |   );
135 | 
136 |   // Delete an index
137 |   server.tool(
138 |     'delete-index',
139 |     'Delete a Meilisearch index',
140 |     {
141 |       indexUid: z.string().describe('Unique identifier of the index to delete'),
142 |     },
143 |     async ({ indexUid }: DeleteIndexParams) => {
144 |       try {
145 |         const response = await apiClient.delete(`/indexes/${indexUid}`);
146 |         return {
147 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
148 |         };
149 |       } catch (error) {
150 |         return createErrorResponse(error);
151 |       }
152 |     }
153 |   );
154 | 
155 |   // Swap indexes
156 |   server.tool(
157 |     'swap-indexes',
158 |     'Swap two or more indexes in Meilisearch',
159 |     {
160 |       indexes: z.string().describe('JSON array of index pairs to swap, e.g. [["movies", "movies_new"]]'),
161 |     },
162 |     async ({ indexes }: SwapIndexesParams) => {
163 |       try {
164 |         // Parse the indexes string to ensure it's valid JSON
165 |         const parsedIndexes = JSON.parse(indexes);
166 |         
167 |         // Ensure indexes is an array of arrays
168 |         if (!Array.isArray(parsedIndexes) || !parsedIndexes.every(pair => Array.isArray(pair) && pair.length === 2)) {
169 |           return {
170 |             isError: true,
171 |             content: [{ type: 'text', text: 'Indexes must be a JSON array of pairs, e.g. [["movies", "movies_new"]]' }],
172 |           };
173 |         }
174 |         
175 |         const response = await apiClient.post('/swap-indexes', parsedIndexes);
176 |         return {
177 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
178 |         };
179 |       } catch (error) {
180 |         return createErrorResponse(error);
181 |       }
182 |     }
183 |   );
184 | };
185 | 
186 | export default registerIndexTools; 
187 | 
```

--------------------------------------------------------------------------------
/src/tools/search-tools.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
  2 | import { z } from 'zod';
  3 | 
  4 | import apiClient from '../utils/api-client.js';
  5 | import { createErrorResponse } from '../utils/error-handler.js';
  6 | 
  7 | /**
  8 |  * Meilisearch Search Tools
  9 |  * 
 10 |  * This module implements MCP tools for searching in Meilisearch indexes.
 11 |  */
 12 | 
 13 | // Define types for search parameters
 14 | interface SearchParams {
 15 |   indexUid: string;
 16 |   q: string;
 17 |   limit?: number;
 18 |   offset?: number;
 19 |   filter?: string;
 20 |   sort?: string[];
 21 |   facets?: string[];
 22 |   attributesToRetrieve?: string[];
 23 |   attributesToCrop?: string[];
 24 |   cropLength?: number;
 25 |   attributesToHighlight?: string[];
 26 |   highlightPreTag?: string;
 27 |   highlightPostTag?: string;
 28 |   showMatchesPosition?: boolean;
 29 |   matchingStrategy?: string;
 30 | }
 31 | 
 32 | interface MultiSearchParams {
 33 |   searches: string;
 34 | }
 35 | 
 36 | /**
 37 |  * Register search tools with the MCP server
 38 |  * 
 39 |  * @param server - The MCP server instance
 40 |  */
 41 | export const registerSearchTools = (server: McpServer) => {
 42 |   // Search in an index
 43 |   server.tool(
 44 |     'search',
 45 |     'Search for documents in a Meilisearch index',
 46 |     {
 47 |       indexUid: z.string().describe('Unique identifier of the index'),
 48 |       q: z.string().describe('Search query'),
 49 |       limit: z.number().min(1).max(1000).optional().describe('Maximum number of results to return (default: 20)'),
 50 |       offset: z.number().min(0).optional().describe('Number of results to skip (default: 0)'),
 51 |       filter: z.string().optional().describe('Filter query to apply'),
 52 |       sort: z.array(z.string()).optional().describe('Attributes to sort by, e.g. ["price:asc"]'),
 53 |       facets: z.array(z.string()).optional().describe('Facets to return'),
 54 |       attributesToRetrieve: z.array(z.string()).optional().describe('Attributes to include in results'),
 55 |       attributesToCrop: z.array(z.string()).optional().describe('Attributes to crop'),
 56 |       cropLength: z.number().optional().describe('Length at which to crop cropped attributes'),
 57 |       attributesToHighlight: z.array(z.string()).optional().describe('Attributes to highlight'),
 58 |       highlightPreTag: z.string().optional().describe('Tag to insert before highlighted text'),
 59 |       highlightPostTag: z.string().optional().describe('Tag to insert after highlighted text'),
 60 |       showMatchesPosition: z.boolean().optional().describe('Whether to include match positions in results'),
 61 |       matchingStrategy: z.string().optional().describe("Matching strategy: 'all' or 'last'"),
 62 |     },
 63 |     async ({ 
 64 |       indexUid, 
 65 |       q, 
 66 |       limit, 
 67 |       offset, 
 68 |       filter, 
 69 |       sort, 
 70 |       facets, 
 71 |       attributesToRetrieve, 
 72 |       attributesToCrop, 
 73 |       cropLength, 
 74 |       attributesToHighlight, 
 75 |       highlightPreTag, 
 76 |       highlightPostTag, 
 77 |       showMatchesPosition, 
 78 |       matchingStrategy 
 79 |     }: SearchParams) => {
 80 |       try {
 81 |         const response = await apiClient.post(`/indexes/${indexUid}/search`, {
 82 |           q,
 83 |           limit,
 84 |           offset,
 85 |           filter,
 86 |           sort,
 87 |           facets,
 88 |           attributesToRetrieve,
 89 |           attributesToCrop,
 90 |           cropLength,
 91 |           attributesToHighlight,
 92 |           highlightPreTag,
 93 |           highlightPostTag,
 94 |           showMatchesPosition,
 95 |           matchingStrategy,
 96 |         });
 97 |         return {
 98 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
 99 |         };
100 |       } catch (error) {
101 |         return createErrorResponse(error);
102 |       }
103 |     }
104 |   );
105 | 
106 |   // Multi-search across multiple indexes
107 |   server.tool(
108 |     'multi-search',
109 |     'Perform multiple searches in one request',
110 |     {
111 |       searches: z.string().describe('JSON array of search queries, each with indexUid and q fields'),
112 |     },
113 |     async ({ searches }: MultiSearchParams) => {
114 |       try {
115 |         // Parse the searches string to ensure it's valid JSON
116 |         const parsedSearches = JSON.parse(searches);
117 |         
118 |         // Ensure searches is an array
119 |         if (!Array.isArray(parsedSearches)) {
120 |           return {
121 |             isError: true,
122 |             content: [{ type: 'text', text: 'Searches must be a JSON array' }],
123 |           };
124 |         }
125 |         
126 |         // Ensure each search has at least indexUid
127 |         for (const search of parsedSearches) {
128 |           if (!search.indexUid) {
129 |             return {
130 |               isError: true,
131 |               content: [{ type: 'text', text: 'Each search must have an indexUid field' }],
132 |             };
133 |           }
134 |         }
135 |         
136 |         const response = await apiClient.post('/multi-search', {
137 |           queries: parsedSearches,
138 |         });
139 |         return {
140 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
141 |         };
142 |       } catch (error) {
143 |         return createErrorResponse(error);
144 |       }
145 |     }
146 |   );
147 | 
148 |   // Facet search
149 |   server.tool(
150 |     'facet-search',
151 |     'Search for facet values matching specific criteria',
152 |     {
153 |       indexUid: z.string().describe('Unique identifier of the index'),
154 |       facetName: z.string().describe('Name of the facet to search'),
155 |       facetQuery: z.string().optional().describe('Query to match against facet values'),
156 |       filter: z.string().optional().describe('Filter to apply to the base search'),
157 |     },
158 |     async ({ indexUid, facetName, facetQuery, filter }) => {
159 |       try {
160 |         const params: Record<string, any> = {
161 |           facetName,
162 |         };
163 |         
164 |         if (facetQuery !== undefined) params.facetQuery = facetQuery;
165 |         if (filter) params.filter = filter;
166 |         
167 |         const response = await apiClient.post(`/indexes/${indexUid}/facet-search`, params);
168 |         return {
169 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
170 |         };
171 |       } catch (error) {
172 |         return createErrorResponse(error);
173 |       }
174 |     }
175 |   );
176 | };
177 | 
178 | export default registerSearchTools; 
179 | 
```

--------------------------------------------------------------------------------
/src/tools/system-tools.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
  2 | import { z } from 'zod';
  3 | 
  4 | import apiClient from '../utils/api-client.js';
  5 | import { createErrorResponse } from '../utils/error-handler.js';
  6 | 
  7 | /**
  8 |  * Meilisearch System Tools
  9 |  * 
 10 |  * This module implements MCP tools for system operations in Meilisearch.
 11 |  */
 12 | 
 13 | /**
 14 |  * Register system tools with the MCP server
 15 |  * 
 16 |  * @param server - The MCP server instance
 17 |  */
 18 | export const registerSystemTools = (server: McpServer) => {
 19 |   // Get health status
 20 |   server.tool(
 21 |     'health',
 22 |     'Check if the Meilisearch server is healthy',
 23 |     {},
 24 |     async () => {
 25 |       try {
 26 |         const response = await apiClient.get('/health');
 27 |         return {
 28 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
 29 |         };
 30 |       } catch (error) {
 31 |         return createErrorResponse(error);
 32 |       }
 33 |     }
 34 |   );
 35 | 
 36 |   // Get version information
 37 |   server.tool(
 38 |     'version',
 39 |     'Get the version information of the Meilisearch server',
 40 |     {},
 41 |     async () => {
 42 |       try {
 43 |         const response = await apiClient.get('/version');
 44 |         return {
 45 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
 46 |         };
 47 |       } catch (error) {
 48 |         return createErrorResponse(error);
 49 |       }
 50 |     }
 51 |   );
 52 | 
 53 |   // Get system information
 54 |   server.tool(
 55 |     'info',
 56 |     'Get the system information of the Meilisearch server',
 57 |     {},
 58 |     async () => {
 59 |       try {
 60 |         const response = await apiClient.get('/');
 61 |         return {
 62 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
 63 |         };
 64 |       } catch (error) {
 65 |         return createErrorResponse(error);
 66 |       }
 67 |     }
 68 |   );
 69 | 
 70 |   // Get statistics
 71 |   server.tool(
 72 |     'stats',
 73 |     'Get statistics about all indexes or a specific index',
 74 |     {
 75 |       indexUid: z.string().optional().describe('Unique identifier of the index (optional, if not provided stats for all indexes will be returned)'),
 76 |     },
 77 |     async ({ indexUid }) => {
 78 |       try {
 79 |         const endpoint = indexUid ? `/indexes/${indexUid}/stats` : '/stats';
 80 |         const response = await apiClient.get(endpoint);
 81 |         return {
 82 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
 83 |         };
 84 |       } catch (error) {
 85 |         return createErrorResponse(error);
 86 |       }
 87 |     }
 88 |   );
 89 | 
 90 |   // Get all tasks (with optional filtering)
 91 |   server.tool(
 92 |     'get-tasks',
 93 |     'Get information about tasks with optional filtering',
 94 |     {
 95 |       limit: z.number().min(0).optional().describe('Maximum number of tasks to return'),
 96 |       from: z.number().min(0).optional().describe('Task uid from which to start fetching'),
 97 |       status: z.enum(['enqueued', 'processing', 'succeeded', 'failed', 'canceled']).optional().describe('Status of tasks to return'),
 98 |       type: z.enum(['indexCreation', 'indexUpdate', 'indexDeletion', 'documentAddition', 'documentUpdate', 'documentDeletion', 'settingsUpdate', 'dumpCreation', 'taskCancelation']).optional().describe('Type of tasks to return'),
 99 |       indexUids: z.array(z.string()).optional().describe('UIDs of the indexes on which tasks were performed'),
100 |     },
101 |     async ({ limit, from, status, type, indexUids }) => {
102 |       try {
103 |         const params: Record<string, any> = {};
104 |         if (limit !== undefined) params.limit = limit;
105 |         if (from !== undefined) params.from = from;
106 |         if (status) params.status = status;
107 |         if (type) params.type = type;
108 |         if (indexUids && indexUids.length > 0) params.indexUids = indexUids.join(',');
109 |         
110 |         const response = await apiClient.get('/tasks', { params });
111 |         return {
112 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
113 |         };
114 |       } catch (error) {
115 |         return createErrorResponse(error);
116 |       }
117 |     }
118 |   );
119 | 
120 |   // Delete tasks
121 |   server.tool(
122 |     'delete-tasks',
123 |     'Delete tasks based on provided filters',
124 |     {
125 |       statuses: z.array(z.enum(['succeeded', 'failed', 'canceled'])).optional().describe('Statuses of tasks to delete'),
126 |       types: z.array(z.enum(['indexCreation', 'indexUpdate', 'indexDeletion', 'documentAddition', 'documentUpdate', 'documentDeletion', 'settingsUpdate', 'dumpCreation', 'taskCancelation'])).optional().describe('Types of tasks to delete'),
127 |       indexUids: z.array(z.string()).optional().describe('UIDs of the indexes on which tasks to delete were performed'),
128 |       uids: z.array(z.number()).optional().describe('UIDs of the tasks to delete'),
129 |       canceledBy: z.array(z.number()).optional().describe('UIDs of the tasks that canceled tasks to delete'),
130 |       beforeUid: z.number().optional().describe('Delete tasks whose uid is before this value'),
131 |       beforeStartedAt: z.string().optional().describe('Delete tasks that started processing before this date (ISO 8601 format)'),
132 |       beforeFinishedAt: z.string().optional().describe('Delete tasks that finished processing before this date (ISO 8601 format)'),
133 |     },
134 |     async ({ statuses, types, indexUids, uids, canceledBy, beforeUid, beforeStartedAt, beforeFinishedAt }) => {
135 |       try {
136 |         const body: Record<string, any> = {};
137 |         if (statuses && statuses.length > 0) body.statuses = statuses;
138 |         if (types && types.length > 0) body.types = types;
139 |         if (indexUids && indexUids.length > 0) body.indexUids = indexUids;
140 |         if (uids && uids.length > 0) body.uids = uids;
141 |         if (canceledBy && canceledBy.length > 0) body.canceledBy = canceledBy;
142 |         if (beforeUid !== undefined) body.beforeUid = beforeUid;
143 |         if (beforeStartedAt) body.beforeStartedAt = beforeStartedAt;
144 |         if (beforeFinishedAt) body.beforeFinishedAt = beforeFinishedAt;
145 |         
146 |         const response = await apiClient.post('/tasks/delete', body);
147 |         return {
148 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
149 |         };
150 |       } catch (error) {
151 |         return createErrorResponse(error);
152 |       }
153 |     }
154 |   );
155 | };
156 | 
157 | export default registerSystemTools; 
158 | 
```

--------------------------------------------------------------------------------
/src/tools/task-tools.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
  2 | import { z } from 'zod';
  3 | 
  4 | import apiClient from '../utils/api-client.js';
  5 | import { createErrorResponse } from '../utils/error-handler.js';
  6 | 
  7 | /**
  8 |  * Meilisearch Task Management Tools
  9 |  * 
 10 |  * This module implements MCP tools for managing tasks in Meilisearch.
 11 |  */
 12 | 
 13 | // Define types for task parameters
 14 | interface ListTasksParams {
 15 |   limit?: number;
 16 |   from?: number;
 17 |   statuses?: string[];
 18 |   types?: string[];
 19 |   indexUids?: string[];
 20 |   uids?: number[];
 21 | }
 22 | 
 23 | interface GetTaskParams {
 24 |   taskUid: number;
 25 | }
 26 | 
 27 | interface CancelTasksParams {
 28 |   statuses?: string[];
 29 |   types?: string[];
 30 |   indexUids?: string[];
 31 |   uids?: number[];
 32 | }
 33 | 
 34 | interface WaitForTaskParams {
 35 |   taskUid: number;
 36 |   timeoutMs?: number;
 37 |   intervalMs?: number;
 38 | }
 39 | 
 40 | /**
 41 |  * Register task management tools with the MCP server
 42 |  * 
 43 |  * @param server - The MCP server instance
 44 |  */
 45 | export const registerTaskTools = (server: McpServer) => {
 46 |   // Get all tasks
 47 |   server.tool(
 48 |     "list-tasks",
 49 |     "List tasks with optional filtering",
 50 |     {
 51 |       limit: z.number().min(0).optional().describe("Maximum number of tasks to return"),
 52 |       from: z.number().min(0).optional().describe("Task uid from which to start fetching"),
 53 |       statuses: z.array(z.enum(["enqueued", "processing", "succeeded", "failed", "canceled"])).optional().describe("Statuses of tasks to return"),
 54 |       types: z.array(z.enum(["indexCreation", "indexUpdate", "indexDeletion", "documentAddition", "documentUpdate", "documentDeletion", "settingsUpdate", "dumpCreation", "taskCancelation"])).optional().describe("Types of tasks to return"),
 55 |       indexUids: z.array(z.string()).optional().describe("UIDs of the indexes on which tasks were performed"),
 56 |       uids: z.array(z.number()).optional().describe("UIDs of specific tasks to return"),
 57 |     },
 58 |     async ({ limit, from, statuses, types, indexUids, uids }: ListTasksParams) => {
 59 |       try {
 60 |         const params: Record<string, any> = {};
 61 |         if (limit !== undefined) params.limit = limit;
 62 |         if (from !== undefined) params.from = from;
 63 |         if (statuses && statuses.length > 0) params.statuses = statuses.join(',');
 64 |         if (types && types.length > 0) params.types = types.join(',');
 65 |         if (indexUids && indexUids.length > 0) params.indexUids = indexUids.join(',');
 66 |         if (uids && uids.length > 0) params.uids = uids.join(',');
 67 |         
 68 |         const response = await apiClient.get('/tasks', { params });
 69 |         return {
 70 |           content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
 71 |         };
 72 |       } catch (error) {
 73 |         return createErrorResponse(error);
 74 |       }
 75 |     }
 76 |   );
 77 | 
 78 |   // Get a specific task
 79 |   server.tool(
 80 |     "get-task",
 81 |     "Get information about a specific task",
 82 |     {
 83 |       taskUid: z.number().describe("Unique identifier of the task"),
 84 |     },
 85 |     async ({ taskUid }: GetTaskParams) => {
 86 |       try {
 87 |         const response = await apiClient.get(`/tasks/${taskUid}`);
 88 |         return {
 89 |           content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
 90 |         };
 91 |       } catch (error) {
 92 |         return createErrorResponse(error);
 93 |       }
 94 |     }
 95 |   );
 96 | 
 97 |   // Cancel tasks
 98 |   server.tool(
 99 |     "cancel-tasks",
100 |     "Cancel tasks based on provided filters",
101 |     {
102 |       statuses: z.array(z.enum(["enqueued", "processing"])).optional().describe("Statuses of tasks to cancel"),
103 |       types: z.array(z.enum(["indexCreation", "indexUpdate", "indexDeletion", "documentAddition", "documentUpdate", "documentDeletion", "settingsUpdate", "dumpCreation", "taskCancelation"])).optional().describe("Types of tasks to cancel"),
104 |       indexUids: z.array(z.string()).optional().describe("UIDs of the indexes on which tasks to cancel were performed"),
105 |       uids: z.array(z.number()).optional().describe("UIDs of the tasks to cancel"),
106 |     },
107 |     async ({ statuses, types, indexUids, uids }: CancelTasksParams) => {
108 |       try {
109 |         const body: Record<string, any> = {};
110 |         if (statuses && statuses.length > 0) body.statuses = statuses;
111 |         if (types && types.length > 0) body.types = types;
112 |         if (indexUids && indexUids.length > 0) body.indexUids = indexUids;
113 |         if (uids && uids.length > 0) body.uids = uids;
114 |         
115 |         const response = await apiClient.post('/tasks/cancel', body);
116 |         return {
117 |           content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
118 |         };
119 |       } catch (error) {
120 |         return createErrorResponse(error);
121 |       }
122 |     }
123 |   );
124 | 
125 |   // Wait for a task to complete
126 |   server.tool(
127 |     "wait-for-task",
128 |     "Wait for a specific task to complete",
129 |     {
130 |       taskUid: z.number().describe("Unique identifier of the task to wait for"),
131 |       timeoutMs: z.number().min(0).optional().describe("Maximum time to wait in milliseconds (default: 5000)"),
132 |       intervalMs: z.number().min(100).optional().describe("Polling interval in milliseconds (default: 500)"),
133 |     },
134 |     async ({ taskUid, timeoutMs = 5000, intervalMs = 500 }: WaitForTaskParams) => {
135 |       try {
136 |         const startTime = Date.now();
137 |         let taskCompleted = false;
138 |         let taskData = null;
139 |         
140 |         while (!taskCompleted && (Date.now() - startTime < timeoutMs)) {
141 |           // Fetch the current task status
142 |           const response = await apiClient.get(`/tasks/${taskUid}`);
143 |           taskData = response.data;
144 |           
145 |           // Check if the task has completed
146 |           if (["succeeded", "failed", "canceled"].includes(taskData.status)) {
147 |             taskCompleted = true;
148 |           } else {
149 |             // Wait for the polling interval
150 |             await new Promise(resolve => setTimeout(resolve, intervalMs));
151 |           }
152 |         }
153 |         
154 |         if (!taskCompleted) {
155 |           return {
156 |             content: [{ type: "text", text: `Task ${taskUid} did not complete within the timeout period of ${timeoutMs}ms` }],
157 |           };
158 |         }
159 |         
160 |         return {
161 |           content: [{ type: "text", text: JSON.stringify(taskData, null, 2) }],
162 |         };
163 |       } catch (error) {
164 |         return createErrorResponse(error);
165 |       }
166 |     }
167 |   );
168 | };
169 | 
170 | export default registerTaskTools; 
171 | 
```

--------------------------------------------------------------------------------
/src/tools/vector-tools.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
  2 | import { z } from 'zod';
  3 | 
  4 | import apiClient from '../utils/api-client.js';
  5 | import { createErrorResponse } from '../utils/error-handler.js';
  6 | 
  7 | /**
  8 |  * Meilisearch Vector Search Tools
  9 |  * 
 10 |  * This module implements MCP tools for vector search capabilities in Meilisearch.
 11 |  * Note: Vector search is an experimental feature in Meilisearch.
 12 |  */
 13 | 
 14 | /**
 15 |  * Register vector search tools with the MCP server
 16 |  * 
 17 |  * @param server - The MCP server instance
 18 |  */
 19 | export const registerVectorTools = (server: McpServer) => {
 20 |   // Enable vector search experimental feature
 21 |   server.tool(
 22 |     "enable-vector-search",
 23 |     "Enable the vector search experimental feature in Meilisearch",
 24 |     {},
 25 |     async () => {
 26 |       try {
 27 |         const response = await apiClient.post('/experimental-features', {
 28 |           vectorStore: true,
 29 |         });
 30 |         return {
 31 |           content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
 32 |         };
 33 |       } catch (error) {
 34 |         return createErrorResponse(error);
 35 |       }
 36 |     }
 37 |   );
 38 | 
 39 |   // Get experimental features status
 40 |   server.tool(
 41 |     "get-experimental-features",
 42 |     "Get the status of experimental features in Meilisearch",
 43 |     {},
 44 |     async () => {
 45 |       try {
 46 |         const response = await apiClient.get('/experimental-features');
 47 |         return {
 48 |           content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
 49 |         };
 50 |       } catch (error) {
 51 |         return createErrorResponse(error);
 52 |       }
 53 |     }
 54 |   );
 55 | 
 56 |   // Update embedders configuration
 57 |   server.tool(
 58 |     "update-embedders",
 59 |     "Configure embedders for vector search",
 60 |     {
 61 |       indexUid: z.string().describe("Unique identifier of the index"),
 62 |       embedders: z.string().describe("JSON object containing embedder configurations"),
 63 |     },
 64 |     async ({ indexUid, embedders }) => {
 65 |       try {
 66 |         // Parse the embedders string to ensure it's valid JSON
 67 |         const parsedEmbedders = JSON.parse(embedders);
 68 |         
 69 |         // Ensure embedders is an object
 70 |         if (typeof parsedEmbedders !== 'object' || parsedEmbedders === null || Array.isArray(parsedEmbedders)) {
 71 |           return {
 72 |             isError: true,
 73 |             content: [{ type: "text", text: "Embedders must be a JSON object" }],
 74 |           };
 75 |         }
 76 |         
 77 |         const response = await apiClient.patch(`/indexes/${indexUid}/settings/embedders`, parsedEmbedders);
 78 |         return {
 79 |           content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
 80 |         };
 81 |       } catch (error) {
 82 |         return createErrorResponse(error);
 83 |       }
 84 |     }
 85 |   );
 86 | 
 87 |   // Get embedders configuration
 88 |   server.tool(
 89 |     "get-embedders",
 90 |     "Get the embedders configuration for an index",
 91 |     {
 92 |       indexUid: z.string().describe("Unique identifier of the index"),
 93 |     },
 94 |     async ({ indexUid }) => {
 95 |       try {
 96 |         const response = await apiClient.get(`/indexes/${indexUid}/settings/embedders`);
 97 |         return {
 98 |           content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
 99 |         };
100 |       } catch (error) {
101 |         return createErrorResponse(error);
102 |       }
103 |     }
104 |   );
105 | 
106 |   // Reset embedders configuration
107 |   server.tool(
108 |     "reset-embedders",
109 |     "Reset the embedders configuration for an index",
110 |     {
111 |       indexUid: z.string().describe("Unique identifier of the index"),
112 |     },
113 |     async ({ indexUid }) => {
114 |       try {
115 |         const response = await apiClient.delete(`/indexes/${indexUid}/settings/embedders`);
116 |         return {
117 |           content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
118 |         };
119 |       } catch (error) {
120 |         return createErrorResponse(error);
121 |       }
122 |     }
123 |   );
124 | 
125 |   // Perform vector search
126 |   server.tool(
127 |     "vector-search",
128 |     "Perform a vector search in a Meilisearch index",
129 |     {
130 |       indexUid: z.string().describe("Unique identifier of the index"),
131 |       vector: z.string().describe("JSON array representing the vector to search for"),
132 |       limit: z.number().min(1).max(1000).optional().describe("Maximum number of results to return (default: 20)"),
133 |       offset: z.number().min(0).optional().describe("Number of results to skip (default: 0)"),
134 |       filter: z.string().optional().describe("Filter to apply (e.g., 'genre = horror AND year > 2020')"),
135 |       embedder: z.string().optional().describe("Name of the embedder to use (if omitted, a 'vector' must be provided)"),
136 |       attributes: z.array(z.string()).optional().describe("Attributes to include in the vector search"),
137 |       query: z.string().optional().describe("Text query to search for (if using 'embedder' instead of 'vector')"),
138 |       hybrid: z.boolean().optional().describe("Whether to perform a hybrid search (combining vector and text search)"),
139 |       hybridRatio: z.number().min(0).max(1).optional().describe("Ratio of vector vs text search in hybrid search (0-1, default: 0.5)"),
140 |     },
141 |     async ({ indexUid, vector, limit, offset, filter, embedder, attributes, query, hybrid, hybridRatio }) => {
142 |       try {
143 |         const searchParams: Record<string, any> = {};
144 |         
145 |         // Add required vector parameter
146 |         if (vector) {
147 |           try {
148 |             searchParams.vector = JSON.parse(vector);
149 |           } catch (parseError) {
150 |             return {
151 |               isError: true,
152 |               content: [{ type: "text", text: "Vector must be a valid JSON array" }],
153 |             };
154 |           }
155 |         }
156 |         
157 |         // Add embedder parameters
158 |         if (embedder) {
159 |           searchParams.embedder = embedder;
160 |           
161 |           if (query !== undefined) {
162 |             searchParams.q = query;
163 |           }
164 |         }
165 |         
166 |         // Ensure we have either vector or (embedder + query)
167 |         if (!vector && (!embedder || query === undefined)) {
168 |           return {
169 |             isError: true,
170 |             content: [{ type: "text", text: "Either 'vector' or both 'embedder' and 'query' must be provided" }],
171 |           };
172 |         }
173 |         
174 |         // Add optional parameters
175 |         if (limit !== undefined) searchParams.limit = limit;
176 |         if (offset !== undefined) searchParams.offset = offset;
177 |         if (filter) searchParams.filter = filter;
178 |         if (attributes?.length) searchParams.attributes = attributes;
179 |         if (hybrid !== undefined) searchParams.hybrid = hybrid;
180 |         if (hybridRatio !== undefined) searchParams.hybridRatio = hybridRatio;
181 |         
182 |         const response = await apiClient.post(`/indexes/${indexUid}/search`, searchParams);
183 |         return {
184 |           content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
185 |         };
186 |       } catch (error) {
187 |         return createErrorResponse(error);
188 |       }
189 |     }
190 |   );
191 | };
192 | 
193 | export default registerVectorTools; 
194 | 
```

--------------------------------------------------------------------------------
/src/tools/document-tools.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
  2 | import { z } from 'zod';
  3 | 
  4 | import apiClient from '../utils/api-client.js';
  5 | import { createErrorResponse } from '../utils/error-handler.js';
  6 | 
  7 | /**
  8 |  * Meilisearch Document Management Tools
  9 |  * 
 10 |  * This module implements MCP tools for managing documents in Meilisearch indexes.
 11 |  */
 12 | 
 13 | // Define types for document parameters
 14 | interface GetDocumentsParams {
 15 |   indexUid: string;
 16 |   limit?: number;
 17 |   offset?: number;
 18 |   fields?: string[];
 19 |   filter?: string;
 20 | }
 21 | 
 22 | interface GetDocumentParams {
 23 |   indexUid: string;
 24 |   documentId: string;
 25 |   fields?: string[];
 26 | }
 27 | 
 28 | interface AddDocumentsParams {
 29 |   indexUid: string;
 30 |   documents: string;
 31 |   primaryKey?: string;
 32 | }
 33 | 
 34 | interface UpdateDocumentsParams {
 35 |   indexUid: string;
 36 |   documents: string;
 37 |   primaryKey?: string;
 38 | }
 39 | 
 40 | interface DeleteDocumentParams {
 41 |   indexUid: string;
 42 |   documentId: string;
 43 | }
 44 | 
 45 | interface DeleteDocumentsParams {
 46 |   indexUid: string;
 47 |   documentIds: string;
 48 | }
 49 | 
 50 | interface DeleteAllDocumentsParams {
 51 |   indexUid: string;
 52 | }
 53 | 
 54 | /**
 55 |  * Register document management tools with the MCP server
 56 |  * 
 57 |  * @param server - The MCP server instance
 58 |  */
 59 | export const registerDocumentTools = (server: McpServer) => {
 60 |   // Get documents from an index
 61 |   server.tool(
 62 |     'get-documents',
 63 |     'Get documents from a Meilisearch index',
 64 |     {
 65 |       indexUid: z.string().describe('Unique identifier of the index'),
 66 |       limit: z.number().min(1).max(1000).optional().describe('Maximum number of documents to return (default: 20)'),
 67 |       offset: z.number().min(0).optional().describe('Number of documents to skip (default: 0)'),
 68 |       fields: z.array(z.string()).optional().describe('Fields to return in the documents'),
 69 |       filter: z.string().optional().describe('Filter query to apply'),
 70 |     },
 71 |     async ({ indexUid, limit, offset, fields, filter }: GetDocumentsParams) => {
 72 |       try {
 73 |         const response = await apiClient.get(`/indexes/${indexUid}/documents`, {
 74 |           params: {
 75 |             limit,
 76 |             offset,
 77 |             fields: fields?.join(','),
 78 |             filter,
 79 |           },
 80 |         });
 81 |         return {
 82 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
 83 |         };
 84 |       } catch (error) {
 85 |         return createErrorResponse(error);
 86 |       }
 87 |     }
 88 |   );
 89 | 
 90 |   // Get a single document by ID
 91 |   server.tool(
 92 |     'get-document',
 93 |     'Get a document by its ID from a Meilisearch index',
 94 |     {
 95 |       indexUid: z.string().describe('Unique identifier of the index'),
 96 |       documentId: z.string().describe('ID of the document to retrieve'),
 97 |       fields: z.array(z.string()).optional().describe('Fields to return in the document'),
 98 |     },
 99 |     async ({ indexUid, documentId, fields }: GetDocumentParams) => {
100 |       try {
101 |         const response = await apiClient.get(`/indexes/${indexUid}/documents/${documentId}`, {
102 |           params: {
103 |             fields: fields?.join(','),
104 |           },
105 |         });
106 |         return {
107 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
108 |         };
109 |       } catch (error) {
110 |         return createErrorResponse(error);
111 |       }
112 |     }
113 |   );
114 | 
115 |   // Add documents to an index
116 |   server.tool(
117 |     'add-documents',
118 |     'Add documents to a Meilisearch index',
119 |     {
120 |       indexUid: z.string().describe('Unique identifier of the index'),
121 |       documents: z.string().describe('JSON array of documents to add'),
122 |       primaryKey: z.string().optional().describe('Primary key for the documents'),
123 |     },
124 |     async ({ indexUid, documents, primaryKey }: AddDocumentsParams) => {
125 |       try {
126 |         // Parse the documents string to ensure it's valid JSON
127 |         const parsedDocuments = JSON.parse(documents);
128 |         
129 |         // Ensure documents is an array
130 |         if (!Array.isArray(parsedDocuments)) {
131 |           return {
132 |             isError: true,
133 |             content: [{ type: 'text', text: 'Documents must be a JSON array' }],
134 |           };
135 |         }
136 |         
137 |         const params: Record<string, string> = {};
138 |         if (primaryKey) {
139 |           params.primaryKey = primaryKey;
140 |         }
141 |         
142 |         const response = await apiClient.post(`/indexes/${indexUid}/documents`, parsedDocuments, {
143 |           params,
144 |         });
145 |         return {
146 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
147 |         };
148 |       } catch (error) {
149 |         return createErrorResponse(error);
150 |       }
151 |     }
152 |   );
153 | 
154 |   // Update documents in an index
155 |   server.tool(
156 |     'update-documents',
157 |     'Update documents in a Meilisearch index',
158 |     {
159 |       indexUid: z.string().describe('Unique identifier of the index'),
160 |       documents: z.string().describe('JSON array of documents to update'),
161 |       primaryKey: z.string().optional().describe('Primary key for the documents'),
162 |     },
163 |     async ({ indexUid, documents, primaryKey }: UpdateDocumentsParams) => {
164 |       try {
165 |         // Parse the documents string to ensure it's valid JSON
166 |         const parsedDocuments = JSON.parse(documents);
167 |         
168 |         // Ensure documents is an array
169 |         if (!Array.isArray(parsedDocuments)) {
170 |           return {
171 |             isError: true,
172 |             content: [{ type: 'text', text: 'Documents must be a JSON array' }],
173 |           };
174 |         }
175 |         
176 |         const params: Record<string, string> = {};
177 |         if (primaryKey) {
178 |           params.primaryKey = primaryKey;
179 |         }
180 |         
181 |         const response = await apiClient.put(`/indexes/${indexUid}/documents`, parsedDocuments, {
182 |           params,
183 |         });
184 |         return {
185 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
186 |         };
187 |       } catch (error) {
188 |         return createErrorResponse(error);
189 |       }
190 |     }
191 |   );
192 | 
193 |   // Delete a document by ID
194 |   server.tool(
195 |     'delete-document',
196 |     'Delete a document by its ID from a Meilisearch index',
197 |     {
198 |       indexUid: z.string().describe('Unique identifier of the index'),
199 |       documentId: z.string().describe('ID of the document to delete'),
200 |     },
201 |     async ({ indexUid, documentId }: DeleteDocumentParams) => {
202 |       try {
203 |         const response = await apiClient.delete(`/indexes/${indexUid}/documents/${documentId}`);
204 |         return {
205 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
206 |         };
207 |       } catch (error) {
208 |         return createErrorResponse(error);
209 |       }
210 |     }
211 |   );
212 | 
213 |   // Delete multiple documents by ID
214 |   server.tool(
215 |     'delete-documents',
216 |     'Delete multiple documents by their IDs from a Meilisearch index',
217 |     {
218 |       indexUid: z.string().describe('Unique identifier of the index'),
219 |       documentIds: z.string().describe('JSON array of document IDs to delete'),
220 |     },
221 |     async ({ indexUid, documentIds }: DeleteDocumentsParams) => {
222 |       try {
223 |         // Parse the document IDs string to ensure it's valid JSON
224 |         const parsedDocumentIds = JSON.parse(documentIds);
225 |         
226 |         // Ensure document IDs is an array
227 |         if (!Array.isArray(parsedDocumentIds)) {
228 |           return {
229 |             isError: true,
230 |             content: [{ type: 'text', text: 'Document IDs must be a JSON array' }],
231 |           };
232 |         }
233 |         
234 |         const response = await apiClient.post(`/indexes/${indexUid}/documents/delete-batch`, parsedDocumentIds);
235 |         return {
236 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
237 |         };
238 |       } catch (error) {
239 |         return createErrorResponse(error);
240 |       }
241 |     }
242 |   );
243 | 
244 |   // Delete all documents in an index
245 |   server.tool(
246 |     'delete-all-documents',
247 |     'Delete all documents in a Meilisearch index',
248 |     {
249 |       indexUid: z.string().describe('Unique identifier of the index'),
250 |     },
251 |     async ({ indexUid }: DeleteAllDocumentsParams) => {
252 |       try {
253 |         const response = await apiClient.delete(`/indexes/${indexUid}/documents`);
254 |         return {
255 |           content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
256 |         };
257 |       } catch (error) {
258 |         return createErrorResponse(error);
259 |       }
260 |     }
261 |   );
262 | };
263 | 
264 | export default registerDocumentTools; 
265 | 
```

--------------------------------------------------------------------------------
/src/tools/settings-tools.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
  2 | import { z } from 'zod';
  3 | 
  4 | import apiClient from '../utils/api-client.js';
  5 | import { createErrorResponse } from '../utils/error-handler.js';
  6 | 
  7 | /**
  8 |  * Meilisearch Settings Management Tools
  9 |  * 
 10 |  * This module implements MCP tools for managing index settings in Meilisearch.
 11 |  */
 12 | 
 13 | /**
 14 |  * Register settings management tools with the MCP server
 15 |  * 
 16 |  * @param server - The MCP server instance
 17 |  */
 18 | export const registerSettingsTools = (server: McpServer) => {
 19 |   // Get all settings
 20 |   server.tool(
 21 |     "get-settings",
 22 |     "Get all settings for a Meilisearch index",
 23 |     {
 24 |       indexUid: z.string().describe("Unique identifier of the index"),
 25 |     },
 26 |     async ({ indexUid }) => {
 27 |       try {
 28 |         const response = await apiClient.get(`/indexes/${indexUid}/settings`);
 29 |         return {
 30 |           content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
 31 |         };
 32 |       } catch (error) {
 33 |         return createErrorResponse(error);
 34 |       }
 35 |     }
 36 |   );
 37 | 
 38 |   // Update settings
 39 |   server.tool(
 40 |     "update-settings",
 41 |     "Update settings for a Meilisearch index",
 42 |     {
 43 |       indexUid: z.string().describe("Unique identifier of the index"),
 44 |       settings: z.string().describe("JSON object containing settings to update"),
 45 |     },
 46 |     async ({ indexUid, settings }) => {
 47 |       try {
 48 |         // Parse the settings string to ensure it's valid JSON
 49 |         const parsedSettings = JSON.parse(settings);
 50 |         
 51 |         // Ensure settings is an object
 52 |         if (typeof parsedSettings !== 'object' || parsedSettings === null || Array.isArray(parsedSettings)) {
 53 |           return {
 54 |             isError: true,
 55 |             content: [{ type: "text", text: "Settings must be a JSON object" }],
 56 |           };
 57 |         }
 58 |         
 59 |         const response = await apiClient.patch(`/indexes/${indexUid}/settings`, parsedSettings);
 60 |         return {
 61 |           content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
 62 |         };
 63 |       } catch (error) {
 64 |         return createErrorResponse(error);
 65 |       }
 66 |     }
 67 |   );
 68 | 
 69 |   // Reset settings
 70 |   server.tool(
 71 |     "reset-settings",
 72 |     "Reset all settings for a Meilisearch index to their default values",
 73 |     {
 74 |       indexUid: z.string().describe("Unique identifier of the index"),
 75 |     },
 76 |     async ({ indexUid }) => {
 77 |       try {
 78 |         const response = await apiClient.delete(`/indexes/${indexUid}/settings`);
 79 |         return {
 80 |           content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
 81 |         };
 82 |       } catch (error) {
 83 |         return createErrorResponse(error);
 84 |       }
 85 |     }
 86 |   );
 87 | 
 88 |   // Get specific settings
 89 |   const specificSettingsTools = [
 90 |     {
 91 |       name: "get-searchable-attributes",
 92 |       endpoint: "searchable-attributes",
 93 |       description: "Get the searchable attributes setting",
 94 |     },
 95 |     {
 96 |       name: "get-displayed-attributes",
 97 |       endpoint: "displayed-attributes",
 98 |       description: "Get the displayed attributes setting",
 99 |     },
100 |     {
101 |       name: "get-filterable-attributes",
102 |       endpoint: "filterable-attributes",
103 |       description: "Get the filterable attributes setting",
104 |     },
105 |     {
106 |       name: "get-sortable-attributes",
107 |       endpoint: "sortable-attributes",
108 |       description: "Get the sortable attributes setting",
109 |     },
110 |     {
111 |       name: "get-ranking-rules",
112 |       endpoint: "ranking-rules",
113 |       description: "Get the ranking rules setting",
114 |     },
115 |     {
116 |       name: "get-stop-words",
117 |       endpoint: "stop-words",
118 |       description: "Get the stop words setting",
119 |     },
120 |     {
121 |       name: "get-synonyms",
122 |       endpoint: "synonyms",
123 |       description: "Get the synonyms setting",
124 |     },
125 |     {
126 |       name: "get-distinct-attribute",
127 |       endpoint: "distinct-attribute",
128 |       description: "Get the distinct attribute setting",
129 |     },
130 |     {
131 |       name: "get-typo-tolerance",
132 |       endpoint: "typo-tolerance",
133 |       description: "Get the typo tolerance setting",
134 |     },
135 |     {
136 |       name: "get-faceting",
137 |       endpoint: "faceting",
138 |       description: "Get the faceting setting",
139 |     },
140 |     {
141 |       name: "get-pagination",
142 |       endpoint: "pagination",
143 |       description: "Get the pagination setting",
144 |     },
145 |   ];
146 | 
147 |   // Create a tool for each specific setting
148 |   specificSettingsTools.forEach(({ name, endpoint, description }) => {
149 |     server.tool(
150 |       name,
151 |       description,
152 |       {
153 |         indexUid: z.string().describe("Unique identifier of the index"),
154 |       },
155 |       async ({ indexUid }) => {
156 |         try {
157 |           const response = await apiClient.get(`/indexes/${indexUid}/settings/${endpoint}`);
158 |           return {
159 |             content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
160 |           };
161 |         } catch (error) {
162 |           return createErrorResponse(error);
163 |         }
164 |       }
165 |     );
166 |   });
167 | 
168 |   // Update specific settings
169 |   const updateSettingsTools = [
170 |     {
171 |       name: "update-searchable-attributes",
172 |       endpoint: "searchable-attributes",
173 |       description: "Update the searchable attributes setting",
174 |     },
175 |     {
176 |       name: "update-displayed-attributes",
177 |       endpoint: "displayed-attributes",
178 |       description: "Update the displayed attributes setting",
179 |     },
180 |     {
181 |       name: "update-filterable-attributes",
182 |       endpoint: "filterable-attributes",
183 |       description: "Update the filterable attributes setting",
184 |     },
185 |     {
186 |       name: "update-sortable-attributes",
187 |       endpoint: "sortable-attributes",
188 |       description: "Update the sortable attributes setting",
189 |     },
190 |     {
191 |       name: "update-ranking-rules",
192 |       endpoint: "ranking-rules",
193 |       description: "Update the ranking rules setting",
194 |     },
195 |     {
196 |       name: "update-stop-words",
197 |       endpoint: "stop-words",
198 |       description: "Update the stop words setting",
199 |     },
200 |     {
201 |       name: "update-synonyms",
202 |       endpoint: "synonyms",
203 |       description: "Update the synonyms setting",
204 |     },
205 |     {
206 |       name: "update-distinct-attribute",
207 |       endpoint: "distinct-attribute",
208 |       description: "Update the distinct attribute setting",
209 |     },
210 |     {
211 |       name: "update-typo-tolerance",
212 |       endpoint: "typo-tolerance",
213 |       description: "Update the typo tolerance setting",
214 |     },
215 |     {
216 |       name: "update-faceting",
217 |       endpoint: "faceting",
218 |       description: "Update the faceting setting",
219 |     },
220 |     {
221 |       name: "update-pagination",
222 |       endpoint: "pagination",
223 |       description: "Update the pagination setting",
224 |     },
225 |   ];
226 | 
227 |   // Create an update tool for each specific setting
228 |   updateSettingsTools.forEach(({ name, endpoint, description }) => {
229 |     server.tool(
230 |       name,
231 |       description,
232 |       {
233 |         indexUid: z.string().describe("Unique identifier of the index"),
234 |         value: z.string().describe("JSON value for the setting"),
235 |       },
236 |       async ({ indexUid, value }) => {
237 |         try {
238 |           // Parse the value string to ensure it's valid JSON
239 |           const parsedValue = JSON.parse(value);
240 |           
241 |           const response = await apiClient.put(`/indexes/${indexUid}/settings/${endpoint}`, parsedValue);
242 |           return {
243 |             content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
244 |           };
245 |         } catch (error) {
246 |           return createErrorResponse(error);
247 |         }
248 |       }
249 |     );
250 |   });
251 | 
252 |   // Reset specific settings
253 |   const resetSettingsTools = [
254 |     {
255 |       name: "reset-searchable-attributes",
256 |       endpoint: "searchable-attributes",
257 |       description: "Reset the searchable attributes setting to its default value",
258 |     },
259 |     {
260 |       name: "reset-displayed-attributes",
261 |       endpoint: "displayed-attributes",
262 |       description: "Reset the displayed attributes setting to its default value",
263 |     },
264 |     {
265 |       name: "reset-filterable-attributes",
266 |       endpoint: "filterable-attributes",
267 |       description: "Reset the filterable attributes setting to its default value",
268 |     },
269 |     {
270 |       name: "reset-sortable-attributes",
271 |       endpoint: "sortable-attributes",
272 |       description: "Reset the sortable attributes setting to its default value",
273 |     },
274 |     {
275 |       name: "reset-ranking-rules",
276 |       endpoint: "ranking-rules",
277 |       description: "Reset the ranking rules setting to its default value",
278 |     },
279 |     {
280 |       name: "reset-stop-words",
281 |       endpoint: "stop-words",
282 |       description: "Reset the stop words setting to its default value",
283 |     },
284 |     {
285 |       name: "reset-synonyms",
286 |       endpoint: "synonyms",
287 |       description: "Reset the synonyms setting to its default value",
288 |     },
289 |     {
290 |       name: "reset-distinct-attribute",
291 |       endpoint: "distinct-attribute",
292 |       description: "Reset the distinct attribute setting to its default value",
293 |     },
294 |     {
295 |       name: "reset-typo-tolerance",
296 |       endpoint: "typo-tolerance",
297 |       description: "Reset the typo tolerance setting to its default value",
298 |     },
299 |     {
300 |       name: "reset-faceting",
301 |       endpoint: "faceting",
302 |       description: "Reset the faceting setting to its default value",
303 |     },
304 |     {
305 |       name: "reset-pagination",
306 |       endpoint: "pagination",
307 |       description: "Reset the pagination setting to its default value",
308 |     },
309 |   ];
310 | 
311 |   // Create a reset tool for each specific setting
312 |   resetSettingsTools.forEach(({ name, endpoint, description }) => {
313 |     server.tool(
314 |       name,
315 |       description,
316 |       {
317 |         indexUid: z.string().describe("Unique identifier of the index"),
318 |       },
319 |       async ({ indexUid }) => {
320 |         try {
321 |           const response = await apiClient.delete(`/indexes/${indexUid}/settings/${endpoint}`);
322 |           return {
323 |             content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
324 |           };
325 |         } catch (error) {
326 |           return createErrorResponse(error);
327 |         }
328 |       }
329 |     );
330 |   });
331 | };
332 | 
333 | export default registerSettingsTools; 
334 | 
```
Page 1/2FirstPrevNextLast