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: 1 addition & 1 deletion docker/pyproject.deps.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "mcp-plex"
version = "1.0.20"
version = "1.0.22"
requires-python = ">=3.11,<3.13"
dependencies = [
"fastmcp>=2.11.2",
Expand Down
123 changes: 70 additions & 53 deletions mcp_plex/server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -820,43 +820,18 @@ def _listify(value: Sequence[str] | str | None) -> list[str]:
)
)

prefetch_entries: list[models.Prefetch] = []
for name, doc in vector_queries:
prefetch_entries.append(
models.Prefetch(
query=models.NearestQuery(nearest=doc),
using=name,
limit=limit,
)
)

if len(prefetch_entries) > 1:
candidate_limit = limit * 3
prefetch_entries = [
models.Prefetch(query=p.query, using=p.using, limit=candidate_limit)
for p in prefetch_entries
]
query_obj: models.Query = models.FusionQuery(fusion=models.Fusion.RRF)
using_param = None
prefetch_param: Sequence[models.Prefetch] | None = prefetch_entries
elif prefetch_entries:
query_obj = prefetch_entries[0].query
using_param = prefetch_entries[0].using
prefetch_param = None
else:
query_obj = None
using_param = None
prefetch_param = None

must: list[models.FieldCondition] = []
keyword_prefetch_conditions: list[models.FieldCondition] = []

if title:
must.append(models.FieldCondition(key="title", match=models.MatchText(text=title)))
media_type = type
if media_type:
must.append(
models.FieldCondition(key="type", match=models.MatchValue(value=media_type))
condition = models.FieldCondition(
key="type", match=models.MatchValue(value=media_type)
)
must.append(condition)
keyword_prefetch_conditions.append(condition)
if year is not None:
must.append(models.FieldCondition(key="year", match=models.MatchValue(value=year)))
if year_from is not None or year_to is not None:
Expand All @@ -875,30 +850,42 @@ def _listify(value: Sequence[str] | str | None) -> list[str]:
must.append(models.FieldCondition(key="added_at", range=models.Range(**rng_at)))

for actor in _listify(actors):
must.append(models.FieldCondition(key="actors", match=models.MatchValue(value=actor)))
condition = models.FieldCondition(
key="actors", match=models.MatchValue(value=actor)
)
must.append(condition)
keyword_prefetch_conditions.append(condition)
for director in _listify(directors):
must.append(
models.FieldCondition(key="directors", match=models.MatchValue(value=director))
condition = models.FieldCondition(
key="directors", match=models.MatchValue(value=director)
)
must.append(condition)
keyword_prefetch_conditions.append(condition)
for writer in _listify(writers):
must.append(
models.FieldCondition(key="writers", match=models.MatchValue(value=writer))
condition = models.FieldCondition(
key="writers", match=models.MatchValue(value=writer)
)
must.append(condition)
keyword_prefetch_conditions.append(condition)
for genre in _listify(genres):
must.append(models.FieldCondition(key="genres", match=models.MatchValue(value=genre)))
condition = models.FieldCondition(
key="genres", match=models.MatchValue(value=genre)
)
must.append(condition)
keyword_prefetch_conditions.append(condition)
for collection in _listify(collections):
must.append(
models.FieldCondition(
key="collections", match=models.MatchValue(value=collection)
)
condition = models.FieldCondition(
key="collections", match=models.MatchValue(value=collection)
)
must.append(condition)
keyword_prefetch_conditions.append(condition)

if show_title:
must.append(
models.FieldCondition(
key="show_title", match=models.MatchValue(value=show_title)
)
condition = models.FieldCondition(
key="show_title", match=models.MatchValue(value=show_title)
)
must.append(condition)
keyword_prefetch_conditions.append(condition)
if season_number is not None:
must.append(
models.FieldCondition(
Expand All @@ -924,18 +911,18 @@ def _listify(value: Sequence[str] | str | None) -> list[str]:
must.append(models.FieldCondition(key="reviews", match=models.MatchText(text=reviews)))

if plex_rating_key:
must.append(
models.FieldCondition(
key="data.plex.rating_key",
match=models.MatchValue(value=plex_rating_key),
)
condition = models.FieldCondition(
key="data.plex.rating_key",
match=models.MatchValue(value=plex_rating_key),
)
must.append(condition)
keyword_prefetch_conditions.append(condition)
if imdb_id:
must.append(
models.FieldCondition(
key="data.imdb.id", match=models.MatchValue(value=imdb_id)
)
condition = models.FieldCondition(
key="data.imdb.id", match=models.MatchValue(value=imdb_id)
)
must.append(condition)
keyword_prefetch_conditions.append(condition)
if tmdb_id is not None:
must.append(
models.FieldCondition(
Expand All @@ -947,6 +934,36 @@ def _listify(value: Sequence[str] | str | None) -> list[str]:
if must:
filter_obj = models.Filter(must=must)

prefetch_filter: models.Filter | None = None
if keyword_prefetch_conditions:
prefetch_filter = models.Filter(must=keyword_prefetch_conditions)
if filter_obj is None:
filter_obj = models.Filter(must=keyword_prefetch_conditions)

query_obj: models.Query | None = None
using_param: str | None = None
prefetch_param: Sequence[models.Prefetch] | None = None
if vector_queries:
candidate_limit = limit * 3 if len(vector_queries) > 1 else limit
prefetch_entries = [
models.Prefetch(
query=models.NearestQuery(nearest=doc),
using=name,
limit=candidate_limit,
filter=prefetch_filter,
)
for name, doc in vector_queries
]
if len(prefetch_entries) > 1:
query_obj = models.FusionQuery(fusion=models.Fusion.RRF)
using_param = None
prefetch_param = prefetch_entries
else:
prefetch_entry = prefetch_entries[0]
query_obj = prefetch_entry.query
using_param = prefetch_entry.using
prefetch_param = None

if query_obj is None:
query_obj = models.SampleQuery(sample=models.Sample.RANDOM)

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 = "1.0.20"
version = "1.0.22"

description = "Plex-Oriented Model Context Protocol Server"
requires-python = ">=3.11,<3.13"
Expand Down
46 changes: 46 additions & 0 deletions tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,52 @@ async def fake_query_points(*args, **kwargs):
assert query_filter is not None
assert len(query_filter.must) >= 10
assert isinstance(captured["query"], models.FusionQuery)
prefetch = captured["prefetch"]
assert prefetch is not None
expected_prefetch_keys = {
"type",
"actors",
"directors",
"writers",
"genres",
"collections",
"show_title",
"data.plex.rating_key",
"data.imdb.id",
}
for entry in prefetch:
assert entry.filter is not None
keys = {condition.key for condition in entry.filter.must}
assert keys == expected_prefetch_keys


def test_query_media_filters_without_vectors(monkeypatch):
with _load_server(monkeypatch) as module:
captured: dict[str, object] = {}

async def fake_query_points(*args, **kwargs):
captured.update(kwargs)
payload = {"title": "Result", "plex": {"rating_key": "1"}}
return types.SimpleNamespace(
points=[types.SimpleNamespace(payload=payload, score=1.0)]
)

monkeypatch.setattr(module.server.qdrant_client, "query_points", fake_query_points)

result = asyncio.run(
module.query_media.fn(
type="movie",
actors=["Actor"],
limit=1,
)
)

assert result and result[0]["plex"]["rating_key"] == "1"
query_filter = captured["query_filter"]
assert query_filter is not None
keys = {condition.key for condition in query_filter.must}
assert keys == {"type", "actors"}
assert captured["prefetch"] is None


def test_openapi_schema_tool_without_params(monkeypatch):
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.