forked from innovacion/searchbox
Make MCP server optional dependency
The fastmcp server code is now an optional dependency that can be installed with the "mcp" extra. Core vector search functionality is available without the MCP server dependency.
This commit is contained in:
@@ -5,13 +5,17 @@ description = "Add your description here"
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastmcp>=2.12.3",
|
|
||||||
"qdrant-client==1.13",
|
"qdrant-client==1.13",
|
||||||
"vault-settings>=0.1.0",
|
"vault-settings>=0.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
mcp = [
|
||||||
|
"fastmcp>=2.12.3",
|
||||||
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
vector-search-mcp = "vector_search_mcp:run"
|
vector-search-mcp = "vector_search_mcp.mcp_server:run"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["uv_build"]
|
requires = ["uv_build"]
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
"""Vector Search MCP (Model Context Protocol) Package.
|
|
||||||
|
|
||||||
This package provides a Model Context Protocol server for vector similarity search
|
|
||||||
operations. It supports multiple vector database backends and provides a unified
|
|
||||||
interface for semantic search functionality.
|
|
||||||
|
|
||||||
The package includes:
|
|
||||||
- Abstract engine interface for pluggable vector database backends
|
|
||||||
- Qdrant vector database implementation
|
|
||||||
- Pydantic models for search operations and conditions
|
|
||||||
- MCP server implementation with transport support
|
|
||||||
|
|
||||||
Example:
|
|
||||||
Run the MCP server:
|
|
||||||
>>> from vector_search_mcp import run
|
|
||||||
>>> run("sse") # Start with Server-Sent Events transport
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from fastmcp.server.server import Transport
|
|
||||||
|
|
||||||
from .main import mcp
|
|
||||||
|
|
||||||
|
|
||||||
def run(transport: Transport = "sse"):
|
|
||||||
"""Run the vector search MCP server with the specified transport.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
transport: The transport protocol to use. Either "sse" for Server-Sent Events
|
|
||||||
or "stdio" for standard input/output communication.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
>>> run("sse") # Start with Server-Sent Events
|
|
||||||
>>> run("stdio") # Start with stdio transport
|
|
||||||
|
|
||||||
"""
|
|
||||||
mcp.run(transport=transport)
|
|
||||||
|
|||||||
28
src/vector_search_mcp/client.py
Normal file
28
src/vector_search_mcp/client.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from typing import final
|
||||||
|
|
||||||
|
from .engine import Backend, get_engine
|
||||||
|
from .models import Chunk, Condition
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class Client:
|
||||||
|
def __init__(self, backend: Backend, collection: str):
|
||||||
|
self.engine = get_engine(backend)
|
||||||
|
self.collection = collection
|
||||||
|
|
||||||
|
async def create_index(self, size: int) -> bool:
|
||||||
|
return await self.engine.create_index(self.collection, size)
|
||||||
|
|
||||||
|
async def upload_chunk(self, chunk: Chunk) -> bool:
|
||||||
|
return await self.engine.upload_chunk(self.collection, chunk)
|
||||||
|
|
||||||
|
async def semantic_search(
|
||||||
|
self,
|
||||||
|
embedding: list[float],
|
||||||
|
limit: int = 10,
|
||||||
|
conditions: list[Condition] | None = None,
|
||||||
|
threshold: float | None = None,
|
||||||
|
):
|
||||||
|
return await self.engine.semantic_search(
|
||||||
|
embedding, self.collection, limit, conditions, threshold
|
||||||
|
)
|
||||||
@@ -14,15 +14,16 @@ maintaining a consistent interface for the semantic search workflow.
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
|
|
||||||
from ..models import Condition, SearchRow
|
from ..models import Chunk, Condition, SearchRow
|
||||||
|
|
||||||
ResponseType = TypeVar("ResponseType")
|
ResponseType = TypeVar("ResponseType")
|
||||||
ConditionType = TypeVar("ConditionType")
|
ConditionType = TypeVar("ConditionType")
|
||||||
|
ChunkType = TypeVar("ChunkType")
|
||||||
|
|
||||||
__all__ = ["BaseEngine"]
|
__all__ = ["BaseEngine"]
|
||||||
|
|
||||||
|
|
||||||
class BaseEngine[ResponseType, ConditionType](ABC):
|
class BaseEngine[ResponseType, ConditionType, ChunkType](ABC):
|
||||||
"""Abstract base class for vector search engines.
|
"""Abstract base class for vector search engines.
|
||||||
|
|
||||||
This class defines the interface that all vector search engine implementations
|
This class defines the interface that all vector search engine implementations
|
||||||
@@ -34,6 +35,8 @@ class BaseEngine[ResponseType, ConditionType](ABC):
|
|||||||
For example, list[ScoredPoint] for Qdrant.
|
For example, list[ScoredPoint] for Qdrant.
|
||||||
ConditionType: The backend-specific filter/condition type.
|
ConditionType: The backend-specific filter/condition type.
|
||||||
For example, models.Filter for Qdrant.
|
For example, models.Filter for Qdrant.
|
||||||
|
ChunkType: The backend-specific chunk type.
|
||||||
|
For example, models.Point for Qdrant.
|
||||||
|
|
||||||
The class implements the Template Method pattern where semantic_search()
|
The class implements the Template Method pattern where semantic_search()
|
||||||
orchestrates calls to the abstract methods that subclasses must implement.
|
orchestrates calls to the abstract methods that subclasses must implement.
|
||||||
@@ -192,3 +195,16 @@ class BaseEngine[ResponseType, ConditionType](ABC):
|
|||||||
embedding, collection, limit, transformed_conditions, threshold
|
embedding, collection, limit, transformed_conditions, threshold
|
||||||
)
|
)
|
||||||
return self.transform_response(response)
|
return self.transform_response(response)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def create_index(self, name: str, size: int) -> bool: ...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def transform_chunk(self, chunk: Chunk) -> ChunkType: ...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def run_upload_chunk(self, index_name: str, chunk: ChunkType) -> bool: ...
|
||||||
|
|
||||||
|
async def upload_chunk(self, index_name: str, chunk: Chunk) -> bool:
|
||||||
|
transformed_chunk = self.transform_chunk(chunk)
|
||||||
|
return await self.run_upload_chunk(index_name, transformed_chunk)
|
||||||
|
|||||||
@@ -16,14 +16,16 @@ from typing import final, override
|
|||||||
from qdrant_client import AsyncQdrantClient, models
|
from qdrant_client import AsyncQdrantClient, models
|
||||||
|
|
||||||
from ..config import Settings
|
from ..config import Settings
|
||||||
from ..models import Condition, Match, MatchAny, MatchExclude, SearchRow
|
from ..models import Chunk, Condition, Match, MatchAny, MatchExclude, SearchRow
|
||||||
from .base_engine import BaseEngine
|
from .base_engine import BaseEngine
|
||||||
|
|
||||||
__all__ = ["QdrantEngine"]
|
__all__ = ["QdrantEngine"]
|
||||||
|
|
||||||
|
|
||||||
@final
|
@final
|
||||||
class QdrantEngine(BaseEngine[list[models.ScoredPoint], models.Filter]):
|
class QdrantEngine(
|
||||||
|
BaseEngine[list[models.ScoredPoint], models.Filter, models.PointStruct]
|
||||||
|
):
|
||||||
"""Qdrant vector database engine implementation.
|
"""Qdrant vector database engine implementation.
|
||||||
|
|
||||||
This class provides a concrete implementation of the BaseEngine interface
|
This class provides a concrete implementation of the BaseEngine interface
|
||||||
@@ -195,3 +197,30 @@ class QdrantEngine(BaseEngine[list[models.ScoredPoint], models.Filter]):
|
|||||||
with_vectors=False,
|
with_vectors=False,
|
||||||
score_threshold=threshold,
|
score_threshold=threshold,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def create_index(self, name: str, size: int) -> bool:
|
||||||
|
return await self.client.create_collection(
|
||||||
|
collection_name=name,
|
||||||
|
vectors_config=models.VectorParams(
|
||||||
|
size=size, distance=models.Distance.COSINE
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def run_upload_chunk(
|
||||||
|
self, index_name: str, chunk: models.PointStruct
|
||||||
|
) -> bool:
|
||||||
|
result = await self.client.upsert(
|
||||||
|
collection_name=index_name,
|
||||||
|
points=[chunk],
|
||||||
|
)
|
||||||
|
return result.status == models.UpdateStatus.ACKNOWLEDGED
|
||||||
|
|
||||||
|
@override
|
||||||
|
def transform_chunk(self, chunk: Chunk) -> models.PointStruct:
|
||||||
|
return models.PointStruct(
|
||||||
|
id=chunk.id,
|
||||||
|
vector=chunk.vector,
|
||||||
|
payload=chunk.payload.model_dump(),
|
||||||
|
)
|
||||||
|
|||||||
37
src/vector_search_mcp/mcp_server/__init__.py
Normal file
37
src/vector_search_mcp/mcp_server/__init__.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
"""Vector Search MCP (Model Context Protocol) Package.
|
||||||
|
|
||||||
|
This package provides a Model Context Protocol server for vector similarity search
|
||||||
|
operations. It supports multiple vector database backends and provides a unified
|
||||||
|
interface for semantic search functionality.
|
||||||
|
|
||||||
|
The package includes:
|
||||||
|
- Abstract engine interface for pluggable vector database backends
|
||||||
|
- Qdrant vector database implementation
|
||||||
|
- Pydantic models for search operations and conditions
|
||||||
|
- MCP server implementation with transport support
|
||||||
|
|
||||||
|
Example:
|
||||||
|
Run the MCP server:
|
||||||
|
>>> from vector_search_mcp import run
|
||||||
|
>>> run("sse") # Start with Server-Sent Events transport
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from fastmcp.server.server import Transport
|
||||||
|
|
||||||
|
from .server import mcp
|
||||||
|
|
||||||
|
|
||||||
|
def run(transport: Transport = "sse"):
|
||||||
|
"""Run the vector search MCP server with the specified transport.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
transport: The transport protocol to use. Either "sse" for Server-Sent Events
|
||||||
|
or "stdio" for standard input/output communication.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> run("sse") # Start with Server-Sent Events
|
||||||
|
>>> run("stdio") # Start with stdio transport
|
||||||
|
|
||||||
|
"""
|
||||||
|
mcp.run(transport=transport)
|
||||||
@@ -18,7 +18,7 @@ Example:
|
|||||||
|
|
||||||
from fastmcp import FastMCP
|
from fastmcp import FastMCP
|
||||||
|
|
||||||
from .engine import Backend, get_engine
|
from ..engine import Backend, get_engine
|
||||||
|
|
||||||
mcp = FastMCP("Vector Search MCP")
|
mcp = FastMCP("Vector Search MCP")
|
||||||
|
|
||||||
@@ -119,3 +119,15 @@ class MatchExclude(Condition):
|
|||||||
|
|
||||||
key: str
|
key: str
|
||||||
exclude: list[str]
|
exclude: list[str]
|
||||||
|
|
||||||
|
|
||||||
|
class ChunkData(BaseModel):
|
||||||
|
page_content: str
|
||||||
|
filename: str
|
||||||
|
page: int
|
||||||
|
|
||||||
|
|
||||||
|
class Chunk(BaseModel):
|
||||||
|
id: str
|
||||||
|
vector: list[float]
|
||||||
|
payload: ChunkData
|
||||||
|
|||||||
9
uv.lock
generated
9
uv.lock
generated
@@ -1620,11 +1620,15 @@ name = "vector-search-mcp"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "fastmcp" },
|
|
||||||
{ name = "qdrant-client" },
|
{ name = "qdrant-client" },
|
||||||
{ name = "vault-settings" },
|
{ name = "vault-settings" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
mcp = [
|
||||||
|
{ name = "fastmcp" },
|
||||||
|
]
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
{ name = "fastembed" },
|
{ name = "fastembed" },
|
||||||
@@ -1636,10 +1640,11 @@ dev = [
|
|||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "fastmcp", specifier = ">=2.12.3" },
|
{ name = "fastmcp", marker = "extra == 'mcp'", specifier = ">=2.12.3" },
|
||||||
{ name = "qdrant-client", specifier = "==1.13" },
|
{ name = "qdrant-client", specifier = "==1.13" },
|
||||||
{ name = "vault-settings", specifier = ">=0.1.0" },
|
{ name = "vault-settings", specifier = ">=0.1.0" },
|
||||||
]
|
]
|
||||||
|
provides-extras = ["mcp"]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [
|
dev = [
|
||||||
|
|||||||
Reference in New Issue
Block a user