Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
- Actor names are stored as a top-level payload field and indexed in Qdrant to enable actor and year-based filtering.
- Dense and sparse embedding model names are configurable via `DENSE_MODEL` and
`SPARSE_MODEL` environment variables or the corresponding CLI options.
- Hybrid search uses Qdrant's built-in `FusionQuery` with reciprocal rank fusion
to combine dense and sparse results before optional cross-encoder reranking.

## User Queries
The project should handle natural-language searches and recommendations such as:
Expand Down
24 changes: 13 additions & 11 deletions mcp_plex/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from pydantic import Field
from qdrant_client import models
from qdrant_client.async_qdrant_client import AsyncQdrantClient
from qdrant_client.hybrid.fusion import reciprocal_rank_fusion

try:
from sentence_transformers import CrossEncoder
Expand Down Expand Up @@ -180,23 +179,26 @@ async def search_media(
dense_doc = models.Document(text=query, model=_DENSE_MODEL_NAME)
sparse_doc = models.Document(text=query, model=_SPARSE_MODEL_NAME)
candidate_limit = limit * 3 if _reranker is not None else limit
dense_resp, sparse_resp = await asyncio.gather(
_client.query_points(
collection_name="media-items",
query=dense_doc,
prefetch = [
models.Prefetch(
query=models.NearestQuery(nearest=dense_doc),
using="dense",
limit=candidate_limit,
with_payload=True,
),
_client.query_points(
collection_name="media-items",
query=sparse_doc,
models.Prefetch(
query=models.NearestQuery(nearest=sparse_doc),
using="sparse",
limit=candidate_limit,
with_payload=True,
),
]
res = await _client.query_points(
collection_name="media-items",
query=models.FusionQuery(fusion=models.Fusion.RRF),
prefetch=prefetch,
limit=candidate_limit,
with_payload=True,
)
hits = reciprocal_rank_fusion([dense_resp.points, sparse_resp.points], limit=candidate_limit)
hits = res.points

async def _prefetch(hit: models.ScoredPoint) -> None:
data = hit.payload["data"]
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "mcp-plex"
version = "0.26.3"
version = "0.26.4"

description = "Plex-Oriented Model Context Protocol Server"
requires-python = ">=3.11,<3.13"
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.