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 = "2.0.19"
version = "2.0.20"
requires-python = ">=3.11,<3.13"
dependencies = [
"fastmcp>=2.11.2",
Expand Down
41 changes: 33 additions & 8 deletions mcp_plex/server/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,36 @@ def _collect_cache_keys(
return cache_keys


def _cache_media_artwork(
cache: "MediaCache", cache_keys: set[str], plex_info: PlexMediaMetadata
) -> None:
"""Persist poster and background URLs for each cache key."""

if not cache_keys:
return

thumb = plex_info.get("thumb")
if isinstance(thumb, str) and thumb:
for cache_key in cache_keys:
cache.set_poster(cache_key, thumb)

art = plex_info.get("art")
if isinstance(art, str) and art:
for cache_key in cache_keys:
cache.set_background(cache_key, art)


def _ensure_rating_key_cached(
cache_keys: set[str], plex_info: PlexMediaMetadata
) -> set[str]:
"""Guarantee the Plex rating key is part of the cache key set."""

rating_key = _normalize_identifier(plex_info.get("rating_key"))
if rating_key:
cache_keys.add(rating_key)
return cache_keys


def _identifier_matches_payload(
identifier: str | int | float | None, payload: AggregatedMediaItem
) -> bool:
Expand Down Expand Up @@ -183,17 +213,12 @@ async def _get_media_data(server: "PlexServer", identifier: str) -> AggregatedMe

plex_data = _extract_plex_metadata(data)
cache_keys = _collect_cache_keys(data, plex_data, identifier)
rating_key = _normalize_identifier(plex_data.get("rating_key"))
cache_keys = _ensure_rating_key_cached(cache_keys, plex_data)

for cache_key in cache_keys:
server.cache.set_payload(cache_key, cast(dict[str, JSONValue], payload))

if rating_key:
thumb = plex_data.get("thumb")
if isinstance(thumb, str) and thumb:
server.cache.set_poster(rating_key, thumb)
art = plex_data.get("art")
if isinstance(art, str) and art:
server.cache.set_background(rating_key, art)
_cache_media_artwork(server.cache, cache_keys, plex_data)
return payload


Expand Down
12 changes: 6 additions & 6 deletions mcp_plex/server/tools/media_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -931,9 +931,9 @@ async def media_poster(
if not thumb:
raise ValueError("Poster not available")
thumb_str = str(thumb)
rating_key = media_helpers._normalize_identifier(plex_info.get("rating_key"))
if rating_key:
server.cache.set_poster(rating_key, thumb_str)
cache_keys = media_helpers._collect_cache_keys(data, plex_info, identifier)
cache_keys = media_helpers._ensure_rating_key_cached(cache_keys, plex_info)
media_helpers._cache_media_artwork(server.cache, cache_keys, plex_info)
return thumb_str

@server.resource("resource://media-background/{identifier}")
Expand All @@ -957,9 +957,9 @@ async def media_background(
if not art:
raise ValueError("Background not available")
art_str = str(art)
rating_key = media_helpers._normalize_identifier(plex_info.get("rating_key"))
if rating_key:
server.cache.set_background(rating_key, art_str)
cache_keys = media_helpers._collect_cache_keys(data, plex_info, identifier)
cache_keys = media_helpers._ensure_rating_key_cached(cache_keys, plex_info)
media_helpers._cache_media_artwork(server.cache, cache_keys, plex_info)
return art_str

@server.prompt("media-info")
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 = "2.0.19"
version = "2.0.20"

description = "Plex-Oriented Model Context Protocol Server"
requires-python = ">=3.11,<3.13"
Expand Down
38 changes: 37 additions & 1 deletion tests/test_server_media_resources_additional.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import pytest

from mcp_plex.server import media as media_helpers
from mcp_plex.server import media_background, media_poster
from mcp_plex.server import media_background, media_poster, server as plex_server


def test_media_poster_raises_when_missing(monkeypatch):
Expand All @@ -32,3 +32,39 @@ async def _fake_get_media(*args, **kwargs):
await media_background.fn(identifier="missing")

asyncio.run(_run())


def test_media_artwork_cached_for_alternate_identifier(monkeypatch):
async def _run() -> None:
plex_server.cache.clear()

calls: list[str] = []

rating_key = "49915"
imdb_id = "tt8367814"
poster_url = "https://example.com/poster.jpg"
background_url = "https://example.com/background.jpg"

async def _fake_get_media(server, identifier):
calls.append(identifier)
return {
"title": "The Gentlemen",
"imdb": {"id": imdb_id},
"plex": {
"rating_key": rating_key,
"thumb": poster_url,
"art": background_url,
},
}

monkeypatch.setattr(media_helpers, "_get_media_data", _fake_get_media)

poster = await media_poster.fn(identifier=rating_key)
assert poster == poster_url
poster_cached = await media_poster.fn(identifier=imdb_id)
assert poster_cached == poster_url
background_cached = await media_background.fn(identifier=imdb_id)
assert background_cached == background_url
assert calls == [rating_key]

asyncio.run(_run())
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.