Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Python cache
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# Virtual environments
.venv/
venv/
ENV/
env/

# Testing
.pytest_cache/
.coverage
htmlcov/
.tox/
.hypothesis/

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# Git
.git/
.gitignore

# Documentation
docs/
*.md
!README.md

# CI/CD
.github/

# Development files
.env
.env.*
CLAUDE.md

# Logs
logs/
*.log

# macOS
.DS_Store

# Test data
tests/
data.ms/
46 changes: 43 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Publish to PyPI
name: Publish to PyPI and Docker Hub

on:
push:
Expand All @@ -24,7 +24,7 @@ jobs:
echo "version_changed=true" >> $GITHUB_OUTPUT
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT

build-and-publish:
build-and-publish-pypi:
needs: check-version
if: needs.check-version.outputs.version_changed == 'true'
runs-on: ubuntu-latest
Expand Down Expand Up @@ -54,4 +54,44 @@ jobs:
uses: pypa/gh-action-pypi-publish@release/v1
with:
verbose: true
print-hash: true
print-hash: true

build-and-publish-docker:
needs: check-version
if: needs.check-version.outputs.version_changed == 'true'
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: meilisearch/meilisearch-mcp
tags: |
type=raw,value=${{ needs.check-version.outputs.new_version }}
type=raw,value=latest

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
28 changes: 28 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Use Python 3.12 slim image for smaller size
FROM python:3.12-slim

# Set working directory
WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*

# Install uv for faster Python package management
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
ENV PATH="/root/.local/bin:${PATH}"

# Copy project files
COPY pyproject.toml README.md ./
COPY src/ ./src/

# Install the package
RUN uv pip install --system .

# Set default environment variables
ENV MEILI_HTTP_ADDR=http://meilisearch:7700
ENV MEILI_MASTER_KEY=""

# Run the MCP server
CMD ["python", "-m", "src.meilisearch_mcp"]
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,58 @@ source .venv/bin/activate # On Windows: .venv\Scripts\activate
uv pip install -e .
```

### Using Docker

Perfect for containerized environments like n8n workflows!

#### From Docker Hub (Recommended)

```bash
# Pull the latest image
docker pull meilisearch/meilisearch-mcp:latest

# Or a specific version
docker pull meilisearch/meilisearch-mcp:0.5.0

# Run the container
docker run -it \
-e MEILI_HTTP_ADDR=http://your-meilisearch:7700 \
-e MEILI_MASTER_KEY=your-master-key \
meilisearch/meilisearch-mcp:latest
```

#### Using Docker Compose

```bash
# Clone the repository for docker-compose.yml
git clone https://github.com/meilisearch/meilisearch-mcp.git
cd meilisearch-mcp

# Start both Meilisearch and MCP server
docker-compose up -d
```

#### Build from Source

```bash
# Build your own image
docker build -t meilisearch-mcp .
docker run -it \
-e MEILI_HTTP_ADDR=http://your-meilisearch:7700 \
-e MEILI_MASTER_KEY=your-master-key \
meilisearch-mcp
```

For n8n integration, use the Docker image in your workflow:
```yaml
# Example n8n docker-compose service
meilisearch-mcp:
image: meilisearch-mcp:latest
environment:
- MEILI_HTTP_ADDR=http://meilisearch:7700
- MEILI_MASTER_KEY=masterKey
```

## 🛠️ What Can You Do?

<details>
Expand Down
33 changes: 33 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
services:
meilisearch:
image: getmeili/meilisearch:v1.6
ports:
- "7700:7700"
environment:
- MEILI_MASTER_KEY=masterKey
- MEILI_ENV=development
volumes:
- meilisearch_data:/meili_data
networks:
- meilisearch-network

meilisearch-mcp:
image: meilisearch/meilisearch-mcp:latest
# To build locally instead, comment the line above and uncomment:
# build: .
environment:
- MEILI_HTTP_ADDR=http://meilisearch:7700
- MEILI_MASTER_KEY=masterKey
depends_on:
- meilisearch
networks:
- meilisearch-network
stdin_open: true
tty: true

volumes:
meilisearch_data:

networks:
meilisearch-network:
driver: bridge
122 changes: 122 additions & 0 deletions tests/test_docker_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""
Integration tests for Docker setup.

These tests verify that the Docker container works correctly and can
communicate with Meilisearch.
"""
import os
import subprocess
import time
import pytest
import requests


def wait_for_service(url, timeout=30):
"""Wait for a service to become available."""
start_time = time.time()
while time.time() - start_time < timeout:
try:
response = requests.get(f"{url}/health")
if response.status_code == 200:
return True
except requests.exceptions.ConnectionError:
pass
time.sleep(1)
return False


@pytest.fixture(scope="module")
def docker_services():
"""Start Docker services for testing."""
# Start services
subprocess.run(["docker-compose", "up", "-d"], check=True)

# Wait for Meilisearch to be ready
if not wait_for_service("http://localhost:7700"):
subprocess.run(["docker-compose", "down"], check=True)
pytest.fail("Meilisearch failed to start")

yield

# Cleanup
subprocess.run(["docker-compose", "down", "-v"], check=True)


def test_docker_build():
"""Test that the Docker image can be built successfully."""
result = subprocess.run(
["docker", "build", "-t", "meilisearch-mcp-test", "."],
capture_output=True,
text=True
)
assert result.returncode == 0, f"Docker build failed: {result.stderr}"


def test_meilisearch_connectivity(docker_services):
"""Test that the MCP container can connect to Meilisearch."""
# Run a simple connectivity test in the container
result = subprocess.run(
[
"docker-compose", "run", "--rm", "meilisearch-mcp",
"python", "-c",
"""
import os
from meilisearch import Client
client = Client(os.getenv('MEILI_HTTP_ADDR'), os.getenv('MEILI_MASTER_KEY'))
health = client.health()
assert health['status'] == 'available'
print('SUCCESS: Connected to Meilisearch')
"""
],
capture_output=True,
text=True
)

assert result.returncode == 0, f"Connectivity test failed: {result.stderr}"
assert "SUCCESS: Connected to Meilisearch" in result.stdout


def test_mcp_server_import(docker_services):
"""Test that the MCP server module can be imported in the container."""
result = subprocess.run(
[
"docker-compose", "run", "--rm", "meilisearch-mcp",
"python", "-c",
"""
import src.meilisearch_mcp
from src.meilisearch_mcp.server import MeilisearchMCPServer
print('SUCCESS: MCP server imported')
"""
],
capture_output=True,
text=True
)

assert result.returncode == 0, f"Import test failed: {result.stderr}"
assert "SUCCESS: MCP server imported" in result.stdout


def test_environment_variables(docker_services):
"""Test that environment variables are correctly set in the container."""
result = subprocess.run(
[
"docker-compose", "run", "--rm", "meilisearch-mcp",
"python", "-c",
"""
import os
assert os.getenv('MEILI_HTTP_ADDR') == 'http://meilisearch:7700'
assert os.getenv('MEILI_MASTER_KEY') == 'masterKey'
print('SUCCESS: Environment variables are correct')
"""
],
capture_output=True,
text=True
)

assert result.returncode == 0, f"Environment test failed: {result.stderr}"
assert "SUCCESS: Environment variables are correct" in result.stdout


if __name__ == "__main__":
# Run tests
pytest.main([__file__, "-v"])
Loading