diff --git a/README.md b/README.md index 87b8e37..544c79f 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,12 @@ This server provides data retrieval capabilities powered by Chroma, enabling AI - Retrieve documents by IDs or filters - Full text search capabilities +- **Docker Support** + - Ready-to-use Docker container for Chroma MCP + - Docker Compose setup for Chroma and MCP Server + - Configurable via environment variables + - See the [Docker directory](docker/) for details + ### Supported Tools - `chroma_list_collections` - List all collections with pagination support @@ -184,4 +190,4 @@ export CHROMA_DOTENV_PATH="/path/to/your/.env" #### Embedding Function Environment Variables When using external embedding functions that access an API key, follow the naming convention `CHROMA_<>_API_KEY=""`. -So to set a Cohere API key, set the environment variable `CHROMA_COHERE_API_KEY=""`. We recommend adding this to a .env file somewhere and using the `CHROMA_DOTENV_PATH` environment variable or `--dotenv-path` flag to set that location for safekeeping. +So to set a Cohere API key, set the environment variable `CHROMA_COHERE_API_KEY=""`. We recommend adding this to a .env file somewhere and using the `CHROMA_DOTENV_PATH` environment variable or `--dotenv-path` flag to set that location for safekeeping. \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..32aef8d --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,37 @@ +FROM python:3.10-slim + +# Set working directory +WORKDIR /app + +# Install system dependencies +RUN apt-get update && apt-get install -y --no-install-recommends gcc + +# Install necessary Python libraries first +RUN pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir fastapi uvicorn fastmcp chromadb + +# Copy application files +COPY ./src /app/src +COPY ./pyproject.toml /app/ +COPY ./entrypoint.sh /app/ +COPY ./server.py /app/ + +# Install the package in development mode +RUN pip install -e . + +# Make the entrypoint script executable +RUN chmod +x /app/entrypoint.sh + +# Expose the port for the MCP server +EXPOSE 8080 + +# Set environment variables with defaults (can be overridden at runtime) +ENV CHROMA_CLIENT_TYPE=http +ENV CHROMA_HOST=chroma +ENV CHROMA_PORT=8000 +ENV CHROMA_SSL=false +ENV MCP_PORT=8080 +ENV MCP_HOST=0.0.0.0 + +# Run the entrypoint script +ENTRYPOINT ["/app/entrypoint.sh"] \ No newline at end of file diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..1d3b1c6 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,99 @@ +# Chroma MCP Server for Docker + +This implementation provides a properly configured Chroma MCP server designed to run in a Docker container, exposing HTTP endpoints for Claude to interact with the Chroma vector database. + +## 📋 Table of Contents + +- [Overview](#overview) +- [Implementation](#implementation) +- [Configuration](#configuration) +- [Usage](#usage) +- [Troubleshooting](#troubleshooting) + +## Overview + +This MCP server implementation connects to a Chroma vector database instance and exposes the Chroma functionality as MCP tools that Claude can use. It is specifically designed to work in a Docker container environment with proper network connectivity between services. + +## Implementation + +The implementation consists of several key components: + +1. **Dockerfile**: A custom Dockerfile that installs the necessary dependencies and configures the container environment. + +2. **Entrypoint Script**: A Bash script that starts the MCP server with the appropriate configuration. + +3. **Server Script**: A Python script that sets up the FastMCP instance with HTTP transport. + +4. **Docker Compose Configuration**: A configuration that connects the Chroma MCP server to the Chroma database. + +## Configuration + +The server is highly configurable through environment variables: + +### Chroma Connection + +- `CHROMA_CLIENT_TYPE`: Type of Chroma client to use (`http`, `cloud`, `persistent`, `ephemeral`) +- `CHROMA_HOST`: Hostname of the Chroma server +- `CHROMA_PORT`: Port of the Chroma server +- `CHROMA_SSL`: Whether to use SSL for Chroma connections (`true`/`false`) +- `CHROMA_DATA_DIR`: Directory for persistent data (only for persistent client type) + +### MCP Server + +- `MCP_HOST`: Host to bind the MCP server to +- `MCP_PORT`: Port to expose the MCP server on +- `MCP_TRANSPORT`: Transport mechanism for the MCP server (`http` or `stdio`) + +## Usage + +### With Docker Compose + +1. Use the provided `docker-compose.yml` file to start both Chroma and the Chroma MCP server: + +```bash +docker-compose up -d +``` + +2. Configure Claude to use the MCP server: + +```bash +claude mcp add chroma http://localhost:8080 +``` + +### Standalone Docker + +You can also run the container standalone: + +```bash +docker build -t chroma-mcp -f Dockerfile.new . +docker run -p 8080:8080 \ + -e CHROMA_CLIENT_TYPE=http \ + -e CHROMA_HOST=hostname \ + -e CHROMA_PORT=8000 \ + -e CHROMA_SSL=false \ + chroma-mcp +``` + +## Troubleshooting + +### Common Issues + +1. **Connection Refused**: Ensure the Chroma database is running and accessible from the MCP container. + +2. **MCP Server Not Starting**: Check the logs for any startup errors: + +```bash +docker logs chroma-mcp +``` + +3. **Claude Can't Connect**: Verify that the HTTP endpoint is accessible and properly configured in Claude: + +```bash +curl http://localhost:8080/health +``` + +--- + +🧭 [Home](../../../README.md) | [Up](../README.md) + +*Last updated: May 20, 2025* \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..4ccf6ce --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,40 @@ +services: + # Chroma Vector Database + chroma: + image: chromadb/chroma + container_name: chroma + restart: always + volumes: + - ${HOME}/chromadb_data:/chroma/chroma + ports: + - "8100:8000" + environment: + - ALLOW_RESET=true + networks: + - chroma-net + + # Chroma MCP Server + chroma-mcp: + build: + context: .claude/mcp-servers/simple-rag/chroma-mcp + dockerfile: Dockerfile.new + container_name: chroma-mcp + restart: always + depends_on: + - chroma + ports: + - "8080:8080" + environment: + - CHROMA_CLIENT_TYPE=http + - CHROMA_HOST=chroma + - CHROMA_PORT=8000 + - CHROMA_SSL=false + - MCP_HOST=0.0.0.0 + - MCP_PORT=8080 + - MCP_TRANSPORT=http + networks: + - chroma-net + +networks: + chroma-net: + driver: bridge \ No newline at end of file diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..2f25146 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -e + +# Print environment variables for debugging +echo "Starting Chroma MCP server with the following configuration:" +echo "CHROMA_CLIENT_TYPE: $CHROMA_CLIENT_TYPE" +echo "CHROMA_HOST: $CHROMA_HOST" +echo "CHROMA_PORT: $CHROMA_PORT" +echo "CHROMA_SSL: $CHROMA_SSL" +echo "MCP_PORT: $MCP_PORT" +echo "MCP_HOST: $MCP_HOST" + +# Check if server.py is accessible +if [ ! -f "/app/server.py" ]; then + echo "Error: server.py not found in /app directory" + ls -la /app + exit 1 +fi + +# Run the server +echo "Starting MCP server with HTTP transport..." +exec python /app/server.py \ + --client-type "$CHROMA_CLIENT_TYPE" \ + --host "$CHROMA_HOST" \ + --port "$CHROMA_PORT" \ + --ssl "$CHROMA_SSL" \ + --transport http \ + --mcp-host "$MCP_HOST" \ + --mcp-port "$MCP_PORT" \ No newline at end of file diff --git a/docker/server.py b/docker/server.py new file mode 100644 index 0000000..641df33 --- /dev/null +++ b/docker/server.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +""" +HTTP server wrapper for Chroma MCP. +This script starts a Chroma MCP server with HTTP transport. +""" + +import os +import sys +import argparse +import logging +from pathlib import Path +import importlib.util + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger("chroma-mcp") + +def get_parser(): + parser = argparse.ArgumentParser(description='Start Chroma MCP server with HTTP transport') + + # Chroma connection parameters + parser.add_argument('--client-type', + choices=['http', 'cloud', 'persistent', 'ephemeral'], + default=os.getenv('CHROMA_CLIENT_TYPE', 'http'), + help='Type of Chroma client to use') + parser.add_argument('--host', + help='Chroma host (required for http client)', + default=os.getenv('CHROMA_HOST', 'localhost')) + parser.add_argument('--port', + help='Chroma port (optional for http client)', + default=os.getenv('CHROMA_PORT', '8000')) + parser.add_argument('--ssl', + help='Use SSL (optional for http client)', + type=lambda x: x.lower() in ['true', 'yes', '1', 't', 'y'], + default=os.getenv('CHROMA_SSL', 'false').lower() in ['true', 'yes', '1', 't', 'y']) + + # MCP Server parameters + parser.add_argument('--transport', + choices=['stdio', 'http'], + default=os.getenv('MCP_TRANSPORT', 'http'), + help='Transport to use for MCP server') + parser.add_argument('--mcp-host', + help='Host to bind MCP server to', + default=os.getenv('MCP_HOST', '0.0.0.0')) + parser.add_argument('--mcp-port', + type=int, + help='Port to bind MCP server to', + default=int(os.getenv('MCP_PORT', '8080'))) + + # Advanced parameters + parser.add_argument('--data-dir', + default=os.getenv('CHROMA_DATA_DIR'), + help='Directory for persistent client data (only used with persistent client)') + parser.add_argument('--dotenv-path', + help='Path to .env file', + default=os.getenv('CHROMA_DOTENV_PATH', '.chroma_env')) + parser.add_argument('--debug', + action='store_true', + help='Enable debug logging') + + return parser + +async def start_mcp_server(): + """Start the MCP server with the configured transport.""" + parser = get_parser() + args = parser.parse_args() + + if args.debug: + logging.getLogger().setLevel(logging.DEBUG) + + # Import the Chroma MCP module + logger.info("Importing Chroma MCP module") + try: + # Try to import from source directory first + src_path = Path(__file__).parent / "src" / "chroma_mcp" / "server.py" + if src_path.exists(): + logger.info(f"Loading from source path: {src_path}") + spec = importlib.util.spec_from_file_location("chroma_mcp", src_path) + server_module = importlib.util.module_from_spec(spec) + sys.modules["chroma_mcp"] = server_module + spec.loader.exec_module(server_module) + else: + # Otherwise import from installed package + logger.info("Loading from installed package") + import chroma_mcp.server as server_module + + # Get the FastMCP instance and initialize the client + mcp = server_module.mcp + + # Prepare args for the client initialization + client_args = [ + "--client-type", args.client_type, + "--host", args.host, + "--port", args.port, + "--ssl", "true" if args.ssl else "false" + ] + + if args.data_dir: + client_args.extend(["--data-dir", args.data_dir]) + + logger.info(f"Initializing Chroma client with args: {client_args}") + client_parser = server_module.create_parser() + client_args = client_parser.parse_args(client_args) + + try: + server_module.get_chroma_client(client_args) + logger.info("Successfully initialized Chroma client") + except Exception as e: + logger.error(f"Failed to initialize Chroma client: {str(e)}") + sys.exit(1) + + # Run the server with the specified transport + if args.transport == 'http': + logger.info(f"Starting MCP server with HTTP transport on {args.mcp_host}:{args.mcp_port}") + return await mcp.run_server(host=args.mcp_host, port=args.mcp_port) + else: + logger.info("Starting MCP server with stdio transport") + return mcp.run(transport='stdio') + + except Exception as e: + logger.error(f"Error starting MCP server: {str(e)}") + sys.exit(1) + +if __name__ == "__main__": + import asyncio + asyncio.run(start_mcp_server()) \ No newline at end of file