diff --git a/docker/pyproject.deps.toml b/docker/pyproject.deps.toml index fab76a4..d10ab0a 100644 --- a/docker/pyproject.deps.toml +++ b/docker/pyproject.deps.toml @@ -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", diff --git a/mcp_plex/server/media.py b/mcp_plex/server/media.py index 04a4f64..0b1c47a 100644 --- a/mcp_plex/server/media.py +++ b/mcp_plex/server/media.py @@ -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: @@ -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 diff --git a/mcp_plex/server/tools/media_library.py b/mcp_plex/server/tools/media_library.py index 0ecbb65..2943a02 100644 --- a/mcp_plex/server/tools/media_library.py +++ b/mcp_plex/server/tools/media_library.py @@ -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}") @@ -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") diff --git a/pyproject.toml b/pyproject.toml index 9b134e3..1bfcca3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/tests/test_server_media_resources_additional.py b/tests/test_server_media_resources_additional.py index f999150..c7a341f 100644 --- a/tests/test_server_media_resources_additional.py +++ b/tests/test_server_media_resources_additional.py @@ -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): @@ -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()) diff --git a/uv.lock b/uv.lock index 7ce225d..179a5b8 100644 --- a/uv.lock +++ b/uv.lock @@ -730,7 +730,7 @@ wheels = [ [[package]] name = "mcp-plex" -version = "2.0.19" +version = "2.0.20" source = { editable = "." } dependencies = [ { name = "fastapi" },