#
tokens: 22146/50000 30/31 files (page 1/2)
lines: off (toggle) GitHub
raw markdown copy
This is page 1 of 2. Use http://codebase.md/devlimelabs/meilisearch-ts-mcp?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:
--------------------------------------------------------------------------------

```
# Meilisearch MCP Server Environment Variables

# URL of the Meilisearch instance
MEILISEARCH_HOST=http://localhost:7700
# API key for authenticating with Meilisearch
# Leave empty if no key is required for your Meilisearch instance
MEILISEARCH_API_KEY=

# Timeout for API requests in milliseconds
MEILISEARCH_TIMEOUT=5000 

```

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

```
export const resolve = (specifier, context, nextResolve) => {
  return nextResolve(specifier, context);
};

export const load = (url, context, nextLoad) => {
  return nextLoad(url, context);
};

export const getFormat = (url, context, defaultGetFormat) => {
  if (url.endsWith('.ts')) {
    return { format: 'module' };
  }
  return defaultGetFormat(url, context);
};

```

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

```json
{
  "env": {
    "es2022": true,
    "node": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module"
  },
  "plugins": [
    "@typescript-eslint"
  ],
  "rules": {
    "indent": ["error", 2],
    "linebreak-style": ["error", "unix"],
    "quotes": ["error", "single", { "avoidEscape": true }],
    "semi": ["error", "always"],
    "@typescript-eslint/no-explicit-any": "warn",
    "@typescript-eslint/explicit-function-return-type": "off",
    "@typescript-eslint/explicit-module-boundary-types": "off"
  }
} 

```

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

```
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

```

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

```markdown
# Meilisearch MCP Server

[![smithery badge](https://smithery.ai/badge/@devlimelabs/meilisearch-ts-mcp)](https://smithery.ai/server/@devlimelabs/meilisearch-ts-mcp)

A Model Context Protocol (MCP) server implementation for Meilisearch, enabling AI assistants to interact with Meilisearch through a standardized interface.

## Features

- **Index Management**: Create, update, and delete indexes
- **Document Management**: Add, update, and delete documents
- **Search Capabilities**: Perform searches with various parameters and filters
- **Settings Management**: Configure index settings
- **Task Management**: Monitor and manage asynchronous tasks
- **System Operations**: Health checks, version information, and statistics
- **Vector Search**: Experimental vector search capabilities

## Installation

### Installing via Smithery

To install Meilisearch MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@devlimelabs/meilisearch-ts-mcp):

```bash
npx -y @smithery/cli install @devlimelabs/meilisearch-ts-mcp --client claude
```

### Manual Installation
1. Clone the repository:
   ```bash
   git clone https://github.com/devlimelabs/meilisearch-ts-mcp.git
   cd meilisearch-ts-mcp
   ```

2. Install dependencies:
   ```bash
   npm install
   ```

3. Create a `.env` file based on the example:
   ```bash
   cp .env.example .env
   ```
   
4. Edit the `.env` file to configure your Meilisearch connection.

## Docker Setup

The Meilisearch MCP Server can be run in a Docker container for easier deployment and isolation.

### Using Docker Compose

The easiest way to get started with Docker is to use Docker Compose:

```bash
# Start the Meilisearch MCP Server
docker-compose up -d

# View logs
docker-compose logs -f

# Stop the server
docker-compose down
```

### Building and Running the Docker Image Manually

You can also build and run the Docker image manually:

```bash
# Build the Docker image
docker build -t meilisearch-ts-mcp .

# Run the container
docker run -p 3000:3000 --env-file .env meilisearch-ts-mcp
```

## Development Setup

For developers who want to contribute to the Meilisearch MCP Server, we provide a convenient setup script:

```bash
# Clone the repository
git clone https://github.com/devlimelabs-ts-mcp/meilisearch-ts-mcp.git
cd meilisearch-ts-mcp

# Run the development setup script
./scripts/setup-dev.sh
```

The setup script will:
1. Create a `.env` file from `.env.example` if it doesn't exist
2. Install dependencies
3. Build the project
4. Run tests to ensure everything is working correctly

After running the setup script, you can start the server in development mode:

```bash
npm run dev
```

## Usage

### Building the Project

```bash
npm run build
```

### Running the Server

```bash
npm start
```

### Development Mode

```bash
npm run dev
```

## Claude Desktop Integration

The Meilisearch MCP Server can be integrated with Claude for Desktop, allowing you to interact with your Meilisearch instance directly through Claude.

### Automated Setup

We provide a setup script that automatically configures Claude for Desktop to work with the Meilisearch MCP Server:

```bash
# First build the project
npm run build

# Then run the setup script
node scripts/claude-desktop-setup.js
```

The script will:
1. Detect your operating system and locate the Claude for Desktop configuration file
2. Read your Meilisearch configuration from the `.env` file
3. Generate the necessary configuration for Claude for Desktop
4. Provide instructions for updating your Claude for Desktop configuration

### Manual Setup

If you prefer to manually configure Claude for Desktop:

1. Locate your Claude for Desktop configuration file:
   - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
   - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
   - **Linux**: `~/.config/Claude/claude_desktop_config.json`

2. Add the following configuration (adjust paths as needed):

```json
{
  "mcpServers": {
    "meilisearch": {
      "command": "node",
      "args": ["/path/to/meilisearch-ts-mcp/dist/index.js"],
      "env": {
        "MEILISEARCH_HOST": "http://localhost:7700",
        "MEILISEARCH_API_KEY": "your-api-key"
      }
    }
  }
}
```

3. Restart Claude for Desktop to apply the changes.

4. In Claude, type: "I want to use the Meilisearch MCP server" to activate the integration.

## Cursor Integration

The Meilisearch MCP Server can also be integrated with [Cursor](https://cursor.com), an AI-powered code editor.

### Setting Up MCP in Cursor

1. Install and set up the Meilisearch MCP Server:
   ```bash
   git clone https://github.com/devlimelabs/meilisearch-ts-mcp.git
   cd meilisearch-ts-mcp
   npm install
   npm run build
   ```

2. Start the MCP server:
   ```bash
   npm start
   ```

3. In Cursor, open the Command Palette (Cmd/Ctrl+Shift+P) and search for "MCP: Connect to MCP Server".

4. Select "Connect to a local MCP server" and enter the following details:
   - **Name**: Meilisearch
   - **Command**: node
   - **Arguments**: /absolute/path/to/meilisearch-ts-mcp/dist/index.js
   - **Environment Variables**: 
     ```
     MEILISEARCH_HOST=http://localhost:7700
     MEILISEARCH_API_KEY=your-api-key
     ```

5. Click "Connect" to establish the connection.

6. You can now interact with your Meilisearch instance through Cursor by typing commands like "Search my Meilisearch index for documents about..."

## Available Tools

The Meilisearch MCP Server provides the following tools:

### Index Tools
- `create-index`: Create a new index
- `get-index`: Get information about an index
- `list-indexes`: List all indexes
- `update-index`: Update an index
- `delete-index`: Delete an index

### Document Tools
- `add-documents`: Add documents to an index
- `get-document`: Get a document by ID
- `get-documents`: Get multiple documents
- `update-documents`: Update documents
- `delete-document`: Delete a document by ID
- `delete-documents`: Delete multiple documents
- `delete-all-documents`: Delete all documents in an index

### Search Tools
- `search`: Search for documents
- `multi-search`: Perform multiple searches in a single request

### Settings Tools
- `get-settings`: Get index settings
- `update-settings`: Update index settings
- `reset-settings`: Reset index settings to default
- Various specific settings tools (synonyms, stop words, ranking rules, etc.)

### Task Tools
- `list-tasks`: List tasks with optional filtering
- `get-task`: Get information about a specific task
- `cancel-tasks`: Cancel tasks based on provided filters
- `wait-for-task`: Wait for a specific task to complete

### System Tools
- `health`: Check the health status of the Meilisearch server
- `version`: Get version information
- `info`: Get system information
- `stats`: Get statistics about indexes

### Vector Tools (Experimental)
- `enable-vector-search`: Enable vector search
- `get-experimental-features`: Get experimental features status
- `update-embedders`: Configure embedders
- `get-embedders`: Get embedders configuration
- `reset-embedders`: Reset embedders configuration
- `vector-search`: Perform vector search

## License

MIT

```

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

```markdown
# Contributing to Meilisearch MCP Server

Thank you for your interest in contributing to the Meilisearch MCP Server! This document provides guidelines and instructions for contributing.

## Code of Conduct

Please be respectful and considerate of others when contributing to this project. We aim to foster an inclusive and welcoming community.

## Getting Started

1. Fork the repository
2. Clone your fork: `git clone https://github.com/devlimelabs/meilisearch-ts-mcp.git`
3. Navigate to the project directory: `cd meilisearch-ts-mcp`
4. Install dependencies: `npm install`
5. Create a new branch for your feature or bugfix: `git checkout -b feature/your-feature-name`

## Development Workflow

1. Make your changes
2. Run the linter: `npm run lint`
3. Run tests: `npm test`
4. Build the project: `npm run build`
5. Test your changes with a local Meilisearch instance

## Pull Request Process

1. Ensure your code passes all tests and linting
2. Update documentation if necessary
3. Submit a pull request to the `main` branch
4. Describe your changes in detail in the pull request description
5. Reference any related issues

## Adding New Tools

When adding new tools to the MCP server:

1. Create a new file in the `src/tools` directory if appropriate
2. Follow the existing pattern for tool registration
3. Use Zod for parameter validation
4. Add proper error handling
5. Update the README.md to document the new tool

## Coding Standards

- Use TypeScript for all new code
- Follow the existing code style
- Write meaningful commit messages
- Add comments for complex logic
- Write tests for new functionality

## Testing

- Write unit tests for new functionality
- Ensure all tests pass before submitting a pull request
- Test with a real Meilisearch instance when possible

## Documentation

- Update the README.md file with any new features or changes
- Document all new tools and parameters
- Provide examples for complex functionality

## License

By contributing to this project, you agree that your contributions will be licensed under the project's MIT license. 

```

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

```javascript
/** @type {import('ts-jest').JestConfigWithTsJest} */
export default {
  preset: 'ts-jest',
  testEnvironment: 'node',
  extensionsToTreatAsEsm: ['.ts'],
  moduleNameMapper: {
    '^(\\.{1,2}/.*)\\.js$': '$1',
  },
  transform: {
    '^.+\\.tsx?$': [
      'ts-jest',
      {
        useESM: true,
      },
    ],
  },
};

```

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

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "outDir": "dist",
    "sourceMap": true,
    "declaration": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.test.ts"],
  "ts-node": {
    "esm": true,
    "experimentalSpecifierResolution": "node"
  }
}

```

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

```typescript
/**
 * Global type declarations for external modules
 */

declare module '@modelcontextprotocol/sdk/server/mcp.js' {
  export class McpServer {
    constructor(options?: { name?: string; version?: string });
    
    tool(
      name: string,
      description: string,
      parameters: Record<string, any>,
      handler: (args: any, extra: any) => any | Promise<any>
    ): void;
    
    connect(transport: any): Promise<void>;
  }
}

declare module '@modelcontextprotocol/sdk/server/stdio.js' {
  export class StdioServerTransport {
    constructor();
  }
} 

```

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

```dockerfile
FROM node:20-alpine AS builder

WORKDIR /app

# Copy package files and install dependencies
COPY package*.json ./
RUN npm ci

# Copy source code
COPY . .

# Build the application
RUN npm run build

# Production stage
FROM node:20-alpine

WORKDIR /app

# Copy package files and install production dependencies only
COPY package*.json ./
RUN npm ci --omit=dev

# Copy built application from builder stage
COPY --from=builder /app/dist ./dist

# Copy .env.example file
COPY .env.example .env.example

# Set environment variables
ENV NODE_ENV=production

# Expose port if needed (for health checks, etc.)
# EXPOSE 8080

# Start the application
CMD ["node", "dist/index.js"] 

```

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

```bash
#!/bin/bash

# Setup Development Environment for Meilisearch MCP Server

# Create .env file if it doesn't exist
if [ ! -f .env ]; then
  echo "Creating .env file from .env.example..."
  cp .env.example .env
  echo "Done! Please edit .env file with your Meilisearch configuration."
else
  echo ".env file already exists."
fi

# Install dependencies
echo "Installing dependencies..."
npm install

# Build the project
echo "Building the project..."
npm run build

# Run tests
echo "Running tests..."
if npm test; then
  echo "All tests passed!"
else
  echo "Warning: Some tests failed. You may need to fix them before proceeding."
  echo "You can continue with development, but be aware that some functionality may not work as expected."
fi

echo "Development environment setup complete!"
echo "To start the server in development mode, run: npm run dev" 

```

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

```yaml
version: '3.8'

services:
  meilisearch:
    image: getmeili/meilisearch:latest
    container_name: meilisearch
    environment:
      - MEILI_MASTER_KEY=${MEILI_MASTER_KEY:-masterKey}
      - MEILI_NO_ANALYTICS=true
      - MEILI_ENV=development
    ports:
      - '7700:7700'
    volumes:
      - meilisearch_data:/meili_data
    restart: unless-stopped
    networks:
      - meilisearch-network

  meilisearch-ts-mcp:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: meilisearch-ts-mcp
    environment:
      - MEILISEARCH_HOST=http://meilisearch:7700
      - MEILISEARCH_API_KEY=${MEILI_MASTER_KEY:-masterKey}
      - MEILISEARCH_TIMEOUT=5000
    depends_on:
      - meilisearch
    networks:
      - meilisearch-network

volumes:
  meilisearch_data:
    driver: local

networks:
  meilisearch-network:
    driver: bridge

```

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

```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml

startCommand:
  type: stdio
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    properties:
      MEILISEARCH_HOST:
        type: string
        default: http://localhost:7700
        description: URL for the Meilisearch instance
      MEILISEARCH_API_KEY:
        type: string
        default: ""
        description: API key for Meilisearch, if required
  commandFunction:
    # A function that produces the CLI command to start the MCP on stdio.
    |-
    (config) => ({
      command: 'node',
      args: ['dist/index.js'],
      env: {
        NODE_ENV: 'production',
        MEILISEARCH_HOST: config.MEILISEARCH_HOST,
        MEILISEARCH_API_KEY: config.MEILISEARCH_API_KEY
      }
    })
  exampleConfig:
    MEILISEARCH_HOST: http://localhost:7700
    MEILISEARCH_API_KEY: your-api-key

```

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

```typescript
/**
 * Meilisearch MCP Server Configuration
 * 
 * This file contains the configuration settings for connecting to the Meilisearch server.
 * Configuration is loaded from environment variables with sensible defaults.
 */

// Server configuration interface
export interface ServerConfig {
  /** The URL of the Meilisearch instance */
  host: string;
  /** The API key for authenticating with Meilisearch */
  apiKey: string;
  /** The timeout for API requests in milliseconds */
  timeout: number;
}

/**
 * Load and initialize configuration from environment variables
 */
export const loadConfig = (): ServerConfig => {
  return {
    host: process.env.MEILISEARCH_HOST || "http://localhost:7700",
    apiKey: process.env.MEILISEARCH_API_KEY || "",
    timeout: parseInt(process.env.MEILISEARCH_TIMEOUT || "5000", 10),
  };
};

// Export the config instance
export const config = loadConfig();

// Re-export for direct use
export default config; 

```

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

```json
{
  "name": "meilisearch-ts-mcp",
  "version": "0.1.0",
  "description": "Meilisearch MCP Server (Typescript) - Model Context Protocol implementation for Meilisearch",
  "main": "dist/index.js",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsx src/index.ts",
    "lint": "eslint src/**/*.ts",
    "test": "jest"
  },
  "keywords": [
    "meilisearch",
    "mcp",
    "search",
    "model-context-protocol"
  ],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.6.0",
    "axios": "^1.6.2",
    "dotenv": "^16.3.1",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@types/jest": "^29.5.10",
    "@types/node": "^20.10.0",
    "@typescript-eslint/eslint-plugin": "^6.12.0",
    "@typescript-eslint/parser": "^6.12.0",
    "eslint": "^8.54.0",
    "jest": "^29.7.0",
    "ts-jest": "^29.1.1",
    "ts-node": "^10.9.1",
    "tsx": "^4.19.3",
    "typescript": "^5.3.2"
  }
}

```

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

```typescript
/**
 * Error handling utilities for Meilisearch API responses
 */


/**
 * Formats Meilisearch API errors for consistent error messaging
 * 
 * @param error - The error from the API request
 * @returns A formatted error message
 */
export const handleApiError = (error: any): string => {
  // If it's an Axios error with a response
  if (error.isAxiosError && error.response) {
    const { status, data } = error.response;
    // Return formatted error with status code and response data
    return `Meilisearch API error (${status}): ${JSON.stringify(data)}`;
  }
  
  // If it's a network error or other error
  return `Error connecting to Meilisearch: ${error.message}`;
};

/**
 * Creates a standardized error response object for MCP tools
 * 
 * @param error - The error from the API request
 * @returns An MCP tool response object with error flag
 */
export const createErrorResponse = (error: any) => {
  return {
    isError: true,
    content: [{ type: "text", text: handleApiError(error) }],
  };
};

export default {
  handleApiError,
  createErrorResponse,
}; 

```

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

```typescript
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';

import config from '../config.js';

/**
 * Meilisearch API client
 * 
 * This module provides a configured Axios instance for making requests to the Meilisearch API.
 */

/**
 * Creates a configured Axios instance for Meilisearch API requests
 * 
 * @returns An Axios instance with base configuration
 */
export const createApiClient = () => {
  const instance = axios.create({
    baseURL: config.host,
    headers: {
      Authorization: `Bearer ${config.apiKey}`,
      'Content-Type': 'application/json',
    },
    timeout: config.timeout,
  });

  return {
    get: <T = any>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> => 
      instance.get(url, config),
    post: <T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> => 
      instance.post(url, data, config),
    put: <T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> => 
      instance.put(url, data, config),
    patch: <T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> => 
      instance.patch(url, data, config),
    delete: <T = any>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> => 
      instance.delete(url, config),
  };
};

// Create and export a singleton instance of the API client
export const apiClient = createApiClient();

// Re-export for direct use
export default apiClient; 

```

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

```typescript
/**
 * API Client Tests
 * 
 * This file contains tests for the API client utility.
 */


// Mock the API client
jest.mock('../utils/api-client', () => {
  const mockGet = jest.fn();
  const mockPost = jest.fn();
  const mockPut = jest.fn();
  const mockPatch = jest.fn();
  const mockDelete = jest.fn();
  
  return {
    createApiClient: jest.fn(() => ({
      get: mockGet,
      post: mockPost,
      put: mockPut,
      patch: mockPatch,
      delete: mockDelete
    })),
    apiClient: {
      get: mockGet,
      post: mockPost,
      put: mockPut,
      patch: mockPatch,
      delete: mockDelete
    },
    __esModule: true,
    default: {
      get: mockGet,
      post: mockPost,
      put: mockPut,
      patch: mockPatch,
      delete: mockDelete
    }
  };
});

// Get the mocked functions
const { apiClient } = require('../utils/api-client');
const mockGet = apiClient.get;
const mockPost = apiClient.post;

describe('API Client', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('should make GET requests correctly', async () => {
    // Setup
    mockGet.mockResolvedValueOnce({ data: { result: 'success' } });

    // Execute
    await apiClient.get('/test-endpoint');

    // Verify
    expect(mockGet).toHaveBeenCalledWith('/test-endpoint');
  });

  it('should include configuration when provided', async () => {
    // Setup
    mockGet.mockResolvedValueOnce({ data: { result: 'success' } });
    const config = { params: { filter: 'test' } };

    // Execute
    await apiClient.get('/test-endpoint', config);

    // Verify
    expect(mockGet).toHaveBeenCalledWith('/test-endpoint', config);
  });

  it('should handle errors appropriately', async () => {
    // Setup
    const errorResponse = {
      response: {
        status: 404,
        data: { message: 'Not found' }
      }
    };
    mockGet.mockRejectedValueOnce(errorResponse);

    // Execute & Verify
    await expect(apiClient.get('/non-existent')).rejects.toEqual(errorResponse);
  });
}); 

```

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

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import axios from 'axios';

import registerDocumentTools from './tools/document-tools.js';
import registerIndexTools from './tools/index-tools.js';
import registerSearchTools from './tools/search-tools.js';
import registerSettingsTools from './tools/settings-tools.js';
import registerSystemTools from './tools/system-tools.js';
import registerTaskTools from './tools/task-tools.js';
import registerVectorTools from './tools/vector-tools.js';

/**
 * Meilisearch MCP Server
 * 
 * This is the main entry point for the Meilisearch MCP server.
 * It integrates with Meilisearch to provide search capabilities to LLMs via the Model Context Protocol.
 */

// Import tool registration functions
// Server configuration
interface ServerConfig {
  host: string;
  apiKey: string;
}

// Initialize configuration from environment variables
const config: ServerConfig = {
  host: process.env.MEILISEARCH_HOST || "http://localhost:7700",
  apiKey: process.env.MEILISEARCH_API_KEY || "",
};

// Create Axios instance with base configuration
const api = axios.create({
  baseURL: config.host,
  headers: {
    Authorization: `Bearer ${config.apiKey}`,
    "Content-Type": "application/json",
  },
});

// Helper function to handle API errors
const handleApiError = (error: any): string => {
  if (error.response) {
    const { status, data } = error.response;
    return `Meilisearch API error (${status}): ${JSON.stringify(data)}`;
  }
  return `Error connecting to Meilisearch: ${error.message}`;
};

/**
 * Main function to initialize and start the MCP server
 */
async function main() {
  console.error("Starting Meilisearch MCP Server...");
  
  // Create the MCP server instance
  const server = new McpServer({
    name: "meilisearch",
    version: "1.0.0",
  });
  
  // Register all tools
  registerIndexTools(server);
  registerDocumentTools(server);
  registerSearchTools(server);
  registerSettingsTools(server);
  registerVectorTools(server);
  registerSystemTools(server);
  registerTaskTools(server);
  
  // Connect to the stdio transport
  const transport = new StdioServerTransport();
  await server.connect(transport);
  
  console.error("Meilisearch MCP Server is running on stdio transport");
}

// Start the server
main().catch(error => {
  console.error("Fatal error:", error);
  process.exit(1);
});

```

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

```markdown
# Meilisearch MCP Server Implementation Plan

## Overview
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).

## Project Structure

```
meilisearch-ts-mcp/
├── src/
│   ├── index.ts                 # Main entry point
│   ├── config.ts                # Server configuration
│   ├── tools/                   # MCP tools implementation
│   │   ├── index-tools.ts       # Index management tools
│   │   ├── document-tools.ts    # Document management tools
│   │   ├── search-tools.ts      # Search tools
│   │   ├── settings-tools.ts    # Settings management tools
│   │   ├── task-tools.ts        # Task management tools
│   │   ├── vector-tools.ts      # Vector search tools 
│   │   └── system-tools.ts      # System tools (health, stats, etc.)
│   └── utils/                   # Utility functions
│       ├── api-client.ts        # Meilisearch API client
│       └── error-handler.ts     # Error handling utilities
```

## Implementation Phases

### Phase 1: Project Setup and Refactoring
1. Create the directory structure
2. Extract configuration into config.ts
3. Create utility modules for API client and error handling
4. Refactor the existing monolithic implementation into separate modules

### Phase 2: Core Functionality Implementation
1. **Index Management Tools**
   - List indexes
   - Get index information
   - Create index
   - Update index
   - Delete index
   - Get index stats

2. **Document Management Tools**
   - Add/update documents
   - Get documents
   - Delete documents
   - Batch document operations

3. **Search Tools**
   - Basic search
   - Search with filters
   - Search with sorting
   - Faceted search

### Phase 3: Advanced Functionality
1. **Settings Management Tools**
   - Get index settings
   - Update settings
   - Reset settings
   - Configure specific settings (synonyms, stop words, etc.)

2. **Task Management Tools**
   - Get tasks
   - Get task by ID
   - Cancel tasks

3. **System Tools**
   - Health check
   - Version information
   - Stats

4. **Vector Search Tools**
   - Configure embedders
   - Perform vector search
   - Hybrid search
   - Vector search with filters

## Testing and Documentation
1. Create unit tests for each module
2. Add integration tests for end-to-end functionality
3. Update README with detailed usage instructions
4. Create example scripts and configurations

## Timeline
- Phase 1: 1 day
- Phase 2: 2-3 days
- Phase 3: 2-3 days
- Testing and Documentation: 1-2 days

Total estimated time: 6-9 days

## Dependencies
- @modelcontextprotocol/sdk: For MCP server implementation
- axios: For HTTP requests to Meilisearch API
- zod: For parameter validation

## Notes
- Vector search functionality requires Meilisearch to have vector search enabled as an experimental feature
- The implementation will be backward compatible with existing clients
- All tools will include detailed error handling and descriptive responses 

```

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

```javascript
import fs from 'fs';
import os from 'os';
import path from 'path';
import { fileURLToPath } from 'url';

/**
 * Claude for Desktop Setup Helper
 *
 * This script helps users set up the Meilisearch MCP server for use with Claude for Desktop.
 * It generates the necessary configuration and provides instructions.
 */

// Get the directory name
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const projectRoot = path.resolve(__dirname, '..');

// Get the absolute path to the built index.js file
const indexPath = path.resolve(projectRoot, 'dist', 'index.js');

// Get user's home directory
const homeDir = os.homedir();

// Default Claude for Desktop config path based on OS
let claudeConfigPath;
if (process.platform === 'darwin') {
  claudeConfigPath = path.join(
    homeDir,
    'Library',
    'Application Support',
    'Claude',
    'claude_desktop_config.json'
  );
} else if (process.platform === 'win32') {
  claudeConfigPath = path.join(
    homeDir,
    'AppData',
    'Roaming',
    'Claude',
    'claude_desktop_config.json'
  );
} else if (process.platform === 'linux') {
  claudeConfigPath = path.join(
    homeDir,
    '.config',
    'Claude',
    'claude_desktop_config.json'
  );
} else {
  console.error(
    'Unsupported platform. Please manually configure Claude for Desktop.'
  );
  process.exit(1);
}

// Read environment variables from .env file
let meilisearchHost = process.env.MEILISEARCH_HOST;
let meilisearchApiKey = process.env.MEILISEARCH_API_KEY;

try {
  const envPath = path.resolve(projectRoot, '.env');
  if (fs.existsSync(envPath)) {
    const envContent = fs.readFileSync(envPath, 'utf8');
    const envLines = envContent.split('\n');

    for (const line of envLines) {
      if (line.trim() && !line.startsWith('#')) {
        const [key, value] = line.split('=');
        if (key === 'MEILISEARCH_HOST' && value) {
          meilisearchHost = value.trim();
        } else if (key === 'MEILISEARCH_API_KEY' && value) {
          meilisearchApiKey = value.trim();
        }
      }
    }
  }
} catch (error) {
  console.warn('Could not read .env file:', error.message);
}

// Generate Claude for Desktop configuration
const claudeConfig = {
  mcpServers: {
    meilisearch: {
      command: 'node',
      args: [indexPath],
      env: {
        MEILISEARCH_HOST: meilisearchHost,
        MEILISEARCH_API_KEY: meilisearchApiKey,
      },
    },
  },
};

// Check if Claude config file exists
let existingConfig = {};
try {
  if (fs.existsSync(claudeConfigPath)) {
    const configContent = fs.readFileSync(claudeConfigPath, 'utf8');
    existingConfig = JSON.parse(configContent);
    console.log('Found existing Claude for Desktop configuration.');
  }
} catch (error) {
  console.warn(
    'Could not read existing Claude for Desktop configuration:',
    error.message
  );
}

// Merge configurations
const mergedConfig = {
  ...existingConfig,
  mcpServers: {
    ...(existingConfig.mcpServers || {}),
    ...claudeConfig.mcpServers,
  },
};

// Output the configuration
console.log('\n=== Claude for Desktop Configuration ===\n');
console.log(JSON.stringify(mergedConfig, null, 2));
console.log('\n');

// Ask if user wants to update the configuration
console.log(
  'To use this configuration with Claude for Desktop, you can either:'
);
console.log(
  `1. Manually update your configuration file at: ${claudeConfigPath}`
);
console.log('2. Run the following command to automatically update it:');
console.log(
  `\n   node -e "require('fs').writeFileSync('${claudeConfigPath.replace(
    /\\/g,
    '\\\\'
  )}', JSON.stringify(${JSON.stringify(mergedConfig)}, null, 2))"\n`
);
console.log(
  'After updating the configuration, restart Claude for Desktop to apply the changes.'
);
console.log(
  '\nYou can then use the Meilisearch MCP server with Claude by typing: "I want to use the Meilisearch MCP server."'
);

```

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

```markdown
# Vector Search with Meilisearch MCP

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.

## Overview

Vector search in Meilisearch enables:
- Semantic search based on the meaning of content
- Similar document recommendations
- Hybrid search combining keyword and semantic results
- Multi-modal search experiences

## Enabling Vector Search

Vector search is an experimental feature in Meilisearch. Before using it, you must enable it:

```
# Enable vector search experimental feature
enable-vector-search
```

## Setting Up Vector Search

### 1. Configure Embedders

First, configure an embedder for your index:

```
# Example: Configure OpenAI embedder
update-embedders {
  "indexUid": "my-index",
  "embedders": {
    "openai-embedder": {
      "source": "openAi",
      "model": "text-embedding-3-small",
      "dimensions": 1536
    }
  }
}
```

Common embedder sources include:
- `openAi` - OpenAI embeddings
- `huggingFace` - HuggingFace models
- `ollama` - Ollama local models
- `rest` - Custom REST API endpoint
- `userProvided` - Pre-computed embeddings

### 2. Add Documents with Vectors

You can add documents with pre-computed vectors:

```
# Add documents with vector embeddings
add-documents {
  "indexUid": "my-index",
  "documents": [
    {
      "id": "1",
      "title": "Vector search guide",
      "content": "This is about vector search...",
      "_vectors": {
        "openai-embedder": [0.123, 0.456, ...]
      }
    }
  ]
}
```

Alternatively, if you've configured an embedder, Meilisearch can generate the embeddings automatically from your text fields.

## Performing Vector Searches

### Basic Vector Search

If you have a vector representation of your query:

```
# Vector search
search {
  "indexUid": "my-index",
  "vector": [0.123, 0.456, ...],
  "limit": 10
}
```

### Hybrid Search

Combine traditional keyword search with vector search:

```
# Hybrid search
search {
  "indexUid": "my-index",
  "q": "machine learning techniques",
  "vector": [0.123, 0.456, ...],
  "hybridEmbedder": "openai-embedder",
  "hybridSemanticRatio": 0.7
}
```

The `hybridSemanticRatio` controls the balance between semantic (vector) and lexical (keyword) search:
- 0.0: Only keyword search
- 1.0: Only vector search
- 0.5: Equal weight to both

### Finding Similar Documents

Find documents similar to an existing document:

```
# Similar documents search
similar-documents {
  "indexUid": "my-index",
  "id": "doc123",
  "embedder": "openai-embedder",
  "limit": 5
}
```

## Multi-Index Vector Search

Perform vector searches across multiple indexes:

```
# Multi-index vector search
multi-search {
  "queries": [
    {
      "indexUid": "products", 
      "vector": [0.1, 0.2, ...],
      "hybridEmbedder": "openai-embedder",
      "limit": 5
    },
    {
      "indexUid": "articles",
      "vector": [0.1, 0.2, ...],
      "hybridEmbedder": "openai-embedder",
      "limit": 5
    }
  ],
  "federation": {
    "limit": 10
  }
}
```

## Best Practices

1. **Choose the right embedder**: Different models have different strengths and capabilities.

2. **Experiment with hybrid ratios**: The ideal balance between vector and keyword search depends on your content and use case.

3. **Pre-compute embeddings** when possible to improve indexing performance.

4. **Use filters** with vector search to constrain results to relevant subsets.

5. **Consider reranking** for critical applications to improve result quality.

## Potential Use Cases

- **Semantic code search**: Find code examples by describing functionality
- **Similar product recommendations**: "Show me products like this one"
- **Research document similarity**: Find related academic papers or reports
- **Natural language queries**: Search for concepts rather than exact keywords
- **Content discovery**: Find content with similar themes or topics

## Limitations

- Vector search is an experimental feature and may change in future Meilisearch releases
- Vector search performs best with larger datasets where semantic similarity matters
- Compute requirements increase with vector dimensions and dataset size

```

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

```javascript
import axios from 'axios';
import path from 'path';
import { fileURLToPath } from 'url';

/**
 * Meilisearch MCP Server - Movies Demo
 *
 * This script demonstrates how to use the Meilisearch MCP server with a sample movie dataset.
 * It creates an index, adds documents, configures settings, and performs searches.
 */

// Get the directory name
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Configuration
const MEILISEARCH_HOST =
  process.env.MEILISEARCH_HOST || 'http://localhost:7700';
const MEILISEARCH_API_KEY = process.env.MEILISEARCH_API_KEY || '';

// Create an axios instance for Meilisearch
const meilisearch = axios.create({
  baseURL: MEILISEARCH_HOST,
  headers: MEILISEARCH_API_KEY
    ? { Authorization: `Bearer ${MEILISEARCH_API_KEY}` }
    : {},
  timeout: 5000,
});

// Sample movie data
const movies = [
  {
    id: 1,
    title: 'The Shawshank Redemption',
    director: 'Frank Darabont',
    genres: ['Drama'],
    year: 1994,
    rating: 9.3,
  },
  {
    id: 2,
    title: 'The Godfather',
    director: 'Francis Ford Coppola',
    genres: ['Crime', 'Drama'],
    year: 1972,
    rating: 9.2,
  },
  {
    id: 3,
    title: 'The Dark Knight',
    director: 'Christopher Nolan',
    genres: ['Action', 'Crime', 'Drama'],
    year: 2008,
    rating: 9.0,
  },
  {
    id: 4,
    title: 'Pulp Fiction',
    director: 'Quentin Tarantino',
    genres: ['Crime', 'Drama'],
    year: 1994,
    rating: 8.9,
  },
  {
    id: 5,
    title: 'The Lord of the Rings: The Return of the King',
    director: 'Peter Jackson',
    genres: ['Action', 'Adventure', 'Drama'],
    year: 2003,
    rating: 8.9,
  },
];

/**
 * Create a movies index
 */
async function createMoviesIndex() {
  try {
    const response = await meilisearch.post('/indexes', {
      uid: 'movies',
      primaryKey: 'id',
    });
    console.log('Index created:', response.data);
    return response.data;
  } catch (error) {
    console.error(
      'Error creating index:',
      error.response?.data || error.message
    );
    throw error;
  }
}

/**
 * Add movies to the index
 */
async function addMovies() {
  try {
    const response = await meilisearch.post(
      '/indexes/movies/documents',
      movies
    );
    console.log('Movies added:', response.data);
    return response.data;
  } catch (error) {
    console.error(
      'Error adding movies:',
      error.response?.data || error.message
    );
    throw error;
  }
}

/**
 * Update index settings
 */
async function updateSettings() {
  try {
    const settings = {
      searchableAttributes: ['title', 'director', 'genres'],
      filterableAttributes: ['genres', 'year', 'rating'],
      sortableAttributes: ['year', 'rating'],
    };

    const response = await meilisearch.patch(
      '/indexes/movies/settings',
      settings
    );
    console.log('Settings updated:', response.data);
    return response.data;
  } catch (error) {
    console.error(
      'Error updating settings:',
      error.response?.data || error.message
    );
    throw error;
  }
}

/**
 * Search for movies
 */
async function searchMovies(query, filters = null) {
  try {
    const params = { q: query };
    if (filters) {
      params.filter = filters;
    }

    const response = await meilisearch.post('/indexes/movies/search', params);
    console.log(`Search results for "${query}":`, response.data.hits);
    return response.data;
  } catch (error) {
    console.error(
      'Error searching movies:',
      error.response?.data || error.message
    );
    throw error;
  }
}

/**
 * Wait for a task to complete
 */
async function waitForTask(taskId) {
  try {
    let task;
    do {
      const response = await meilisearch.get(`/tasks/${taskId}`);
      task = response.data;

      if (['succeeded', 'failed', 'canceled'].includes(task.status)) {
        break;
      }

      console.log(`Task ${taskId} is ${task.status}. Waiting...`);
      await new Promise((resolve) => setTimeout(resolve, 500));
    } while (true);

    console.log(`Task ${taskId} ${task.status}`);
    return task;
  } catch (error) {
    console.error(
      'Error waiting for task:',
      error.response?.data || error.message
    );
    throw error;
  }
}

/**
 * Run the demo
 */
async function runDemo() {
  try {
    console.log('Starting Meilisearch Movies Demo...');

    // Create index
    const createIndexTask = await createMoviesIndex();
    await waitForTask(createIndexTask.taskUid);

    // Add movies
    const addMoviesTask = await addMovies();
    await waitForTask(addMoviesTask.taskUid);

    // Update settings
    const updateSettingsTask = await updateSettings();
    await waitForTask(updateSettingsTask.taskUid);

    // Perform searches
    await searchMovies('dark');
    await searchMovies('', 'genres = Drama AND year > 2000');
    await searchMovies('', 'rating > 9');

    console.log('Demo completed successfully!');
  } catch (error) {
    console.error('Demo failed:', error);
  }
}

// Run the demo
runDemo();

```

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

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';

import apiClient from '../utils/api-client.js';
import { createErrorResponse } from '../utils/error-handler.js';

/**
 * Meilisearch Index Management Tools
 * 
 * This module implements MCP tools for managing Meilisearch indexes.
 */

// Define types for index parameters
interface ListIndexesParams {
  limit?: number;
  offset?: number;
}

interface GetIndexParams {
  indexUid: string;
}

interface CreateIndexParams {
  indexUid: string;
  primaryKey?: string;
}

interface UpdateIndexParams {
  indexUid: string;
  primaryKey: string;
}

interface DeleteIndexParams {
  indexUid: string;
}

interface SwapIndexesParams {
  indexes: string;
}

/**
 * Register index management tools with the MCP server
 * 
 * @param server - The MCP server instance
 */
export const registerIndexTools = (server: McpServer) => {
  // List all indexes
  server.tool(
    'list-indexes',
    'List all indexes in the Meilisearch instance',
    {
      limit: z.number().min(1).max(100).optional().describe('Maximum number of indexes to return'),
      offset: z.number().min(0).optional().describe('Number of indexes to skip'),
    },
    async ({ limit, offset }: ListIndexesParams) => {
      try {
        const response = await apiClient.get('/indexes', {
          params: {
            limit,
            offset,
          },
        });
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Get index information
  server.tool(
    'get-index',
    'Get information about a specific Meilisearch index',
    {
      indexUid: z.string().describe('Unique identifier of the index'),
    },
    async ({ indexUid }: GetIndexParams) => {
      try {
        const response = await apiClient.get(`/indexes/${indexUid}`);
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Create a new index
  server.tool(
    'create-index',
    'Create a new Meilisearch index',
    {
      indexUid: z.string().describe('Unique identifier for the new index'),
      primaryKey: z.string().optional().describe('Primary key for the index'),
    },
    async ({ indexUid, primaryKey }: CreateIndexParams) => {
      try {
        const response = await apiClient.post('/indexes', {
          uid: indexUid,
          primaryKey,
        });
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Update an index
  server.tool(
    'update-index',
    'Update a Meilisearch index (currently only supports updating the primary key)',
    {
      indexUid: z.string().describe('Unique identifier of the index'),
      primaryKey: z.string().describe('New primary key for the index'),
    },
    async ({ indexUid, primaryKey }: UpdateIndexParams) => {
      try {
        const response = await apiClient.patch(`/indexes/${indexUid}`, {
          primaryKey,
        });
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Delete an index
  server.tool(
    'delete-index',
    'Delete a Meilisearch index',
    {
      indexUid: z.string().describe('Unique identifier of the index to delete'),
    },
    async ({ indexUid }: DeleteIndexParams) => {
      try {
        const response = await apiClient.delete(`/indexes/${indexUid}`);
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Swap indexes
  server.tool(
    'swap-indexes',
    'Swap two or more indexes in Meilisearch',
    {
      indexes: z.string().describe('JSON array of index pairs to swap, e.g. [["movies", "movies_new"]]'),
    },
    async ({ indexes }: SwapIndexesParams) => {
      try {
        // Parse the indexes string to ensure it's valid JSON
        const parsedIndexes = JSON.parse(indexes);
        
        // Ensure indexes is an array of arrays
        if (!Array.isArray(parsedIndexes) || !parsedIndexes.every(pair => Array.isArray(pair) && pair.length === 2)) {
          return {
            isError: true,
            content: [{ type: 'text', text: 'Indexes must be a JSON array of pairs, e.g. [["movies", "movies_new"]]' }],
          };
        }
        
        const response = await apiClient.post('/swap-indexes', parsedIndexes);
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );
};

export default registerIndexTools; 

```

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

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';

import apiClient from '../utils/api-client.js';
import { createErrorResponse } from '../utils/error-handler.js';

/**
 * Meilisearch Search Tools
 * 
 * This module implements MCP tools for searching in Meilisearch indexes.
 */

// Define types for search parameters
interface SearchParams {
  indexUid: string;
  q: string;
  limit?: number;
  offset?: number;
  filter?: string;
  sort?: string[];
  facets?: string[];
  attributesToRetrieve?: string[];
  attributesToCrop?: string[];
  cropLength?: number;
  attributesToHighlight?: string[];
  highlightPreTag?: string;
  highlightPostTag?: string;
  showMatchesPosition?: boolean;
  matchingStrategy?: string;
}

interface MultiSearchParams {
  searches: string;
}

/**
 * Register search tools with the MCP server
 * 
 * @param server - The MCP server instance
 */
export const registerSearchTools = (server: McpServer) => {
  // Search in an index
  server.tool(
    'search',
    'Search for documents in a Meilisearch index',
    {
      indexUid: z.string().describe('Unique identifier of the index'),
      q: z.string().describe('Search query'),
      limit: z.number().min(1).max(1000).optional().describe('Maximum number of results to return (default: 20)'),
      offset: z.number().min(0).optional().describe('Number of results to skip (default: 0)'),
      filter: z.string().optional().describe('Filter query to apply'),
      sort: z.array(z.string()).optional().describe('Attributes to sort by, e.g. ["price:asc"]'),
      facets: z.array(z.string()).optional().describe('Facets to return'),
      attributesToRetrieve: z.array(z.string()).optional().describe('Attributes to include in results'),
      attributesToCrop: z.array(z.string()).optional().describe('Attributes to crop'),
      cropLength: z.number().optional().describe('Length at which to crop cropped attributes'),
      attributesToHighlight: z.array(z.string()).optional().describe('Attributes to highlight'),
      highlightPreTag: z.string().optional().describe('Tag to insert before highlighted text'),
      highlightPostTag: z.string().optional().describe('Tag to insert after highlighted text'),
      showMatchesPosition: z.boolean().optional().describe('Whether to include match positions in results'),
      matchingStrategy: z.string().optional().describe("Matching strategy: 'all' or 'last'"),
    },
    async ({ 
      indexUid, 
      q, 
      limit, 
      offset, 
      filter, 
      sort, 
      facets, 
      attributesToRetrieve, 
      attributesToCrop, 
      cropLength, 
      attributesToHighlight, 
      highlightPreTag, 
      highlightPostTag, 
      showMatchesPosition, 
      matchingStrategy 
    }: SearchParams) => {
      try {
        const response = await apiClient.post(`/indexes/${indexUid}/search`, {
          q,
          limit,
          offset,
          filter,
          sort,
          facets,
          attributesToRetrieve,
          attributesToCrop,
          cropLength,
          attributesToHighlight,
          highlightPreTag,
          highlightPostTag,
          showMatchesPosition,
          matchingStrategy,
        });
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Multi-search across multiple indexes
  server.tool(
    'multi-search',
    'Perform multiple searches in one request',
    {
      searches: z.string().describe('JSON array of search queries, each with indexUid and q fields'),
    },
    async ({ searches }: MultiSearchParams) => {
      try {
        // Parse the searches string to ensure it's valid JSON
        const parsedSearches = JSON.parse(searches);
        
        // Ensure searches is an array
        if (!Array.isArray(parsedSearches)) {
          return {
            isError: true,
            content: [{ type: 'text', text: 'Searches must be a JSON array' }],
          };
        }
        
        // Ensure each search has at least indexUid
        for (const search of parsedSearches) {
          if (!search.indexUid) {
            return {
              isError: true,
              content: [{ type: 'text', text: 'Each search must have an indexUid field' }],
            };
          }
        }
        
        const response = await apiClient.post('/multi-search', {
          queries: parsedSearches,
        });
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Facet search
  server.tool(
    'facet-search',
    'Search for facet values matching specific criteria',
    {
      indexUid: z.string().describe('Unique identifier of the index'),
      facetName: z.string().describe('Name of the facet to search'),
      facetQuery: z.string().optional().describe('Query to match against facet values'),
      filter: z.string().optional().describe('Filter to apply to the base search'),
    },
    async ({ indexUid, facetName, facetQuery, filter }) => {
      try {
        const params: Record<string, any> = {
          facetName,
        };
        
        if (facetQuery !== undefined) params.facetQuery = facetQuery;
        if (filter) params.filter = filter;
        
        const response = await apiClient.post(`/indexes/${indexUid}/facet-search`, params);
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );
};

export default registerSearchTools; 

```

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

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';

import apiClient from '../utils/api-client.js';
import { createErrorResponse } from '../utils/error-handler.js';

/**
 * Meilisearch System Tools
 * 
 * This module implements MCP tools for system operations in Meilisearch.
 */

/**
 * Register system tools with the MCP server
 * 
 * @param server - The MCP server instance
 */
export const registerSystemTools = (server: McpServer) => {
  // Get health status
  server.tool(
    'health',
    'Check if the Meilisearch server is healthy',
    {},
    async () => {
      try {
        const response = await apiClient.get('/health');
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Get version information
  server.tool(
    'version',
    'Get the version information of the Meilisearch server',
    {},
    async () => {
      try {
        const response = await apiClient.get('/version');
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Get system information
  server.tool(
    'info',
    'Get the system information of the Meilisearch server',
    {},
    async () => {
      try {
        const response = await apiClient.get('/');
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Get statistics
  server.tool(
    'stats',
    'Get statistics about all indexes or a specific index',
    {
      indexUid: z.string().optional().describe('Unique identifier of the index (optional, if not provided stats for all indexes will be returned)'),
    },
    async ({ indexUid }) => {
      try {
        const endpoint = indexUid ? `/indexes/${indexUid}/stats` : '/stats';
        const response = await apiClient.get(endpoint);
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Get all tasks (with optional filtering)
  server.tool(
    'get-tasks',
    'Get information about tasks with optional filtering',
    {
      limit: z.number().min(0).optional().describe('Maximum number of tasks to return'),
      from: z.number().min(0).optional().describe('Task uid from which to start fetching'),
      status: z.enum(['enqueued', 'processing', 'succeeded', 'failed', 'canceled']).optional().describe('Status of tasks to return'),
      type: z.enum(['indexCreation', 'indexUpdate', 'indexDeletion', 'documentAddition', 'documentUpdate', 'documentDeletion', 'settingsUpdate', 'dumpCreation', 'taskCancelation']).optional().describe('Type of tasks to return'),
      indexUids: z.array(z.string()).optional().describe('UIDs of the indexes on which tasks were performed'),
    },
    async ({ limit, from, status, type, indexUids }) => {
      try {
        const params: Record<string, any> = {};
        if (limit !== undefined) params.limit = limit;
        if (from !== undefined) params.from = from;
        if (status) params.status = status;
        if (type) params.type = type;
        if (indexUids && indexUids.length > 0) params.indexUids = indexUids.join(',');
        
        const response = await apiClient.get('/tasks', { params });
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Delete tasks
  server.tool(
    'delete-tasks',
    'Delete tasks based on provided filters',
    {
      statuses: z.array(z.enum(['succeeded', 'failed', 'canceled'])).optional().describe('Statuses of tasks to delete'),
      types: z.array(z.enum(['indexCreation', 'indexUpdate', 'indexDeletion', 'documentAddition', 'documentUpdate', 'documentDeletion', 'settingsUpdate', 'dumpCreation', 'taskCancelation'])).optional().describe('Types of tasks to delete'),
      indexUids: z.array(z.string()).optional().describe('UIDs of the indexes on which tasks to delete were performed'),
      uids: z.array(z.number()).optional().describe('UIDs of the tasks to delete'),
      canceledBy: z.array(z.number()).optional().describe('UIDs of the tasks that canceled tasks to delete'),
      beforeUid: z.number().optional().describe('Delete tasks whose uid is before this value'),
      beforeStartedAt: z.string().optional().describe('Delete tasks that started processing before this date (ISO 8601 format)'),
      beforeFinishedAt: z.string().optional().describe('Delete tasks that finished processing before this date (ISO 8601 format)'),
    },
    async ({ statuses, types, indexUids, uids, canceledBy, beforeUid, beforeStartedAt, beforeFinishedAt }) => {
      try {
        const body: Record<string, any> = {};
        if (statuses && statuses.length > 0) body.statuses = statuses;
        if (types && types.length > 0) body.types = types;
        if (indexUids && indexUids.length > 0) body.indexUids = indexUids;
        if (uids && uids.length > 0) body.uids = uids;
        if (canceledBy && canceledBy.length > 0) body.canceledBy = canceledBy;
        if (beforeUid !== undefined) body.beforeUid = beforeUid;
        if (beforeStartedAt) body.beforeStartedAt = beforeStartedAt;
        if (beforeFinishedAt) body.beforeFinishedAt = beforeFinishedAt;
        
        const response = await apiClient.post('/tasks/delete', body);
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );
};

export default registerSystemTools; 

```

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

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';

import apiClient from '../utils/api-client.js';
import { createErrorResponse } from '../utils/error-handler.js';

/**
 * Meilisearch Task Management Tools
 * 
 * This module implements MCP tools for managing tasks in Meilisearch.
 */

// Define types for task parameters
interface ListTasksParams {
  limit?: number;
  from?: number;
  statuses?: string[];
  types?: string[];
  indexUids?: string[];
  uids?: number[];
}

interface GetTaskParams {
  taskUid: number;
}

interface CancelTasksParams {
  statuses?: string[];
  types?: string[];
  indexUids?: string[];
  uids?: number[];
}

interface WaitForTaskParams {
  taskUid: number;
  timeoutMs?: number;
  intervalMs?: number;
}

/**
 * Register task management tools with the MCP server
 * 
 * @param server - The MCP server instance
 */
export const registerTaskTools = (server: McpServer) => {
  // Get all tasks
  server.tool(
    "list-tasks",
    "List tasks with optional filtering",
    {
      limit: z.number().min(0).optional().describe("Maximum number of tasks to return"),
      from: z.number().min(0).optional().describe("Task uid from which to start fetching"),
      statuses: z.array(z.enum(["enqueued", "processing", "succeeded", "failed", "canceled"])).optional().describe("Statuses of tasks to return"),
      types: z.array(z.enum(["indexCreation", "indexUpdate", "indexDeletion", "documentAddition", "documentUpdate", "documentDeletion", "settingsUpdate", "dumpCreation", "taskCancelation"])).optional().describe("Types of tasks to return"),
      indexUids: z.array(z.string()).optional().describe("UIDs of the indexes on which tasks were performed"),
      uids: z.array(z.number()).optional().describe("UIDs of specific tasks to return"),
    },
    async ({ limit, from, statuses, types, indexUids, uids }: ListTasksParams) => {
      try {
        const params: Record<string, any> = {};
        if (limit !== undefined) params.limit = limit;
        if (from !== undefined) params.from = from;
        if (statuses && statuses.length > 0) params.statuses = statuses.join(',');
        if (types && types.length > 0) params.types = types.join(',');
        if (indexUids && indexUids.length > 0) params.indexUids = indexUids.join(',');
        if (uids && uids.length > 0) params.uids = uids.join(',');
        
        const response = await apiClient.get('/tasks', { params });
        return {
          content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Get a specific task
  server.tool(
    "get-task",
    "Get information about a specific task",
    {
      taskUid: z.number().describe("Unique identifier of the task"),
    },
    async ({ taskUid }: GetTaskParams) => {
      try {
        const response = await apiClient.get(`/tasks/${taskUid}`);
        return {
          content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Cancel tasks
  server.tool(
    "cancel-tasks",
    "Cancel tasks based on provided filters",
    {
      statuses: z.array(z.enum(["enqueued", "processing"])).optional().describe("Statuses of tasks to cancel"),
      types: z.array(z.enum(["indexCreation", "indexUpdate", "indexDeletion", "documentAddition", "documentUpdate", "documentDeletion", "settingsUpdate", "dumpCreation", "taskCancelation"])).optional().describe("Types of tasks to cancel"),
      indexUids: z.array(z.string()).optional().describe("UIDs of the indexes on which tasks to cancel were performed"),
      uids: z.array(z.number()).optional().describe("UIDs of the tasks to cancel"),
    },
    async ({ statuses, types, indexUids, uids }: CancelTasksParams) => {
      try {
        const body: Record<string, any> = {};
        if (statuses && statuses.length > 0) body.statuses = statuses;
        if (types && types.length > 0) body.types = types;
        if (indexUids && indexUids.length > 0) body.indexUids = indexUids;
        if (uids && uids.length > 0) body.uids = uids;
        
        const response = await apiClient.post('/tasks/cancel', body);
        return {
          content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Wait for a task to complete
  server.tool(
    "wait-for-task",
    "Wait for a specific task to complete",
    {
      taskUid: z.number().describe("Unique identifier of the task to wait for"),
      timeoutMs: z.number().min(0).optional().describe("Maximum time to wait in milliseconds (default: 5000)"),
      intervalMs: z.number().min(100).optional().describe("Polling interval in milliseconds (default: 500)"),
    },
    async ({ taskUid, timeoutMs = 5000, intervalMs = 500 }: WaitForTaskParams) => {
      try {
        const startTime = Date.now();
        let taskCompleted = false;
        let taskData = null;
        
        while (!taskCompleted && (Date.now() - startTime < timeoutMs)) {
          // Fetch the current task status
          const response = await apiClient.get(`/tasks/${taskUid}`);
          taskData = response.data;
          
          // Check if the task has completed
          if (["succeeded", "failed", "canceled"].includes(taskData.status)) {
            taskCompleted = true;
          } else {
            // Wait for the polling interval
            await new Promise(resolve => setTimeout(resolve, intervalMs));
          }
        }
        
        if (!taskCompleted) {
          return {
            content: [{ type: "text", text: `Task ${taskUid} did not complete within the timeout period of ${timeoutMs}ms` }],
          };
        }
        
        return {
          content: [{ type: "text", text: JSON.stringify(taskData, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );
};

export default registerTaskTools; 

```

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

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';

import apiClient from '../utils/api-client.js';
import { createErrorResponse } from '../utils/error-handler.js';

/**
 * Meilisearch Vector Search Tools
 * 
 * This module implements MCP tools for vector search capabilities in Meilisearch.
 * Note: Vector search is an experimental feature in Meilisearch.
 */

/**
 * Register vector search tools with the MCP server
 * 
 * @param server - The MCP server instance
 */
export const registerVectorTools = (server: McpServer) => {
  // Enable vector search experimental feature
  server.tool(
    "enable-vector-search",
    "Enable the vector search experimental feature in Meilisearch",
    {},
    async () => {
      try {
        const response = await apiClient.post('/experimental-features', {
          vectorStore: true,
        });
        return {
          content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Get experimental features status
  server.tool(
    "get-experimental-features",
    "Get the status of experimental features in Meilisearch",
    {},
    async () => {
      try {
        const response = await apiClient.get('/experimental-features');
        return {
          content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Update embedders configuration
  server.tool(
    "update-embedders",
    "Configure embedders for vector search",
    {
      indexUid: z.string().describe("Unique identifier of the index"),
      embedders: z.string().describe("JSON object containing embedder configurations"),
    },
    async ({ indexUid, embedders }) => {
      try {
        // Parse the embedders string to ensure it's valid JSON
        const parsedEmbedders = JSON.parse(embedders);
        
        // Ensure embedders is an object
        if (typeof parsedEmbedders !== 'object' || parsedEmbedders === null || Array.isArray(parsedEmbedders)) {
          return {
            isError: true,
            content: [{ type: "text", text: "Embedders must be a JSON object" }],
          };
        }
        
        const response = await apiClient.patch(`/indexes/${indexUid}/settings/embedders`, parsedEmbedders);
        return {
          content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Get embedders configuration
  server.tool(
    "get-embedders",
    "Get the embedders configuration for an index",
    {
      indexUid: z.string().describe("Unique identifier of the index"),
    },
    async ({ indexUid }) => {
      try {
        const response = await apiClient.get(`/indexes/${indexUid}/settings/embedders`);
        return {
          content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Reset embedders configuration
  server.tool(
    "reset-embedders",
    "Reset the embedders configuration for an index",
    {
      indexUid: z.string().describe("Unique identifier of the index"),
    },
    async ({ indexUid }) => {
      try {
        const response = await apiClient.delete(`/indexes/${indexUid}/settings/embedders`);
        return {
          content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Perform vector search
  server.tool(
    "vector-search",
    "Perform a vector search in a Meilisearch index",
    {
      indexUid: z.string().describe("Unique identifier of the index"),
      vector: z.string().describe("JSON array representing the vector to search for"),
      limit: z.number().min(1).max(1000).optional().describe("Maximum number of results to return (default: 20)"),
      offset: z.number().min(0).optional().describe("Number of results to skip (default: 0)"),
      filter: z.string().optional().describe("Filter to apply (e.g., 'genre = horror AND year > 2020')"),
      embedder: z.string().optional().describe("Name of the embedder to use (if omitted, a 'vector' must be provided)"),
      attributes: z.array(z.string()).optional().describe("Attributes to include in the vector search"),
      query: z.string().optional().describe("Text query to search for (if using 'embedder' instead of 'vector')"),
      hybrid: z.boolean().optional().describe("Whether to perform a hybrid search (combining vector and text search)"),
      hybridRatio: z.number().min(0).max(1).optional().describe("Ratio of vector vs text search in hybrid search (0-1, default: 0.5)"),
    },
    async ({ indexUid, vector, limit, offset, filter, embedder, attributes, query, hybrid, hybridRatio }) => {
      try {
        const searchParams: Record<string, any> = {};
        
        // Add required vector parameter
        if (vector) {
          try {
            searchParams.vector = JSON.parse(vector);
          } catch (parseError) {
            return {
              isError: true,
              content: [{ type: "text", text: "Vector must be a valid JSON array" }],
            };
          }
        }
        
        // Add embedder parameters
        if (embedder) {
          searchParams.embedder = embedder;
          
          if (query !== undefined) {
            searchParams.q = query;
          }
        }
        
        // Ensure we have either vector or (embedder + query)
        if (!vector && (!embedder || query === undefined)) {
          return {
            isError: true,
            content: [{ type: "text", text: "Either 'vector' or both 'embedder' and 'query' must be provided" }],
          };
        }
        
        // Add optional parameters
        if (limit !== undefined) searchParams.limit = limit;
        if (offset !== undefined) searchParams.offset = offset;
        if (filter) searchParams.filter = filter;
        if (attributes?.length) searchParams.attributes = attributes;
        if (hybrid !== undefined) searchParams.hybrid = hybrid;
        if (hybridRatio !== undefined) searchParams.hybridRatio = hybridRatio;
        
        const response = await apiClient.post(`/indexes/${indexUid}/search`, searchParams);
        return {
          content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );
};

export default registerVectorTools; 

```

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

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';

import apiClient from '../utils/api-client.js';
import { createErrorResponse } from '../utils/error-handler.js';

/**
 * Meilisearch Document Management Tools
 * 
 * This module implements MCP tools for managing documents in Meilisearch indexes.
 */

// Define types for document parameters
interface GetDocumentsParams {
  indexUid: string;
  limit?: number;
  offset?: number;
  fields?: string[];
  filter?: string;
}

interface GetDocumentParams {
  indexUid: string;
  documentId: string;
  fields?: string[];
}

interface AddDocumentsParams {
  indexUid: string;
  documents: string;
  primaryKey?: string;
}

interface UpdateDocumentsParams {
  indexUid: string;
  documents: string;
  primaryKey?: string;
}

interface DeleteDocumentParams {
  indexUid: string;
  documentId: string;
}

interface DeleteDocumentsParams {
  indexUid: string;
  documentIds: string;
}

interface DeleteAllDocumentsParams {
  indexUid: string;
}

/**
 * Register document management tools with the MCP server
 * 
 * @param server - The MCP server instance
 */
export const registerDocumentTools = (server: McpServer) => {
  // Get documents from an index
  server.tool(
    'get-documents',
    'Get documents from a Meilisearch index',
    {
      indexUid: z.string().describe('Unique identifier of the index'),
      limit: z.number().min(1).max(1000).optional().describe('Maximum number of documents to return (default: 20)'),
      offset: z.number().min(0).optional().describe('Number of documents to skip (default: 0)'),
      fields: z.array(z.string()).optional().describe('Fields to return in the documents'),
      filter: z.string().optional().describe('Filter query to apply'),
    },
    async ({ indexUid, limit, offset, fields, filter }: GetDocumentsParams) => {
      try {
        const response = await apiClient.get(`/indexes/${indexUid}/documents`, {
          params: {
            limit,
            offset,
            fields: fields?.join(','),
            filter,
          },
        });
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Get a single document by ID
  server.tool(
    'get-document',
    'Get a document by its ID from a Meilisearch index',
    {
      indexUid: z.string().describe('Unique identifier of the index'),
      documentId: z.string().describe('ID of the document to retrieve'),
      fields: z.array(z.string()).optional().describe('Fields to return in the document'),
    },
    async ({ indexUid, documentId, fields }: GetDocumentParams) => {
      try {
        const response = await apiClient.get(`/indexes/${indexUid}/documents/${documentId}`, {
          params: {
            fields: fields?.join(','),
          },
        });
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Add documents to an index
  server.tool(
    'add-documents',
    'Add documents to a Meilisearch index',
    {
      indexUid: z.string().describe('Unique identifier of the index'),
      documents: z.string().describe('JSON array of documents to add'),
      primaryKey: z.string().optional().describe('Primary key for the documents'),
    },
    async ({ indexUid, documents, primaryKey }: AddDocumentsParams) => {
      try {
        // Parse the documents string to ensure it's valid JSON
        const parsedDocuments = JSON.parse(documents);
        
        // Ensure documents is an array
        if (!Array.isArray(parsedDocuments)) {
          return {
            isError: true,
            content: [{ type: 'text', text: 'Documents must be a JSON array' }],
          };
        }
        
        const params: Record<string, string> = {};
        if (primaryKey) {
          params.primaryKey = primaryKey;
        }
        
        const response = await apiClient.post(`/indexes/${indexUid}/documents`, parsedDocuments, {
          params,
        });
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Update documents in an index
  server.tool(
    'update-documents',
    'Update documents in a Meilisearch index',
    {
      indexUid: z.string().describe('Unique identifier of the index'),
      documents: z.string().describe('JSON array of documents to update'),
      primaryKey: z.string().optional().describe('Primary key for the documents'),
    },
    async ({ indexUid, documents, primaryKey }: UpdateDocumentsParams) => {
      try {
        // Parse the documents string to ensure it's valid JSON
        const parsedDocuments = JSON.parse(documents);
        
        // Ensure documents is an array
        if (!Array.isArray(parsedDocuments)) {
          return {
            isError: true,
            content: [{ type: 'text', text: 'Documents must be a JSON array' }],
          };
        }
        
        const params: Record<string, string> = {};
        if (primaryKey) {
          params.primaryKey = primaryKey;
        }
        
        const response = await apiClient.put(`/indexes/${indexUid}/documents`, parsedDocuments, {
          params,
        });
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Delete a document by ID
  server.tool(
    'delete-document',
    'Delete a document by its ID from a Meilisearch index',
    {
      indexUid: z.string().describe('Unique identifier of the index'),
      documentId: z.string().describe('ID of the document to delete'),
    },
    async ({ indexUid, documentId }: DeleteDocumentParams) => {
      try {
        const response = await apiClient.delete(`/indexes/${indexUid}/documents/${documentId}`);
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Delete multiple documents by ID
  server.tool(
    'delete-documents',
    'Delete multiple documents by their IDs from a Meilisearch index',
    {
      indexUid: z.string().describe('Unique identifier of the index'),
      documentIds: z.string().describe('JSON array of document IDs to delete'),
    },
    async ({ indexUid, documentIds }: DeleteDocumentsParams) => {
      try {
        // Parse the document IDs string to ensure it's valid JSON
        const parsedDocumentIds = JSON.parse(documentIds);
        
        // Ensure document IDs is an array
        if (!Array.isArray(parsedDocumentIds)) {
          return {
            isError: true,
            content: [{ type: 'text', text: 'Document IDs must be a JSON array' }],
          };
        }
        
        const response = await apiClient.post(`/indexes/${indexUid}/documents/delete-batch`, parsedDocumentIds);
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Delete all documents in an index
  server.tool(
    'delete-all-documents',
    'Delete all documents in a Meilisearch index',
    {
      indexUid: z.string().describe('Unique identifier of the index'),
    },
    async ({ indexUid }: DeleteAllDocumentsParams) => {
      try {
        const response = await apiClient.delete(`/indexes/${indexUid}/documents`);
        return {
          content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );
};

export default registerDocumentTools; 

```

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

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';

import apiClient from '../utils/api-client.js';
import { createErrorResponse } from '../utils/error-handler.js';

/**
 * Meilisearch Settings Management Tools
 * 
 * This module implements MCP tools for managing index settings in Meilisearch.
 */

/**
 * Register settings management tools with the MCP server
 * 
 * @param server - The MCP server instance
 */
export const registerSettingsTools = (server: McpServer) => {
  // Get all settings
  server.tool(
    "get-settings",
    "Get all settings for a Meilisearch index",
    {
      indexUid: z.string().describe("Unique identifier of the index"),
    },
    async ({ indexUid }) => {
      try {
        const response = await apiClient.get(`/indexes/${indexUid}/settings`);
        return {
          content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Update settings
  server.tool(
    "update-settings",
    "Update settings for a Meilisearch index",
    {
      indexUid: z.string().describe("Unique identifier of the index"),
      settings: z.string().describe("JSON object containing settings to update"),
    },
    async ({ indexUid, settings }) => {
      try {
        // Parse the settings string to ensure it's valid JSON
        const parsedSettings = JSON.parse(settings);
        
        // Ensure settings is an object
        if (typeof parsedSettings !== 'object' || parsedSettings === null || Array.isArray(parsedSettings)) {
          return {
            isError: true,
            content: [{ type: "text", text: "Settings must be a JSON object" }],
          };
        }
        
        const response = await apiClient.patch(`/indexes/${indexUid}/settings`, parsedSettings);
        return {
          content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Reset settings
  server.tool(
    "reset-settings",
    "Reset all settings for a Meilisearch index to their default values",
    {
      indexUid: z.string().describe("Unique identifier of the index"),
    },
    async ({ indexUid }) => {
      try {
        const response = await apiClient.delete(`/indexes/${indexUid}/settings`);
        return {
          content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
        };
      } catch (error) {
        return createErrorResponse(error);
      }
    }
  );

  // Get specific settings
  const specificSettingsTools = [
    {
      name: "get-searchable-attributes",
      endpoint: "searchable-attributes",
      description: "Get the searchable attributes setting",
    },
    {
      name: "get-displayed-attributes",
      endpoint: "displayed-attributes",
      description: "Get the displayed attributes setting",
    },
    {
      name: "get-filterable-attributes",
      endpoint: "filterable-attributes",
      description: "Get the filterable attributes setting",
    },
    {
      name: "get-sortable-attributes",
      endpoint: "sortable-attributes",
      description: "Get the sortable attributes setting",
    },
    {
      name: "get-ranking-rules",
      endpoint: "ranking-rules",
      description: "Get the ranking rules setting",
    },
    {
      name: "get-stop-words",
      endpoint: "stop-words",
      description: "Get the stop words setting",
    },
    {
      name: "get-synonyms",
      endpoint: "synonyms",
      description: "Get the synonyms setting",
    },
    {
      name: "get-distinct-attribute",
      endpoint: "distinct-attribute",
      description: "Get the distinct attribute setting",
    },
    {
      name: "get-typo-tolerance",
      endpoint: "typo-tolerance",
      description: "Get the typo tolerance setting",
    },
    {
      name: "get-faceting",
      endpoint: "faceting",
      description: "Get the faceting setting",
    },
    {
      name: "get-pagination",
      endpoint: "pagination",
      description: "Get the pagination setting",
    },
  ];

  // Create a tool for each specific setting
  specificSettingsTools.forEach(({ name, endpoint, description }) => {
    server.tool(
      name,
      description,
      {
        indexUid: z.string().describe("Unique identifier of the index"),
      },
      async ({ indexUid }) => {
        try {
          const response = await apiClient.get(`/indexes/${indexUid}/settings/${endpoint}`);
          return {
            content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
          };
        } catch (error) {
          return createErrorResponse(error);
        }
      }
    );
  });

  // Update specific settings
  const updateSettingsTools = [
    {
      name: "update-searchable-attributes",
      endpoint: "searchable-attributes",
      description: "Update the searchable attributes setting",
    },
    {
      name: "update-displayed-attributes",
      endpoint: "displayed-attributes",
      description: "Update the displayed attributes setting",
    },
    {
      name: "update-filterable-attributes",
      endpoint: "filterable-attributes",
      description: "Update the filterable attributes setting",
    },
    {
      name: "update-sortable-attributes",
      endpoint: "sortable-attributes",
      description: "Update the sortable attributes setting",
    },
    {
      name: "update-ranking-rules",
      endpoint: "ranking-rules",
      description: "Update the ranking rules setting",
    },
    {
      name: "update-stop-words",
      endpoint: "stop-words",
      description: "Update the stop words setting",
    },
    {
      name: "update-synonyms",
      endpoint: "synonyms",
      description: "Update the synonyms setting",
    },
    {
      name: "update-distinct-attribute",
      endpoint: "distinct-attribute",
      description: "Update the distinct attribute setting",
    },
    {
      name: "update-typo-tolerance",
      endpoint: "typo-tolerance",
      description: "Update the typo tolerance setting",
    },
    {
      name: "update-faceting",
      endpoint: "faceting",
      description: "Update the faceting setting",
    },
    {
      name: "update-pagination",
      endpoint: "pagination",
      description: "Update the pagination setting",
    },
  ];

  // Create an update tool for each specific setting
  updateSettingsTools.forEach(({ name, endpoint, description }) => {
    server.tool(
      name,
      description,
      {
        indexUid: z.string().describe("Unique identifier of the index"),
        value: z.string().describe("JSON value for the setting"),
      },
      async ({ indexUid, value }) => {
        try {
          // Parse the value string to ensure it's valid JSON
          const parsedValue = JSON.parse(value);
          
          const response = await apiClient.put(`/indexes/${indexUid}/settings/${endpoint}`, parsedValue);
          return {
            content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
          };
        } catch (error) {
          return createErrorResponse(error);
        }
      }
    );
  });

  // Reset specific settings
  const resetSettingsTools = [
    {
      name: "reset-searchable-attributes",
      endpoint: "searchable-attributes",
      description: "Reset the searchable attributes setting to its default value",
    },
    {
      name: "reset-displayed-attributes",
      endpoint: "displayed-attributes",
      description: "Reset the displayed attributes setting to its default value",
    },
    {
      name: "reset-filterable-attributes",
      endpoint: "filterable-attributes",
      description: "Reset the filterable attributes setting to its default value",
    },
    {
      name: "reset-sortable-attributes",
      endpoint: "sortable-attributes",
      description: "Reset the sortable attributes setting to its default value",
    },
    {
      name: "reset-ranking-rules",
      endpoint: "ranking-rules",
      description: "Reset the ranking rules setting to its default value",
    },
    {
      name: "reset-stop-words",
      endpoint: "stop-words",
      description: "Reset the stop words setting to its default value",
    },
    {
      name: "reset-synonyms",
      endpoint: "synonyms",
      description: "Reset the synonyms setting to its default value",
    },
    {
      name: "reset-distinct-attribute",
      endpoint: "distinct-attribute",
      description: "Reset the distinct attribute setting to its default value",
    },
    {
      name: "reset-typo-tolerance",
      endpoint: "typo-tolerance",
      description: "Reset the typo tolerance setting to its default value",
    },
    {
      name: "reset-faceting",
      endpoint: "faceting",
      description: "Reset the faceting setting to its default value",
    },
    {
      name: "reset-pagination",
      endpoint: "pagination",
      description: "Reset the pagination setting to its default value",
    },
  ];

  // Create a reset tool for each specific setting
  resetSettingsTools.forEach(({ name, endpoint, description }) => {
    server.tool(
      name,
      description,
      {
        indexUid: z.string().describe("Unique identifier of the index"),
      },
      async ({ indexUid }) => {
        try {
          const response = await apiClient.delete(`/indexes/${indexUid}/settings/${endpoint}`);
          return {
            content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
          };
        } catch (error) {
          return createErrorResponse(error);
        }
      }
    );
  });
};

export default registerSettingsTools; 

```
Page 1/2FirstPrevNextLast