Skip to content

Commit b9f773e

Browse files
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
1 parent 349abbb commit b9f773e

File tree

11 files changed

+81
-158
lines changed

11 files changed

+81
-158
lines changed

README.md

Lines changed: 50 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
# mcp-server-qdrant: A Qdrant MCP server
22
[![smithery badge](https://smithery.ai/badge/mcp-server-qdrant)](https://smithery.ai/protocol/mcp-server-qdrant)
33

4-
> 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.
4+
> The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol that enables
5+
> seamless integration between LLM applications and external data sources and tools. Whether you’re building an
6+
> AI-powered IDE, enhancing a chat interface, or creating custom AI workflows, MCP provides a standardized way to
7+
> connect LLMs with the context they need.
58
69
This repository is an example of how to create a MCP server for [Qdrant](https://qdrant.tech/), a vector search engine.
710

@@ -27,18 +30,18 @@ It acts as a semantic memory layer on top of the Qdrant database.
2730
- `query` (string): Query to retrieve a memory
2831
- Returns: Memories stored in the Qdrant database as separate messages
2932

30-
## Installation
33+
## Installation in Claude Desktop
3134

32-
### Using uv (recommended)
35+
### Using mcp (recommended)
3336

34-
When using [`uv`](https://docs.astral.sh/uv/) no specific installation is needed to directly run *mcp-server-qdrant*.
37+
When using [`mcp`](https://github.com/modelcontextprotocol/python-sdk) no specific installation is needed to directly run *mcp-server-qdrant*.
3538

3639
```shell
37-
uv run mcp-server-qdrant \
38-
--qdrant-url "http://localhost:6333" \
39-
--qdrant-api-key "your_api_key" \
40-
--collection-name "my_collection" \
41-
--embedding-model "sentence-transformers/all-MiniLM-L6-v2"
40+
mcp install src/mcp_server_qdrant/server.py \
41+
-v QDRANT_URL="http://localhost:6333" \
42+
-v QDRANT_API_KEY="your_api_key" \
43+
-v COLLECTION_NAME="my_collection" \
44+
-v EMBEDDING_MODEL="sentence-transformers/all-MiniLM-L6-v2"
4245
```
4346

4447
### Installing via Smithery
@@ -49,70 +52,68 @@ To install Qdrant MCP Server for Claude Desktop automatically via [Smithery](htt
4952
npx @smithery/cli install mcp-server-qdrant --client claude
5053
```
5154

52-
## Usage with Claude Desktop
55+
### Manual configuration
5356

54-
To use this server with the Claude Desktop app, add the following configuration to the "mcpServers" section of your `claude_desktop_config.json`:
57+
To use this server with the Claude Desktop app, add the following configuration to the "mcpServers" section of your
58+
`claude_desktop_config.json`:
5559

5660
```json
5761
{
5862
"qdrant": {
5963
"command": "uvx",
60-
"args": [
61-
"mcp-server-qdrant",
62-
"--qdrant-url",
63-
"http://localhost:6333",
64-
"--qdrant-api-key",
65-
"your_api_key",
66-
"--collection-name",
67-
"your_collection_name"
68-
]
64+
"args": ["mcp-server-qdrant"],
65+
"env": {
66+
"QDRANT_URL": "http://localhost:6333",
67+
"QDRANT_API_KEY": "your_api_key",
68+
"COLLECTION_NAME": "your_collection_name",
69+
"EMBEDDING_MODEL": "sentence-transformers/all-MiniLM-L6-v2"
70+
}
6971
}
7072
}
7173
```
7274

73-
Replace `http://localhost:6333`, `your_api_key` and `your_collection_name` with your Qdrant server URL, Qdrant API key
74-
and collection name, respectively. The use of API key is optional, but recommended for security reasons, and depends on
75-
the Qdrant server configuration.
76-
77-
This MCP server will automatically create a collection with the specified name if it doesn't exist.
78-
79-
By default, the server will use the `sentence-transformers/all-MiniLM-L6-v2` embedding model to encode memories.
80-
For the time being, only [FastEmbed](https://qdrant.github.io/fastembed/) models are supported, and you can change it
81-
by passing the `--embedding-model` argument to the server.
82-
83-
### Using the local mode of Qdrant
84-
85-
To use a local mode of Qdrant, you can specify the path to the database using the `--qdrant-local-path` argument:
75+
For local Qdrant mode:
8676

8777
```json
8878
{
8979
"qdrant": {
9080
"command": "uvx",
91-
"args": [
92-
"mcp-server-qdrant",
93-
"--qdrant-local-path",
94-
"/path/to/qdrant/database",
95-
"--collection-name",
96-
"your_collection_name"
97-
]
81+
"args": ["mcp-server-qdrant"],
82+
"env": {
83+
"QDRANT_LOCAL_PATH": "/path/to/qdrant/database",
84+
"COLLECTION_NAME": "your_collection_name",
85+
"EMBEDDING_MODEL": "sentence-transformers/all-MiniLM-L6-v2"
86+
}
9887
}
9988
}
10089
```
10190

102-
It will run Qdrant local mode inside the same process as the MCP server. Although it is not recommended for production.
91+
This MCP server will automatically create a collection with the specified name if it doesn't exist.
92+
93+
By default, the server will use the `sentence-transformers/all-MiniLM-L6-v2` embedding model to encode memories.
94+
For the time being, only [FastEmbed](https://qdrant.github.io/fastembed/) models are supported.
95+
96+
### Support for other tools
97+
98+
This MCP server can be used with any MCP-compatible client. For example, you can use it with
99+
[Cursor](https://docs.cursor.com/context/model-context-protocol), which provides built-in support for the Model Context
100+
Protocol.
103101

104102
## Environment Variables
105103

106-
The configuration of the server can be also done using environment variables:
104+
The configuration of the server is done using environment variables:
107105

108106
- `QDRANT_URL`: URL of the Qdrant server, e.g. `http://localhost:6333`
109-
- `QDRANT_API_KEY`: API key for the Qdrant server
110-
- `COLLECTION_NAME`: Name of the collection to use
111-
- `EMBEDDING_MODEL`: Name of the embedding model to use
107+
- `QDRANT_API_KEY`: API key for the Qdrant server (optional, depends on Qdrant server configuration)
108+
- `COLLECTION_NAME`: Name of the collection to use (required)
109+
- `EMBEDDING_MODEL`: Name of the embedding model to use (default: `sentence-transformers/all-MiniLM-L6-v2`)
112110
- `EMBEDDING_PROVIDER`: Embedding provider to use (currently only "fastembed" is supported)
113-
- `QDRANT_LOCAL_PATH`: Path to the local Qdrant database
111+
- `QDRANT_LOCAL_PATH`: Path to the local Qdrant database (alternative to `QDRANT_URL`)
112+
113+
Note: You cannot provide both `QDRANT_URL` and `QDRANT_LOCAL_PATH` at the same time.
114114

115-
You cannot provide `QDRANT_URL` and `QDRANT_LOCAL_PATH` at the same time.
115+
> [!IMPORTANT]
116+
> Command-line arguments are not supported anymore! Please use environment variables for all configuration.
116117
117118
## Contributing
118119

@@ -126,9 +127,8 @@ servers. It runs both a client UI (default port 5173) and an MCP proxy server (d
126127
your browser to use the inspector.
127128

128129
```shell
129-
npx @modelcontextprotocol/inspector uv run mcp-server-qdrant \
130-
--collection-name test \
131-
--qdrant-local-path /tmp/qdrant-local-test
130+
QDRANT_URL=":memory:" COLLECTION_NAME="test" \
131+
mcp dev src/mcp_server_qdrant/server.py
132132
```
133133

134134
Once started, open your browser to http://localhost:5173 to access the inspector interface.

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ dependencies = [
99
"mcp[cli]>=1.3.0",
1010
"fastembed>=0.6.0",
1111
"qdrant-client>=1.12.0",
12-
"typer>=0.15.2",
1312
]
1413

1514
[build-system]
@@ -26,7 +25,7 @@ dev-dependencies = [
2625
]
2726

2827
[project.scripts]
29-
mcp-server-qdrant = "mcp_server_qdrant:main"
28+
mcp-server-qdrant = "mcp_server_qdrant.main:main"
3029

3130
[tool.pytest.ini_options]
3231
testpaths = ["tests"]

src/mcp_server_qdrant/__init__.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +0,0 @@
1-
from . import server
2-
3-
4-
def main():
5-
"""Main entry point for the package."""
6-
server.mcp.run()
7-
8-
9-
# Optionally expose other important items at package level
10-
__all__ = ["main", "server"]
Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +0,0 @@
1-
from .base import EmbeddingProvider
2-
from .factory import create_embedding_provider
3-
from .fastembed import FastEmbedProvider
4-
5-
__all__ = ["EmbeddingProvider", "FastEmbedProvider", "create_embedding_provider"]

src/mcp_server_qdrant/embeddings/factory.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from mcp_server_qdrant.embeddings import EmbeddingProvider
2+
from mcp_server_qdrant.embeddings.types import EmbeddingProviderType
23
from mcp_server_qdrant.settings import EmbeddingProviderSettings
34

45

@@ -8,7 +9,7 @@ def create_embedding_provider(settings: EmbeddingProviderSettings) -> EmbeddingP
89
:param settings: The settings for the embedding provider.
910
:return: An instance of the specified embedding provider.
1011
"""
11-
if settings.provider_type.lower() == "fastembed":
12+
if settings.provider_type == EmbeddingProviderType.FASTEMBED:
1213
from mcp_server_qdrant.embeddings.fastembed import FastEmbedProvider
1314

1415
return FastEmbedProvider(settings.model_name)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from enum import Enum
2+
3+
4+
class EmbeddingProviderType(Enum):
5+
FASTEMBED = "fastembed"

src/mcp_server_qdrant/main.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from mcp_server_qdrant.server import mcp
2+
3+
4+
def main():
5+
"""
6+
Main entry point for the mcp-server-qdrant script defined
7+
in pyproject.toml. It runs the MCP server.
8+
"""
9+
mcp.run()

src/mcp_server_qdrant/server.py

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import logging
2-
import os
32
from contextlib import asynccontextmanager
43
from typing import AsyncIterator, List
54

@@ -8,27 +7,18 @@
87

98
from mcp_server_qdrant.embeddings.factory import create_embedding_provider
109
from mcp_server_qdrant.qdrant import QdrantConnector
11-
from mcp_server_qdrant.settings import (
12-
EmbeddingProviderSettings,
13-
QdrantSettings,
14-
parse_args,
15-
)
10+
from mcp_server_qdrant.settings import EmbeddingProviderSettings, QdrantSettings
1611

1712
logger = logging.getLogger(__name__)
1813

19-
# Parse command line arguments and set them as environment variables.
20-
# This is done for backwards compatibility with the previous versions
21-
# of the MCP server.
22-
env_vars = parse_args()
23-
for key, value in env_vars.items():
24-
os.environ[key] = value
25-
2614

2715
@asynccontextmanager
2816
async def server_lifespan(server: Server) -> AsyncIterator[dict]: # noqa
2917
"""
3018
Context manager to handle the lifespan of the server.
3119
This is used to configure the embedding provider and Qdrant connector.
20+
All the configuration is now loaded from the environment variables.
21+
Settings handle that for us.
3222
"""
3323
try:
3424
# 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
6353
pass
6454

6555

66-
mcp = FastMCP("Qdrant", lifespan=server_lifespan)
56+
mcp = FastMCP("mcp-server-qdrant", lifespan=server_lifespan)
6757

6858

6959
@mcp.tool(
@@ -116,7 +106,3 @@ async def find(query: str, ctx: Context) -> List[str]:
116106
for entry in entries:
117107
content.append(f"<entry>{entry}</entry>")
118108
return content
119-
120-
121-
if __name__ == "__main__":
122-
mcp.run()

src/mcp_server_qdrant/settings.py

Lines changed: 6 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1-
import argparse
2-
from typing import Any, Dict, Optional
1+
from typing import Optional
32

43
from pydantic import Field
54
from pydantic_settings import BaseSettings
65

6+
from mcp_server_qdrant.embeddings.types import EmbeddingProviderType
7+
78

89
class EmbeddingProviderSettings(BaseSettings):
910
"""
1011
Configuration for the embedding provider.
1112
"""
1213

13-
provider_type: str = Field(
14-
default="fastembed", validation_alias="EMBEDDING_PROVIDER"
14+
provider_type: EmbeddingProviderType = Field(
15+
default=EmbeddingProviderType.FASTEMBED,
16+
validation_alias="EMBEDDING_PROVIDER",
1517
)
1618
model_name: str = Field(
1719
default="sentence-transformers/all-MiniLM-L6-v2",
@@ -36,66 +38,3 @@ def get_qdrant_location(self) -> str:
3638
Get the Qdrant location, either the URL or the local path.
3739
"""
3840
return self.location or self.local_path
39-
40-
41-
def parse_args() -> Dict[str, Any]:
42-
"""
43-
Parse command line arguments for the MCP server.
44-
45-
Returns:
46-
Dict[str, Any]: Dictionary of parsed arguments
47-
"""
48-
parser = argparse.ArgumentParser(description="Qdrant MCP Server")
49-
50-
# Qdrant connection options
51-
connection_group = parser.add_mutually_exclusive_group()
52-
connection_group.add_argument(
53-
"--qdrant-url",
54-
help="URL of the Qdrant server, e.g. http://localhost:6333",
55-
)
56-
connection_group.add_argument(
57-
"--qdrant-local-path",
58-
help="Path to the local Qdrant database",
59-
)
60-
61-
# Other Qdrant settings
62-
parser.add_argument(
63-
"--qdrant-api-key",
64-
help="API key for the Qdrant server",
65-
)
66-
parser.add_argument(
67-
"--collection-name",
68-
help="Name of the collection to use",
69-
)
70-
71-
# Embedding settings
72-
parser.add_argument(
73-
"--embedding-provider",
74-
help="Embedding provider to use (currently only 'fastembed' is supported)",
75-
)
76-
parser.add_argument(
77-
"--embedding-model",
78-
help="Name of the embedding model to use",
79-
)
80-
81-
args = parser.parse_args()
82-
83-
# Convert to dictionary and filter out None values
84-
args_dict = {k: v for k, v in vars(args).items() if v is not None}
85-
86-
# Convert argument names to environment variable format
87-
env_vars = {}
88-
if "qdrant_url" in args_dict:
89-
env_vars["QDRANT_URL"] = args_dict["qdrant_url"]
90-
if "qdrant_api_key" in args_dict:
91-
env_vars["QDRANT_API_KEY"] = args_dict["qdrant_api_key"]
92-
if "collection_name" in args_dict:
93-
env_vars["COLLECTION_NAME"] = args_dict["collection_name"]
94-
if "embedding_model" in args_dict:
95-
env_vars["EMBEDDING_MODEL"] = args_dict["embedding_model"]
96-
if "embedding_provider" in args_dict:
97-
env_vars["EMBEDDING_PROVIDER"] = args_dict["embedding_provider"]
98-
if "qdrant_local_path" in args_dict:
99-
env_vars["QDRANT_LOCAL_PATH"] = args_dict["qdrant_local_path"]
100-
101-
return env_vars

tests/test_config.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import pytest
55

6+
from mcp_server_qdrant.embeddings.types import EmbeddingProviderType
67
from mcp_server_qdrant.settings import EmbeddingProviderSettings, QdrantSettings
78

89

@@ -47,15 +48,15 @@ class TestEmbeddingProviderSettings:
4748
def test_default_values(self):
4849
"""Test default values are set correctly."""
4950
settings = EmbeddingProviderSettings()
50-
assert settings.provider_type == "fastembed"
51+
assert settings.provider_type == EmbeddingProviderType.FASTEMBED
5152
assert settings.model_name == "sentence-transformers/all-MiniLM-L6-v2"
5253

5354
@patch.dict(
5455
os.environ,
55-
{"EMBEDDING_PROVIDER": "custom_provider", "EMBEDDING_MODEL": "custom_model"},
56+
{"EMBEDDING_MODEL": "custom_model"},
5657
)
5758
def test_custom_values(self):
5859
"""Test loading custom values from environment variables."""
5960
settings = EmbeddingProviderSettings()
60-
assert settings.provider_type == "custom_provider"
61+
assert settings.provider_type == EmbeddingProviderType.FASTEMBED
6162
assert settings.model_name == "custom_model"

0 commit comments

Comments
 (0)