Initial commit

This commit is contained in:
2025-09-25 23:39:12 +00:00
commit 3ec2687226
12 changed files with 1723 additions and 0 deletions

10
.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.13

0
README.md Normal file
View File

26
pyproject.toml Normal file
View File

@@ -0,0 +1,26 @@
[project]
name = "qdrant-mcp"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"fastmcp>=2.12.3",
"qdrant-client==1.13",
"vault-settings>=0.1.0",
]
[project.scripts]
qdrant-mcp = "qdrant_mcp:run"
[build-system]
requires = ["uv_build"]
build-backend = "uv_build"
[tool.vault-settings]
secret = "qdrant-mcp"
[dependency-groups]
dev = [
"fastembed>=0.7.3",
]

16
scripts/client.py Normal file
View File

@@ -0,0 +1,16 @@
import asyncio
from fastmcp import Client
from fastembed import TextEmbedding
embedding_model = TextEmbedding()
client = Client("http://localhost:8000/sse")
async def call_tool(input: str, collection: str):
embedding: list[float] = list(embedding_model.embed(input))[0].tolist()
async with client:
result = await client.call_tool("semantic_search", {"embedding": embedding, "collection": collection})
print(result)
asyncio.run(call_tool("Dime sobre las cucarachas", "dummy_collection"))

View File

@@ -0,0 +1,40 @@
from qdrant_client import QdrantClient
from fastembed import TextEmbedding
from qdrant_client.models import Distance, VectorParams, PointStruct
from qdrant_mcp.config import Settings
settings = Settings()
embedding_model = TextEmbedding()
client = QdrantClient(url=settings.url, api_key=settings.api_key)
documents: list[str] = [
"Rick es el mas guapo",
"Los pulpos tienen tres corazones y sangre azul",
"Las cucarachas pueden vivir hasta una semana sin cabeza",
"Los koalas tienen huellas dactilares casi idénticas a las humanas",
"La miel nunca se echa a perder, incluso después de miles de años"
]
embeddings = list(embedding_model.embed(documents))
size = len(embeddings[0])
_ = client.recreate_collection(
collection_name="dummy_collection",
vectors_config=VectorParams(
distance=Distance.COSINE,
size=size
)
)
for idx, (emb, document) in enumerate(zip(embeddings, documents)):
_ = client.upsert(
collection_name="dummy_collection",
points=[
PointStruct(
id=idx,
vector=emb,
payload={
"text": document
}
)
]
)

View File

@@ -0,0 +1,4 @@
from .main import mcp
def run():
mcp.run(transport="sse")

7
src/qdrant_mcp/config.py Normal file
View File

@@ -0,0 +1,7 @@
from pydantic import Field
from vault_settings import VaultSettings
class Settings(VaultSettings):
url: str = Field(...)
api_key: str | None = None

35
src/qdrant_mcp/engine.py Normal file
View File

@@ -0,0 +1,35 @@
from collections.abc import Sequence
from typing import Any
from qdrant_client import AsyncQdrantClient, models
from .config import Settings
from .models import SearchRow
class QdrantEngine:
def __init__(self) -> None:
self.settings = Settings()
self.client = AsyncQdrantClient(
url=self.settings.url, api_key=self.settings.api_key
)
async def semantic_search(
self,
embedding: Sequence[float] | models.NamedVector,
collection: str,
limit: int = 10,
conditions: Any | None = None,
threshold: float | None = None,
) -> list[SearchRow]:
points = await self.client.search(
collection_name=collection,
query_vector=embedding,
query_filter=conditions,
limit=limit,
with_payload=True,
with_vectors=False,
score_threshold=threshold,
)
return [SearchRow(chunk_id=str(point.id), score=point.score, payload=point.payload) for point in points if point.payload is not None]

9
src/qdrant_mcp/main.py Normal file
View File

@@ -0,0 +1,9 @@
from fastmcp import FastMCP
from .engine import QdrantEngine
mcp = FastMCP("Qdrant MCP")
engine = QdrantEngine()
_ = mcp.tool(engine.semantic_search)

7
src/qdrant_mcp/models.py Normal file
View File

@@ -0,0 +1,7 @@
from typing import Any
from pydantic import BaseModel
class SearchRow(BaseModel):
chunk_id: str
score: float
payload: dict[str, Any]

1568
uv.lock generated Normal file

File diff suppressed because it is too large Load Diff