Handle parameters with environmental variables only (#24)
* Switch to Typer to read the CLI parameters and options * Rely on environmental variables only * Fix tests * Update README
This commit is contained in:
106
README.md
106
README.md
@@ -1,7 +1,10 @@
|
||||
# mcp-server-qdrant: A Qdrant MCP server
|
||||
[](https://smithery.ai/protocol/mcp-server-qdrant)
|
||||
|
||||
> The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol that enables seamless integration between LLM applications and external data sources and tools. Whether you’re building an AI-powered IDE, enhancing a chat interface, or creating custom AI workflows, MCP provides a standardized way to connect LLMs with the context they need.
|
||||
> The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol that enables
|
||||
> seamless integration between LLM applications and external data sources and tools. Whether you’re building an
|
||||
> AI-powered IDE, enhancing a chat interface, or creating custom AI workflows, MCP provides a standardized way to
|
||||
> connect LLMs with the context they need.
|
||||
|
||||
This repository is an example of how to create a MCP server for [Qdrant](https://qdrant.tech/), a vector search engine.
|
||||
|
||||
@@ -27,18 +30,18 @@ It acts as a semantic memory layer on top of the Qdrant database.
|
||||
- `query` (string): Query to retrieve a memory
|
||||
- Returns: Memories stored in the Qdrant database as separate messages
|
||||
|
||||
## Installation
|
||||
## Installation in Claude Desktop
|
||||
|
||||
### Using uv (recommended)
|
||||
### Using mcp (recommended)
|
||||
|
||||
When using [`uv`](https://docs.astral.sh/uv/) no specific installation is needed to directly run *mcp-server-qdrant*.
|
||||
When using [`mcp`](https://github.com/modelcontextprotocol/python-sdk) no specific installation is needed to directly run *mcp-server-qdrant*.
|
||||
|
||||
```shell
|
||||
uv run mcp-server-qdrant \
|
||||
--qdrant-url "http://localhost:6333" \
|
||||
--qdrant-api-key "your_api_key" \
|
||||
--collection-name "my_collection" \
|
||||
--embedding-model "sentence-transformers/all-MiniLM-L6-v2"
|
||||
mcp install src/mcp_server_qdrant/server.py \
|
||||
-v QDRANT_URL="http://localhost:6333" \
|
||||
-v QDRANT_API_KEY="your_api_key" \
|
||||
-v COLLECTION_NAME="my_collection" \
|
||||
-v EMBEDDING_MODEL="sentence-transformers/all-MiniLM-L6-v2"
|
||||
```
|
||||
|
||||
### Installing via Smithery
|
||||
@@ -49,70 +52,68 @@ To install Qdrant MCP Server for Claude Desktop automatically via [Smithery](htt
|
||||
npx @smithery/cli install mcp-server-qdrant --client claude
|
||||
```
|
||||
|
||||
## Usage with Claude Desktop
|
||||
### Manual configuration
|
||||
|
||||
To use this server with the Claude Desktop app, add the following configuration to the "mcpServers" section of your `claude_desktop_config.json`:
|
||||
To use this server with the Claude Desktop app, add the following configuration to the "mcpServers" section of your
|
||||
`claude_desktop_config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"qdrant": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"mcp-server-qdrant",
|
||||
"--qdrant-url",
|
||||
"http://localhost:6333",
|
||||
"--qdrant-api-key",
|
||||
"your_api_key",
|
||||
"--collection-name",
|
||||
"your_collection_name"
|
||||
]
|
||||
"args": ["mcp-server-qdrant"],
|
||||
"env": {
|
||||
"QDRANT_URL": "http://localhost:6333",
|
||||
"QDRANT_API_KEY": "your_api_key",
|
||||
"COLLECTION_NAME": "your_collection_name",
|
||||
"EMBEDDING_MODEL": "sentence-transformers/all-MiniLM-L6-v2"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Replace `http://localhost:6333`, `your_api_key` and `your_collection_name` with your Qdrant server URL, Qdrant API key
|
||||
and collection name, respectively. The use of API key is optional, but recommended for security reasons, and depends on
|
||||
the Qdrant server configuration.
|
||||
For local Qdrant mode:
|
||||
|
||||
```json
|
||||
{
|
||||
"qdrant": {
|
||||
"command": "uvx",
|
||||
"args": ["mcp-server-qdrant"],
|
||||
"env": {
|
||||
"QDRANT_LOCAL_PATH": "/path/to/qdrant/database",
|
||||
"COLLECTION_NAME": "your_collection_name",
|
||||
"EMBEDDING_MODEL": "sentence-transformers/all-MiniLM-L6-v2"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This MCP server will automatically create a collection with the specified name if it doesn't exist.
|
||||
|
||||
By default, the server will use the `sentence-transformers/all-MiniLM-L6-v2` embedding model to encode memories.
|
||||
For the time being, only [FastEmbed](https://qdrant.github.io/fastembed/) models are supported, and you can change it
|
||||
by passing the `--embedding-model` argument to the server.
|
||||
For the time being, only [FastEmbed](https://qdrant.github.io/fastembed/) models are supported.
|
||||
|
||||
### Using the local mode of Qdrant
|
||||
### Support for other tools
|
||||
|
||||
To use a local mode of Qdrant, you can specify the path to the database using the `--qdrant-local-path` argument:
|
||||
|
||||
```json
|
||||
{
|
||||
"qdrant": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"mcp-server-qdrant",
|
||||
"--qdrant-local-path",
|
||||
"/path/to/qdrant/database",
|
||||
"--collection-name",
|
||||
"your_collection_name"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
It will run Qdrant local mode inside the same process as the MCP server. Although it is not recommended for production.
|
||||
This MCP server can be used with any MCP-compatible client. For example, you can use it with
|
||||
[Cursor](https://docs.cursor.com/context/model-context-protocol), which provides built-in support for the Model Context
|
||||
Protocol.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The configuration of the server can be also done using environment variables:
|
||||
The configuration of the server is done using environment variables:
|
||||
|
||||
- `QDRANT_URL`: URL of the Qdrant server, e.g. `http://localhost:6333`
|
||||
- `QDRANT_API_KEY`: API key for the Qdrant server
|
||||
- `COLLECTION_NAME`: Name of the collection to use
|
||||
- `EMBEDDING_MODEL`: Name of the embedding model to use
|
||||
- `QDRANT_API_KEY`: API key for the Qdrant server (optional, depends on Qdrant server configuration)
|
||||
- `COLLECTION_NAME`: Name of the collection to use (required)
|
||||
- `EMBEDDING_MODEL`: Name of the embedding model to use (default: `sentence-transformers/all-MiniLM-L6-v2`)
|
||||
- `EMBEDDING_PROVIDER`: Embedding provider to use (currently only "fastembed" is supported)
|
||||
- `QDRANT_LOCAL_PATH`: Path to the local Qdrant database
|
||||
- `QDRANT_LOCAL_PATH`: Path to the local Qdrant database (alternative to `QDRANT_URL`)
|
||||
|
||||
You cannot provide `QDRANT_URL` and `QDRANT_LOCAL_PATH` at the same time.
|
||||
Note: You cannot provide both `QDRANT_URL` and `QDRANT_LOCAL_PATH` at the same time.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Command-line arguments are not supported anymore! Please use environment variables for all configuration.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -126,9 +127,8 @@ servers. It runs both a client UI (default port 5173) and an MCP proxy server (d
|
||||
your browser to use the inspector.
|
||||
|
||||
```shell
|
||||
npx @modelcontextprotocol/inspector uv run mcp-server-qdrant \
|
||||
--collection-name test \
|
||||
--qdrant-local-path /tmp/qdrant-local-test
|
||||
QDRANT_URL=":memory:" COLLECTION_NAME="test" \
|
||||
mcp dev src/mcp_server_qdrant/server.py
|
||||
```
|
||||
|
||||
Once started, open your browser to http://localhost:5173 to access the inspector interface.
|
||||
|
||||
@@ -9,7 +9,6 @@ dependencies = [
|
||||
"mcp[cli]>=1.3.0",
|
||||
"fastembed>=0.6.0",
|
||||
"qdrant-client>=1.12.0",
|
||||
"typer>=0.15.2",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
@@ -26,7 +25,7 @@ dev-dependencies = [
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
mcp-server-qdrant = "mcp_server_qdrant:main"
|
||||
mcp-server-qdrant = "mcp_server_qdrant.main:main"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
from . import server
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point for the package."""
|
||||
server.mcp.run()
|
||||
|
||||
|
||||
# Optionally expose other important items at package level
|
||||
__all__ = ["main", "server"]
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
from .base import EmbeddingProvider
|
||||
from .factory import create_embedding_provider
|
||||
from .fastembed import FastEmbedProvider
|
||||
|
||||
__all__ = ["EmbeddingProvider", "FastEmbedProvider", "create_embedding_provider"]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from mcp_server_qdrant.embeddings import EmbeddingProvider
|
||||
from mcp_server_qdrant.embeddings.types import EmbeddingProviderType
|
||||
from mcp_server_qdrant.settings import EmbeddingProviderSettings
|
||||
|
||||
|
||||
@@ -8,7 +9,7 @@ def create_embedding_provider(settings: EmbeddingProviderSettings) -> EmbeddingP
|
||||
:param settings: The settings for the embedding provider.
|
||||
:return: An instance of the specified embedding provider.
|
||||
"""
|
||||
if settings.provider_type.lower() == "fastembed":
|
||||
if settings.provider_type == EmbeddingProviderType.FASTEMBED:
|
||||
from mcp_server_qdrant.embeddings.fastembed import FastEmbedProvider
|
||||
|
||||
return FastEmbedProvider(settings.model_name)
|
||||
|
||||
5
src/mcp_server_qdrant/embeddings/types.py
Normal file
5
src/mcp_server_qdrant/embeddings/types.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class EmbeddingProviderType(Enum):
|
||||
FASTEMBED = "fastembed"
|
||||
9
src/mcp_server_qdrant/main.py
Normal file
9
src/mcp_server_qdrant/main.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from mcp_server_qdrant.server import mcp
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for the mcp-server-qdrant script defined
|
||||
in pyproject.toml. It runs the MCP server.
|
||||
"""
|
||||
mcp.run()
|
||||
@@ -1,5 +1,4 @@
|
||||
import logging
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import AsyncIterator, List
|
||||
|
||||
@@ -8,27 +7,18 @@ from mcp.server.fastmcp import Context, FastMCP
|
||||
|
||||
from mcp_server_qdrant.embeddings.factory import create_embedding_provider
|
||||
from mcp_server_qdrant.qdrant import QdrantConnector
|
||||
from mcp_server_qdrant.settings import (
|
||||
EmbeddingProviderSettings,
|
||||
QdrantSettings,
|
||||
parse_args,
|
||||
)
|
||||
from mcp_server_qdrant.settings import EmbeddingProviderSettings, QdrantSettings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Parse command line arguments and set them as environment variables.
|
||||
# This is done for backwards compatibility with the previous versions
|
||||
# of the MCP server.
|
||||
env_vars = parse_args()
|
||||
for key, value in env_vars.items():
|
||||
os.environ[key] = value
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def server_lifespan(server: Server) -> AsyncIterator[dict]: # noqa
|
||||
"""
|
||||
Context manager to handle the lifespan of the server.
|
||||
This is used to configure the embedding provider and Qdrant connector.
|
||||
All the configuration is now loaded from the environment variables.
|
||||
Settings handle that for us.
|
||||
"""
|
||||
try:
|
||||
# Embedding provider is created with a factory function so we can add
|
||||
@@ -63,7 +53,7 @@ async def server_lifespan(server: Server) -> AsyncIterator[dict]: # noqa
|
||||
pass
|
||||
|
||||
|
||||
mcp = FastMCP("Qdrant", lifespan=server_lifespan)
|
||||
mcp = FastMCP("mcp-server-qdrant", lifespan=server_lifespan)
|
||||
|
||||
|
||||
@mcp.tool(
|
||||
@@ -116,7 +106,3 @@ async def find(query: str, ctx: Context) -> List[str]:
|
||||
for entry in entries:
|
||||
content.append(f"<entry>{entry}</entry>")
|
||||
return content
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import argparse
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
from mcp_server_qdrant.embeddings.types import EmbeddingProviderType
|
||||
|
||||
|
||||
class EmbeddingProviderSettings(BaseSettings):
|
||||
"""
|
||||
Configuration for the embedding provider.
|
||||
"""
|
||||
|
||||
provider_type: str = Field(
|
||||
default="fastembed", validation_alias="EMBEDDING_PROVIDER"
|
||||
provider_type: EmbeddingProviderType = Field(
|
||||
default=EmbeddingProviderType.FASTEMBED,
|
||||
validation_alias="EMBEDDING_PROVIDER",
|
||||
)
|
||||
model_name: str = Field(
|
||||
default="sentence-transformers/all-MiniLM-L6-v2",
|
||||
@@ -36,66 +38,3 @@ class QdrantSettings(BaseSettings):
|
||||
Get the Qdrant location, either the URL or the local path.
|
||||
"""
|
||||
return self.location or self.local_path
|
||||
|
||||
|
||||
def parse_args() -> Dict[str, Any]:
|
||||
"""
|
||||
Parse command line arguments for the MCP server.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Dictionary of parsed arguments
|
||||
"""
|
||||
parser = argparse.ArgumentParser(description="Qdrant MCP Server")
|
||||
|
||||
# Qdrant connection options
|
||||
connection_group = parser.add_mutually_exclusive_group()
|
||||
connection_group.add_argument(
|
||||
"--qdrant-url",
|
||||
help="URL of the Qdrant server, e.g. http://localhost:6333",
|
||||
)
|
||||
connection_group.add_argument(
|
||||
"--qdrant-local-path",
|
||||
help="Path to the local Qdrant database",
|
||||
)
|
||||
|
||||
# Other Qdrant settings
|
||||
parser.add_argument(
|
||||
"--qdrant-api-key",
|
||||
help="API key for the Qdrant server",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--collection-name",
|
||||
help="Name of the collection to use",
|
||||
)
|
||||
|
||||
# Embedding settings
|
||||
parser.add_argument(
|
||||
"--embedding-provider",
|
||||
help="Embedding provider to use (currently only 'fastembed' is supported)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--embedding-model",
|
||||
help="Name of the embedding model to use",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Convert to dictionary and filter out None values
|
||||
args_dict = {k: v for k, v in vars(args).items() if v is not None}
|
||||
|
||||
# Convert argument names to environment variable format
|
||||
env_vars = {}
|
||||
if "qdrant_url" in args_dict:
|
||||
env_vars["QDRANT_URL"] = args_dict["qdrant_url"]
|
||||
if "qdrant_api_key" in args_dict:
|
||||
env_vars["QDRANT_API_KEY"] = args_dict["qdrant_api_key"]
|
||||
if "collection_name" in args_dict:
|
||||
env_vars["COLLECTION_NAME"] = args_dict["collection_name"]
|
||||
if "embedding_model" in args_dict:
|
||||
env_vars["EMBEDDING_MODEL"] = args_dict["embedding_model"]
|
||||
if "embedding_provider" in args_dict:
|
||||
env_vars["EMBEDDING_PROVIDER"] = args_dict["embedding_provider"]
|
||||
if "qdrant_local_path" in args_dict:
|
||||
env_vars["QDRANT_LOCAL_PATH"] = args_dict["qdrant_local_path"]
|
||||
|
||||
return env_vars
|
||||
|
||||
@@ -3,6 +3,7 @@ from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from mcp_server_qdrant.embeddings.types import EmbeddingProviderType
|
||||
from mcp_server_qdrant.settings import EmbeddingProviderSettings, QdrantSettings
|
||||
|
||||
|
||||
@@ -47,15 +48,15 @@ class TestEmbeddingProviderSettings:
|
||||
def test_default_values(self):
|
||||
"""Test default values are set correctly."""
|
||||
settings = EmbeddingProviderSettings()
|
||||
assert settings.provider_type == "fastembed"
|
||||
assert settings.provider_type == EmbeddingProviderType.FASTEMBED
|
||||
assert settings.model_name == "sentence-transformers/all-MiniLM-L6-v2"
|
||||
|
||||
@patch.dict(
|
||||
os.environ,
|
||||
{"EMBEDDING_PROVIDER": "custom_provider", "EMBEDDING_MODEL": "custom_model"},
|
||||
{"EMBEDDING_MODEL": "custom_model"},
|
||||
)
|
||||
def test_custom_values(self):
|
||||
"""Test loading custom values from environment variables."""
|
||||
settings = EmbeddingProviderSettings()
|
||||
assert settings.provider_type == "custom_provider"
|
||||
assert settings.provider_type == EmbeddingProviderType.FASTEMBED
|
||||
assert settings.model_name == "custom_model"
|
||||
|
||||
2
uv.lock
generated
2
uv.lock
generated
@@ -498,7 +498,6 @@ dependencies = [
|
||||
{ name = "fastembed" },
|
||||
{ name = "mcp", extra = ["cli"] },
|
||||
{ name = "qdrant-client" },
|
||||
{ name = "typer" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
@@ -515,7 +514,6 @@ requires-dist = [
|
||||
{ name = "fastembed", specifier = ">=0.6.0" },
|
||||
{ name = "mcp", extras = ["cli"], specifier = ">=1.3.0" },
|
||||
{ name = "qdrant-client", specifier = ">=1.12.0" },
|
||||
{ name = "typer", specifier = ">=0.15.2" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
|
||||
Reference in New Issue
Block a user