Back to Browse

Chromadb Remote MCP Server

AI & MLLow Risk10.0MCP RegistryLocalRemote
Free

Server data from the Official MCP Registry

Remote ChromaDB vector database MCP server with streamable HTTP transport

About

Remote ChromaDB vector database MCP server with streamable HTTP transport

Remote endpoints: streamable-http: https://meloncafe.github.io/chromadb-remote-mcp/mcp

Security Report

10.0
Low Risk10.0Low Risk

Valid MCP server (1 strong, 1 medium validity signals). No known CVEs in dependencies. Imported from the Official MCP Registry.

3 files analyzed · No issues found

Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.

Permissions Required

This plugin requests these system permissions. Most are normal for its category.

env_vars

Check that this permission is expected for this type of plugin.

What You'll Need

Set these up before or after installing:

URL of your deployed ChromaDB Remote MCP serverOptional

Environment variable: CHROMADB_MCP_URL

Authentication token for the MCP server (if enabled)Required

Environment variable: MCP_AUTH_TOKEN

How to Install & Connect

Available as Local & Remote

This plugin can run on your machine or connect to a hosted endpoint. during install.

Documentation

View on GitHub

From the project's GitHub README.

ChromaDB Remote MCP Server

MCP TypeScript License MseeP.ai codecov DeepSource

A Streamable HTTP MCP (Model Context Protocol) server that provides remote access to ChromaDB for AI assistants like Claude. Enables semantic search and vector database operations from mobile devices and remote locations.

Note: This project uses MCP Streamable HTTP (2025-03-26 spec). SSE transport is deprecated.

한국어 문서


Cross-Platform AI Memory Server

Compatible with ALL major AI platforms:

  • Claude (Desktop, Mobile, Code)
  • Gemini (CLI, Code Assist)
  • Cursor, Cline, Windsurf, VS Code Copilot
  • and use Remote MCP with any other MCP-compatible client

Features

Remote MCP server that enables all Claude clients (Desktop, Code, Mobile) to access the same self-hosted ChromaDB instance.

  • Shared Memory Across Devices - All Claude clients use the same ChromaDB instance
  • Self-Hosted & Private - Your data stays on your infrastructure
  • Remote Access - Connect from anywhere via Tailscale or public internet
  • Complete ChromaDB Support - All CRUD operations via MCP tools
  • REST API Proxy - Direct ChromaDB access for Python/JavaScript
  • Unified Authentication - Single token protects both MCP and REST API endpoints
  • Easy Deployment - One-command installation with Docker

Architecture

Overview

┌──────────────────────────────┐      ┌──────────────┐
│   Claude Desktop + Mobile    │      │  Claude Code │
│  (Custom Connector - synced) │      │  (CLI setup) │
└──────────────┬───────────────┘      └──────┬───────┘
               │                             │
               │     MCP Remote Connector    │
               └─────────────┬───────────────┘
                             │ HTTPS
                   ┌─────────▼──────────┐
                   │   Remote MCP       │
                   │   Server (Node.js) │
                   │                    │
                   │ • Auth Gateway     │
                   │ • MCP Protocol     │
                   │ • REST API Proxy   │
                   └─────────┬──────────┘
                             │
                   ┌─────────▼──────────┐
                   │     ChromaDB       │
                   │ (Vector Database)  │
                   │                    │
                   │ • Embeddings       │
                   │ • Collections      │
                   │ • Semantic Search  │
                   └────────────────────┘

How Clients Connect:

  • Claude Desktop + Mobile: Set up once using custom connector in Claude Desktop, and it automatically syncs to the mobile app. Both share the same connection automatically.
  • Claude Code: Requires separate setup using claude mcp add CLI command.

All clients access the same self-hosted ChromaDB through this remote MCP server. Vector embeddings and semantic search results persist across all platforms.

API Endpoints

PathPurposeClientAuthentication
/mcpMCP ProtocolClaude Desktop/Code/Mobile✅
/api/v2/*ChromaDB REST APIPython✅
/docsSwagger UIBrowser (API docs)✅
/openapi.jsonOpenAPI SpecAPI tools✅
/healthHealth checkMonitoring❌

How It Works

  1. Claude Desktop/Mobile: Add MCP server via custom connector (syncs automatically between devices)
  2. Claude Code: Add MCP server using claude mcp add CLI command
  3. Remote MCP Server authenticates requests and translates MCP protocol to ChromaDB operations
  4. ChromaDB stores and retrieves vector embeddings for semantic search
  5. Python can also access ChromaDB directly via the proxied REST API

Benefits:

  • Same vector database across all clients
  • Desktop and mobile share connection automatically
  • Self-hosted and private
  • Persistent memory across app restarts
  • Single source of truth for embeddings

Quick Start

One-Command Installation

curl -fsSL https://raw.githubusercontent.com/meloncafe/chromadb-remote-mcp/release/scripts/install.sh | bash

This will:

  1. Download docker-compose.yml and .env.example
  2. Auto-detect Docker Compose command (docker-compose or docker compose)
  3. Auto-generate a secure authentication token (optional)
  4. Configure ChromaDB data storage location (Docker volume, local directory, or custom path)
  5. Pull Docker images
  6. Display your authentication token and connection URL

Manual Installation

Option 1: Docker (Recommended - Pre-built Image)
# Download configuration files
mkdir chromadb-remote-mcp && cd chromadb-remote-mcp
curl -O https://raw.githubusercontent.com/meloncafe/chromadb-remote-mcp/release/docker-compose.yml
curl -O https://raw.githubusercontent.com/meloncafe/chromadb-remote-mcp/release/.env.example

# Configure environment
cp .env.example .env
# Edit .env and set:
#   - MCP_AUTH_TOKEN (see token generation below)
#   - PORT (default: 8080)
#   - CHROMA_DATA_PATH (default: chroma-data)

# Start services
docker compose up -d
# or: docker-compose up -d (for older versions)

# Check health
curl http://localhost:8080/health

# View logs
docker compose logs -f
Option 2: Build from Source
# Clone repository
git clone https://github.com/meloncafe/chromadb-remote-mcp.git
cd chromadb-remote-mcp

# Configure environment
cp .env.example .env
# Edit .env with your configuration

# Start with docker-compose (builds image from source)
docker compose -f docker-compose.dev.yml up -d
# or: docker-compose -f docker-compose.dev.yml up -d (for older versions)
Option 3: Local Development
# Clone and install
git clone https://github.com/meloncafe/chromadb-remote-mcp.git
cd chromadb-remote-mcp
yarn install

# Configure environment
cp .env.example .env
# Edit .env file

# Build and run
yarn build
yarn start

Generate Secure Token

For production use, generate a secure token for MCP_AUTH_TOKEN in .env:

# Method 1: Node.js (Recommended)
node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"

# Method 2: OpenSSL
openssl rand -base64 32 | tr '+/' '-_' | tr -d '='

Copy the generated token and paste it into your .env file:

MCP_AUTH_TOKEN=your-generated-token-here

Server Endpoints

  • MCP: http://localhost:8080/mcp (via Caddy proxy)
  • Health: http://localhost:8080/health
  • ChromaDB API: http://localhost:8080/api/v2/*
  • Swagger UI: http://localhost:8080/docs

Configuration

Environment Variables (.env file)

All configuration is done through the .env file. Copy .env.example to .env and customize:

cp .env.example .env
VariableDescriptionDefaultRequired
PORTExternal port (Caddy reverse proxy)8080No
CHROMA_DATA_PATHChromaDB data storage path (volume name, ./data, or absolute path)chroma-dataNo
CHROMA_HOSTChromaDB host (internal)chromadbNo
CHROMA_PORTChromaDB port (internal)8000No
CHROMA_TENANTChromaDB tenantdefault_tenantNo
CHROMA_DATABASEChromaDB databasedefault_databaseNo
MCP_AUTH_TOKENAuthentication token for MCP and REST API-Yes (for public access)
CHROMA_AUTH_TOKENChromaDB auth token (if ChromaDB requires auth)-No
RATE_LIMIT_MAXMax requests per IP per 15 minutes100No
ALLOWED_ORIGINSComma-separated list of allowed origins (DNS rebinding protection)-No
CHROMA_SERVER_VERSIONChroma server version declared by the operator — used by chroma_get_server_version and /health/detail as the trusted version source. Warning: with the chromadb/chroma:latest image this value does not follow image updates and can drift — pin a version tag and set this to the same value.unknown when unset; docker-compose.yml default 1.5.9No
MCP_SERVER_VERSIONMCP wrapper version — set automatically in release images (release tag → VERSION build-arg); no need to set it manuallydev when unsetNo

Authentication

IMPORTANT: For public internet access (Tailscale Funnel, Cloudflare Tunnel, etc.), you must set MCP_AUTH_TOKEN in your .env file.

Generate a secure token:

# Method 1: Node.js (Recommended - from .env.example)
node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"

# Method 2: OpenSSL
openssl rand -base64 32 | tr '+/' '-_' | tr -d '='

Edit your .env file:

MCP_AUTH_TOKEN=your-generated-token-here

Then restart the services:

docker compose restart
# or: docker-compose restart

Supported authentication methods (v2.0.0):

  1. Authorization: Bearer TOKEN — only supported way to send MCP_AUTH_TOKEN.

    • Recommended for service-to-service callers (API clients, scripts, MCP relays).
    • Compliant with MCP specification.
    • Example: curl -H "Authorization: Bearer YOUR_TOKEN" https://your-server.com/mcp
  2. OAuth 2.1 / OpenID Connect — recommended for human users.

    • Set OIDC_ISSUERS (comma-separated issuer URLs) or OIDC_PRESET=google,github,microsoft.
    • Set OIDC_AUDIENCE to the resource identifier (typically your MCP server's public URL).
    • The server publishes RFC 9728 Protected Resource Metadata at /.well-known/oauth-protected-resource.
    • 401 responses include WWW-Authenticate: Bearer error="...", resource_metadata="..." per RFC 6750.

Removed in v2.0.0: X-Chroma-Token header and ?apiKey= / ?token= / ?api_key= query-parameter auth are no longer accepted. Clients that previously used those paths must migrate to Authorization: Bearer. The ALLOW_QUERY_AUTH env var is ignored.

Origin Header Validation (DNS Rebinding Protection)

The server validates the Origin header for browser requests to prevent DNS rebinding attacks. This security feature is enabled by default and protects your local MCP server from malicious websites.

Default allowed origins (always permitted):

  • Localhost variants: localhost, 127.0.0.1, [::1]
  • Claude.ai domains: https://claude.ai, https://api.anthropic.com

Configure additional allowed origins:

If you need to allow additional web applications or custom domains, add them to ALLOWED_ORIGINS in your .env file:

# Add additional custom domains (Claude.ai is already allowed by default)
ALLOWED_ORIGINS=https://myapp.com,https://yourdomain.com

When to configure ALLOWED_ORIGINS:

  • ✅ Using Claude Desktop Custom Connector → No configuration needed (allowed by default)
  • ✅ Accessing from custom web applications → Add your application's domain
  • ✅ Using Swagger UI remotely → Add your server's domain
  • ❌ Using Claude Code CLI → Not needed (no Origin header)
  • ❌ Using Python/JavaScript clients → Not needed (no Origin header)
  • ❌ Local development only → Not needed (localhost is allowed by default)

Example configurations:

# For custom web application
ALLOWED_ORIGINS=https://myapp.com,https://app.mycompany.com

# Multiple custom domains (comma-separated, spaces are trimmed)
ALLOWED_ORIGINS=https://myapp.com, https://api.example.com, https://dashboard.mycompany.com

# Leave empty if you only need Claude.ai and localhost
ALLOWED_ORIGINS=

Note: Claude.ai domains (https://claude.ai, https://api.anthropic.com) and localhost are always allowed, even if ALLOWED_ORIGINS is empty. Server-to-server requests (without Origin header) are always permitted.

Data Storage Configuration

ChromaDB data can be stored in three ways:

  1. Docker volume (default): CHROMA_DATA_PATH=chroma-data

    • Managed by Docker
    • Survives container restarts
    • Use docker volume ls and docker volume inspect chroma-data to locate
  2. Local directory: CHROMA_DATA_PATH=./data

    • Easy to backup and access
    • Stored in installation directory
  3. Custom path: CHROMA_DATA_PATH=/path/to/data

    • Must be an absolute path
    • Useful for mounting external storage

After changing CHROMA_DATA_PATH, restart the services:

docker compose restart

Connecting Claude

Claude Desktop + Mobile

Method 1: Custom Connector (Recommended - Pro/Team/Enterprise)

  1. Open Claude Desktop → Settings → Integrations → Custom Connector
  2. Click "Add Custom Server"
  3. Enter:
    • Name: ChromaDB
    • URL: https://your-server.com/mcp (set Authorization: Bearer YOUR_TOKEN in the connector's header config)

Note: Custom connector automatically syncs to the mobile app. Authentication is mandatory for remote access.

Method 2: mcp-remote Wrapper (Free/Pro Users)

If you don't have access to Custom Connectors, use the mcp-remote package as a workaround:

Configuration file location:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Add to configuration file:

{
  "mcpServers": {
    "chromadb": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://your-server.com/mcp", "--header", "Authorization: Bearer YOUR_TOKEN"]
    }
  }
}

Restart Claude Desktop after editing the file.

Important: Remote MCP servers cannot be configured directly in claude_desktop_config.json using streamableHttp transport. You must either use Custom Connectors or the mcp-remote wrapper package.

Claude Code

CLI Command:

# Without authentication
claude mcp add --transport http chromadb https://your-server.com/mcp

# With authentication (Query Parameter - Recommended)
claude mcp add --transport http chromadb https://your-server.com/mcp \
  --header "Authorization: Bearer YOUR_TOKEN"

# With authentication (Header)
claude mcp add --transport http chromadb https://your-server.com/mcp \
  --header "Authorization: Bearer YOUR_TOKEN"

# Verify
claude mcp list

Available Tools (v2.2.0)

The MCP server provides these tools for Claude. v2.2.0 expands coverage to 30 tools across collection / document / search / fork / client-info / admin / destructive groups.

Collection Management

  • chroma_list_collections - List all collections (with limit / offset)
  • chroma_create_collection - Create a new collection (configuration / schema optional)
  • chroma_get_or_create_collection - Idempotent create-or-get (v2.2.0)
  • chroma_modify_collection - Rename / change metadata or configuration (v2.2.0)
  • chroma_delete_collection - Delete a collection
  • chroma_get_collection_info - Get collection metadata
  • chroma_get_collection_count - Get document count (read_level optional)
  • chroma_count_collections - Total collection count (v2.2.0)
  • chroma_peek_collection - Preview collection contents

Document Operations

  • chroma_add_documents - Add documents (with uris for multi-modal)
  • chroma_upsert_documents - Idempotent insert-or-update (v2.2.0)
  • chroma_query_documents - Semantic search (with query_uris / ids pre-filter)
  • chroma_get_documents - Retrieve documents (read_level optional)
  • chroma_update_documents - Update existing documents (with embeddings / uris)
  • chroma_delete_documents - Delete by ids and/or where / where_document filter

Server Info (v2.2.0)

  • chroma_heartbeat - Server heartbeat (nanosecond timestamp)
  • chroma_get_server_version - Server version string
  • chroma_get_max_batch_size - Max batch size (for client-side splitting)
  • chroma_get_user_identity - Current tenant + databases

Distributed/Cloud-only — opt-in (CHROMA_DISTRIBUTED_TOOLS_ENABLED=true)

These 4 tools require ChromaDB's distributed executor (the executor is the chromadb-server-internal frontend layer, not an algorithmic distribution requirement). The single-node open-source server (chromadb/chroma:latest docker) ships with the local executor, which has these methods hard-coded as unimplemented in rust/frontend/src/executor/local.rs and rust/types/src/api_types.rs. To use them you need either Chroma Cloud (CloudClient) or a self-hosted distributed Chroma deployment (Kubernetes multi-component: frontend + query executor + WAL + compactor + object storage).

Hidden by default so single-node deployments don't waste LLM context on tools that always return "not implemented for local executor" / "unsupported for local chroma".

  • chroma_search - Hybrid dense + sparse search (RRF). The algorithm itself works on a single node; chromadb open-source simply hasn't implemented the search() endpoint in the local executor.
  • chroma_fork_collection - Zero-copy fork (segment-level operation on object storage — architecturally requires the distributed compactor/storage stack).
  • chroma_get_fork_count - Fork metadata lookup (depends on the distributed metadata store).
  • chroma_get_indexing_status - WAL offset + compactor index progress (requires the distributed WAL/compactor services).

Admin — opt-in (CHROMA_ADMIN_TOOLS_ENABLED=true)

  • chroma_admin_create_database / chroma_admin_get_database / chroma_admin_list_databases
  • chroma_admin_create_tenant / chroma_admin_get_tenant

Destructive — opt-in (CHROMA_ALLOW_DESTRUCTIVE_OPS=true)

Calls emit a [DESTRUCTIVE] audit line.

  • chroma_reset_database - Reset entire database (irreversible)
  • chroma_admin_delete_database - Delete a database (requires both flags)

Using ChromaDB from Python

The MCP server proxies all ChromaDB REST API endpoints, allowing direct access from Python clients.

Python Example

import chromadb

# HTTPS (Tailscale Funnel, public deployment)
client = chromadb.HttpClient(
    host="your-server.com",
    port=443,
    ssl=True,
    headers={
        "Authorization": "Bearer YOUR_TOKEN"
    }
)

# Local development (HTTP)
client = chromadb.HttpClient(
    host="localhost",
    port=8080,
    ssl=False,
    headers={
        "Authorization": "Bearer YOUR_TOKEN"
    }
)

# Usage
collection = client.create_collection("my_collection")
collection.add(
    documents=["Document 1", "Document 2"],
    ids=["id1", "id2"]
)
results = collection.query(query_texts=["query"], n_results=2)

Alternative authentication:

from chromadb.config import Settings

client = chromadb.HttpClient(
    host="your-server.com",
    port=443,
    ssl=True,
    settings=Settings(
        chroma_client_auth_provider="chromadb.auth.token_authn.TokenAuthClientProvider",
        chroma_client_auth_credentials="YOUR_TOKEN"
    )
)

API Documentation

Visit https://your-server.com/docs for Swagger UI documentation of all ChromaDB REST API endpoints.


Deployment

Deploying with deploy.sh

deploy.sh wraps the Docker Compose lifecycle of this repository's docker-compose.yml in one script. It is host independent: everything host specific lives in .env (see .env.example) and optional extra compose files, never in the script.

CommandWhat it does
./deploy.sh installFirst-time setup. Creates .env from .env.example when missing and stops so you can edit it; otherwise pulls images and starts services
./deploy.sh updatePulls images, recreates containers (up -d --force-recreate), prunes dangling images, syncs CHROMA_SERVER_VERSION in .env, then prints the version information reusing that single judgment
./deploy.sh startStarts services (up -d)
./deploy.sh stopStops and removes the containers (down); data is kept
./deploy.sh restartRestarts the containers. Does not re-read .env — use update or start after changing it
./deploy.sh statusdocker compose ps plus the state of the chromadb / chroma-mcp-server / caddy-proxy containers
./deploy.sh logs [service]Follows logs (--tail=100), e.g. ./deploy.sh logs mcp-server
./deploy.sh versionShows the mcp image and running-container versions and the pinned chroma image, resolves the Chroma server version from the running image digest, and records it in .env
./deploy.sh backupArchives the chroma data to backups/chromadb_data_<timestamp>.tar.gz (see below)
./deploy.sh self-update [tag]Replaces deploy.sh, docker-compose.yml, docker-compose.traefik.example.yml and .env.example with the ones from a GitHub release (default: latest). .env, local compose overrides and data are never touched (see below)

./deploy.sh help prints the usage.

Settings — read from the host's .env (a process environment variable of the same name takes precedence):

KeyMeaningDefault
COMPOSE_FILECompose files merged in order, separated by :docker-compose.yml
COMPOSE_PATH_SEPARATORSeparator for the COMPOSE_FILE list:
COMPOSE_PROJECT_NAMECompose project name; it prefixes the network and the named volume (<project>_chroma-data). Changing it after the first start gives a named-volume stack a new, empty volume — a bind path keeps its datadeploy directory name
CHROMA_DATA_PATHChroma data: the compose named volume chroma-data, or an absolute / ./relative bind pathchroma-data (named volume)
PORTHost port published by caddy8080
MCP_IMAGE_TAGTag of the devsaurus/chromadb-remote-mcp imagelatest
DEPLOY_LANGMessage language of deploy.sh: en or kofrom the locale

DRY_RUN=1, DEPLOY_DIR=<dir> (target directory, for tests) and CHROMA_IMAGE=<ref> (image for the backup tar when no chromadb container exists) are read from the process environment.

One stack per host — docker-compose.yml fixes container_name (chromadb, chroma-mcp-server, caddy-proxy), so changing only COMPOSE_PROJECT_NAME does not let a second stack run on the same host: the container names collide.

Compose file selection — COMPOSE_FILE is a list merged in order (later files override earlier ones), exactly like docker compose. Relative entries resolve against the deploy directory, absolute entries are used unchanged. deploy.sh passes --project-directory on every call, so .env and relative paths always refer to the deploy directory.

DRY_RUN=1 — prints every docker command prefixed [DRY-RUN], calls no docker and changes no file; it works even without docker installed. install prints its plan without copying .env or creating backups/; version / update skip the digest judgment, the .env write and the mcp-server recreation. A missing .env is downgraded to a warning: the plan is printed from the committed .env.example / compose defaults and no file is created. Without DRY_RUN, a missing .env is still an error.

DRY_RUN=1 ./deploy.sh update

Output language — help and log messages are English or Korean. The language is DEPLOY_LANG (environment, then .env); if unset, the first non-empty of LC_ALL, LC_MESSAGES, LANG: a value starting with ko selects Korean, anything else English.

CHROMA_SERVER_VERSION handling — the Chroma server reports a hardcoded 1.0.0, so update / version compare the RepoDigest of the chromadb container's image with the digest pinned in the compose file and then with the Docker Hub digest of the pinned tag, and record the result in .env. When the value changed, mcp-server alone is recreated with up -d. If the version cannot be determined (including a match on the Docker Hub latest tag alone), an existing .env value is preserved with a warning; unknown is written only when no value exists yet.

Backup — ./deploy.sh backup reads the data through a throwaway container with a read-only mount, so root-owned bind directories and named volumes both work. The data location is detected automatically: the existing chromadb container's /data mount first, otherwise CHROMA_DATA_PATH from .env (an absolute, ./ or ../ path is a bind mount; a bare name — the default chroma-data — is the compose named volume <project>_<name>). The archive is written as .tar.gz.partial and renamed on success (removed on failure). chromadb is stopped for a consistent snapshot and started again only if it was running before the backup. If the backup receives SIGINT or SIGTERM (for example Ctrl+C), it removes the .tar.gz.partial file, starts chromadb again when it was running before the backup, and exits with code 130 (SIGINT) or 143 (SIGTERM).

Self-update — ./deploy.sh self-update [tag] downloads the repository-managed files from a GitHub release tag (default: the latest release; DEPLOY_TAG or --tag also work) into a staging directory, validates them (non-empty, bash -n for the script, services: for compose files, and SHA256SUMS when the release publishes one), backs up the current files to backups/self-update_<timestamp>_<version>/, replaces each file atomically (temp file + mv), and restores the backup on any failure or interrupt. It shows the current and target version (the installed tag is recorded in .deploy-release). .env, local compose overrides that are not part of the release, and the data directory are never touched. It refuses to run inside a git checkout (use git pull) and supports DRY_RUN=1. DEPLOY_REPO (default meloncafe/chromadb-remote-mcp) and SELF_UPDATE_FILES override the source repository and the file list. update (images) and self-update (scripts/compose) are deliberately separate: swapping the compose file in the middle of update would change what the pull/recreate runs against, so update only prints a hint and self-update ends by telling you to run update.

Behind a Traefik reverse proxy — docker-compose.traefik.example.yml is an optional example override with placeholder variables. Copy it, keep the copy on the server, and select it through the compose file list in .env:

cp docker-compose.traefik.example.yml docker-compose.traefik.yml

# .env
COMPOSE_FILE=docker-compose.yml:docker-compose.traefik.yml
TRAEFIK_HOST=mcp.example.com
TRAEFIK_CERT_RESOLVER=your-resolver

./deploy.sh update

TRAEFIK_HOST and TRAEFIK_CERT_RESOLVER are required. Optional: TRAEFIK_NETWORK (external network Traefik is attached to, which must already exist; default traefik), TRAEFIK_ENTRYPOINT (default websecure), TRAEFIK_MIDDLEWARES (comma-separated; see the note below) and TRAEFIK_ROUTER_NAME (default chromadb-mcp). Traefik router and service names are global, so pick a TRAEFIK_ROUTER_NAME that no other container on that Traefik already uses. The example removes the base file's host port mapping with !reset, which requires Docker Compose 2.24 or newer.

The middlewares label in docker-compose.traefik.example.yml is commented out by default, so no middleware is attached. Once you uncomment the label, TRAEFIK_MIDDLEWARES becomes required: not only an unset variable but also an empty value (TRAEFIK_MIDDLEWARES=) aborts with required variable TRAEFIK_MIDDLEWARES is missing a value. To run without middlewares, keep the label commented out.

Option 1: Tailscale VPN (Recommended)

Secure access within your Tailscale network:

# Start services
docker compose up -d

# Enable Tailscale Serve (HTTPS with automatic certificates)
tailscale serve https / http://127.0.0.1:8080

# Check status
tailscale serve status

Your server is now accessible at https://your-machine.tailXXXXX.ts.net to all devices in your Tailnet.

Advantages:

  • Automatic HTTPS certificates
  • No public internet exposure
  • Encrypted VPN tunnel
  • Authentication optional (VPN provides security layer)

Option 2: Tailscale Funnel (Public Internet)

To use Claude Desktop UI Custom Connector or share publicly:

# Enable Funnel (allows public internet access)
tailscale funnel 8080 on
tailscale serve https / http://127.0.0.1:8080

# Verify Funnel is active
tailscale serve status  # Should show "Funnel on"

Warning: This exposes your server to the public internet. Authentication is mandatory! Set MCP_AUTH_TOKEN in your environment.

Disable Funnel:

tailscale funnel 8080 off

Option 3: Cloudflare Tunnel

# Install cloudflared
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o cloudflared
chmod +x cloudflared

# Authenticate
./cloudflared tunnel login

# Create tunnel
./cloudflared tunnel create chroma-mcp

# Run tunnel
./cloudflared tunnel --url http://localhost:3000

Option 4: Nginx Reverse Proxy

server {
    listen 80;
    server_name your-domain.com;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Security

Code Quality & Security Analysis

This project follows strict security practices and has resolved all security issues identified by static analysis:

  • ✅ Zero Active Issues: All OWASP and CWE security findings have been resolved
  • 🔒 Static Analysis: Continuous monitoring with DeepSource
  • 🛡️ Security Standards: Compliant with OWASP Top 10 and Node.js security best practices
  • 📊 Automated Scanning: Dependabot, CodeQL, and container vulnerability scanning

For detailed security information, see Security Policy.

Security Recommendations

  1. Enable Authentication for Public Access

    • Set MCP_AUTH_TOKEN when using Tailscale Funnel or public internet
    • Generate strong tokens: openssl rand -base64 32 | tr '+/' '-_' | tr -d '='
    • Rotate tokens regularly
  2. Use HTTPS

    • Tailscale provides automatic HTTPS certificates
    • Use reverse proxy (Nginx/Caddy) with Let's Encrypt for other deployments
  3. Prefer VPN Over Public Internet

    • Tailscale Serve (VPN-only) is more secure than Funnel (public)
    • Authentication is optional within VPN but mandatory for public access
  4. Monitor Access

    # Check for unauthorized access attempts
    docker compose logs mcp-server | grep "Unauthorized"
    
  5. Network Isolation

    • Keep ChromaDB on private network
    • Only expose MCP server to public internet

Testing

Local Testing

# Health check
curl http://localhost:3000/health

# MCP tools list
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

# ChromaDB heartbeat
curl http://localhost:3000/api/v2/heartbeat

Remote Testing (with authentication)

# MCP endpoint (Bearer token)
curl -X POST https://your-server.com/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'

# MCP endpoint (Bearer token)
curl -X POST "https://your-server.com/mcp" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'

# ChromaDB REST API
curl https://your-server.com/api/v2/heartbeat \
  -H "Authorization: Bearer YOUR_TOKEN"

# Swagger UI (browser)
https://your-server.com/docs  # send Authorization: Bearer YOUR_TOKEN header

Troubleshooting

ChromaDB Connection Failed

# Check if ChromaDB is running
curl http://localhost:8000/api/v2/heartbeat

# Start ChromaDB with Docker
# WARNING: ChromaDB has no built-in authentication — do not publish on routable interface.
# Bind to loopback only (127.0.0.1:8000:8000). Use MCP server as the authenticated gateway.
docker run -d -p 127.0.0.1:8000:8000 chromadb/chroma:1.5.9

# Check MCP server logs
docker compose logs mcp-server

MCP Server Not Responding

# Check logs
docker compose logs mcp-server

# Check port conflicts
lsof -i :3000

# Restart services
docker compose restart

Claude Desktop Connection Issues

  1. Restart Claude Desktop
  2. Verify URL includes /mcp path
  3. Confirm transport type is streamableHttp (not sse)
  4. Check authentication token if enabled
  5. For Custom Connector: Ensure Tailscale Funnel is active

TLS Handshake Timeout on Local Network

If you're connecting from the same local network as the server and using Tailscale Funnel HTTPS:

Problem: TLS handshake fails with timeout when accessing https://your-server.ts.net from the same network.

Root cause: Tailscale Funnel has issues with TLS termination when clients on the same LAN try to connect via the public Funnel domain.

Solution: Use direct local network connection instead of Tailscale HTTPS:

# Remove existing configuration
claude mcp remove chromadb

# Add with local IP address
claude mcp add chromadb --transport http \
  http://192.168.x.x:8080/mcp \
  --header "Authorization: Bearer YOUR_TOKEN"

# Or use hostname if DNS resolves
claude mcp add chromadb --transport http \
  http://server-hostname:8080/mcp \
  --header "Authorization: Bearer YOUR_TOKEN"

Verification:

# Test local network connection
curl http://192.168.x.x:8080/health

# Should return: {"status":"ok","service":"chroma-remote-mcp",...}

Note: External clients should continue using Tailscale Funnel HTTPS. This issue only affects clients on the same LAN as the server.

Authentication Errors (401)

# Verify MCP_AUTH_TOKEN is set
docker compose exec mcp-server env | grep MCP_AUTH_TOKEN

# Test without token (should fail with 401)
curl -X POST https://your-server.com/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'

# Test with correct token (should succeed)
curl -X POST https://your-server.com/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'

Development

Building from Source

# Clone repository
git clone https://github.com/meloncafe/chromadb-remote-mcp.git
cd chromadb-remote-mcp

# Install dependencies
yarn install

# Development mode (auto-reload)
yarn dev

# Build TypeScript
yarn build

# Type check
yarn type-check

Testing

The project includes integration tests with Docker-based E2E validation:

# Run all tests (starts services, runs tests, cleans up)
yarn test

# Run tests and keep containers running for debugging
yarn test:keep

# Manual test script with options
./scripts/test.sh --help

Integration Test Coverage:

  • ✅ Health check endpoint
  • ✅ Authentication (Authorization: Bearer MCP_AUTH_TOKEN; OAuth 2.1 / OIDC multi-provider)
  • ✅ MCP protocol (tools/list, tools/call)
  • ✅ ChromaDB REST API proxy
  • ✅ Collection CRUD operations
  • ✅ Rate limiting
  • ✅ Unauthorized access handling

Unit Tests:

# Run unit tests
yarn test:unit

# Run with watch mode
yarn test:unit:watch

# Run with coverage
yarn test:unit:coverage

# Run all tests (unit + integration)
yarn test:all

Unit Test Coverage:

  • ✅ Authentication utilities (timing-safe comparison, buffer operations)
  • ✅ Input validation (collection names, document IDs, metadata)
  • ✅ Data processing (response formatting, JSON serialization)
  • ✅ Error message formatting

See __tests__/README.md for detailed testing strategy.

Code Quality & Coverage

This project uses Codecov for code coverage tracking and test analytics.

Docker Development

Local Build and Test
# Build for local testing (single platform, loads to Docker)
yarn docker:build:local

# Or with script directly
./scripts/build.sh --platform linux/amd64 --load

# Test the built image
docker run -p 3000:3000 \
  -e MCP_AUTH_TOKEN=test123 \
  devsaurus/chromadb-remote-mcp:latest
Multi-Platform Build
# Build for all platforms (amd64, arm64)
yarn docker:build

# Build with custom version
./scripts/build.sh --version 1.2.3

# Build with custom repository
./scripts/build.sh --repo myuser/my-mcp --version dev
Push to Docker Hub
# Push latest tag
yarn docker:push

# Push specific version
VERSION=1.2.3 yarn docker:push

# Or with script directly
./scripts/build.sh --version 1.2.3 --push

# With custom repository
DOCKER_REPO=myuser/my-mcp ./scripts/build.sh --version 1.2.3 --push

Environment Variables for Docker Scripts:

export DOCKER_REPO=myuser/my-mcp       # Docker repository
export VERSION=1.2.3                    # Image version tag
export DOCKER_USERNAME=myuser           # For push authentication
export DOCKER_PASSWORD=mytoken          # Docker Hub token

Development Scripts

All development scripts are located in scripts/:

ScriptPurposeUsage
build.shBuild and push Docker images./scripts/build.sh --help
test.shRun integration tests./scripts/test.sh --help
install.shOne-command installationcurl ... | bash

Quick Development Workflow:

# 1. Make code changes
vim src/index.ts

# 2. Test locally
yarn dev

# 3. Run integration tests
yarn test

# 4. Build Docker image
yarn docker:build:local

# 5. Test Docker image
docker-compose up

# 6. If all good, build multi-platform and push
./scripts/build.sh --version 1.2.3 --push

Project Structure

Documentation truncated — see the full README on GitHub.

Reviews

No reviews yet

Be the first to review this server!