Skip to content

Commit 5c4873a

Browse files
authored
feat(server): support similarity seeds in query media (#148)
1 parent 0886058 commit 5c4873a

File tree

5 files changed

+78
-20
lines changed

5 files changed

+78
-20
lines changed

docker/pyproject.deps.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "mcp-plex"
3-
version = "2.0.9"
3+
version = "2.0.10"
44
requires-python = ">=3.11,<3.13"
55
dependencies = [
66
"fastmcp>=2.11.2",

mcp_plex/server/tools/media_library.py

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,13 @@ async def query_media(
317317
int | None,
318318
Field(description="Match a TMDb identifier", examples=[568467]),
319319
] = None,
320+
similar_to: Annotated[
321+
str | Sequence[str] | None,
322+
Field(
323+
description="Recommend candidates similar to these identifiers",
324+
examples=[["49915"], "tt8367814"],
325+
),
326+
] = None,
320327
limit: Annotated[
321328
int,
322329
Field(
@@ -337,24 +344,37 @@ def _listify(value: Sequence[str] | str | None) -> list[str]:
337344
return [v for v in value if isinstance(v, str) and v]
338345

339346
vector_queries: list[tuple[str, models.Document]] = []
340-
if dense_query:
341-
vector_queries.append(
342-
(
343-
"dense",
344-
models.Document(
345-
text=dense_query, model=server.settings.dense_model
346-
),
347+
positive_point_ids: list[Any] = []
348+
similar_identifiers = _listify(similar_to)
349+
if similar_identifiers:
350+
for identifier in similar_identifiers:
351+
records = await media_helpers._find_records(
352+
server, identifier, limit=1
347353
)
348-
)
349-
if sparse_query:
350-
vector_queries.append(
351-
(
352-
"sparse",
353-
models.Document(
354-
text=sparse_query, model=server.settings.sparse_model
355-
),
354+
for record in records:
355+
if record.id is not None:
356+
positive_point_ids.append(record.id)
357+
if not positive_point_ids:
358+
return []
359+
if not positive_point_ids:
360+
if dense_query:
361+
vector_queries.append(
362+
(
363+
"dense",
364+
models.Document(
365+
text=dense_query, model=server.settings.dense_model
366+
),
367+
)
368+
)
369+
if sparse_query:
370+
vector_queries.append(
371+
(
372+
"sparse",
373+
models.Document(
374+
text=sparse_query, model=server.settings.sparse_model
375+
),
376+
)
356377
)
357-
)
358378

359379
must: list[models.FieldCondition] = []
360380
keyword_prefetch_conditions: list[models.FieldCondition] = []
@@ -503,7 +523,20 @@ def _listify(value: Sequence[str] | str | None) -> list[str]:
503523
query_obj: models.Query | None = None
504524
using_param: str | None = None
505525
prefetch_param: Sequence[models.Prefetch] | None = None
506-
if vector_queries:
526+
prefetch_entries: list[models.Prefetch] = []
527+
if positive_point_ids:
528+
recommend_query = models.RecommendQuery(
529+
recommend=models.RecommendInput(positive=positive_point_ids)
530+
)
531+
prefetch_entries.append(
532+
models.Prefetch(
533+
query=recommend_query,
534+
using="dense",
535+
limit=limit,
536+
filter=prefetch_filter,
537+
)
538+
)
539+
if not positive_point_ids and vector_queries:
507540
candidate_limit = limit * 3 if len(vector_queries) > 1 else limit
508541
prefetch_entries = [
509542
models.Prefetch(
@@ -514,6 +547,8 @@ def _listify(value: Sequence[str] | str | None) -> list[str]:
514547
)
515548
for name, doc in vector_queries
516549
]
550+
551+
if prefetch_entries:
517552
if len(prefetch_entries) > 1:
518553
query_obj = models.FusionQuery(fusion=models.Fusion.RRF)
519554
using_param = None

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "mcp-plex"
7-
version = "2.0.9"
7+
version = "2.0.10"
88

99
description = "Plex-Oriented Model Context Protocol Server"
1010
requires-python = ">=3.11,<3.13"

tests/test_server.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,29 @@ def test_server_tools(monkeypatch):
150150
)
151151
assert episode_structured[0]["show_title"] == "Alien: Earth"
152152

153+
similar_structured = asyncio.run(
154+
server.query_media.fn(
155+
similar_to=["49915"],
156+
type="episode",
157+
limit=3,
158+
)
159+
)
160+
assert similar_structured
161+
assert {
162+
item["plex"]["rating_key"]
163+
for item in similar_structured
164+
if isinstance(item.get("plex"), dict)
165+
} >= {"61960"}
166+
167+
assert (
168+
asyncio.run(
169+
server.query_media.fn(
170+
similar_to="does-not-exist", type="movie", limit=1
171+
)
172+
)
173+
== []
174+
)
175+
153176
rec = asyncio.run(server.recommend_media.fn(identifier=movie_id, limit=1))
154177
assert rec and rec[0]["plex"]["rating_key"] == "61960"
155178

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)