Skip to content

Commit c0dd009

Browse files
authored
fix: load complete items and send TMDb auth header (#43)
1 parent c70f559 commit c0dd009

File tree

6 files changed

+68
-43
lines changed

6 files changed

+68
-43
lines changed

mcp_plex/loader.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ async def _fetch_tmdb_movie(
6565
client: httpx.AsyncClient, tmdb_id: str, api_key: str
6666
) -> Optional[TMDBMovie]:
6767
url = (
68-
f"https://api.themoviedb.org/3/movie/{tmdb_id}?api_key={api_key}&append_to_response=reviews"
68+
f"https://api.themoviedb.org/3/movie/{tmdb_id}?append_to_response=reviews"
6969
)
70-
resp = await client.get(url)
70+
resp = await client.get(url, headers={"Authorization": f"Bearer {api_key}"})
7171
if resp.is_success:
7272
return TMDBMovie.model_validate(resp.json())
7373
return None
@@ -77,9 +77,9 @@ async def _fetch_tmdb_show(
7777
client: httpx.AsyncClient, tmdb_id: str, api_key: str
7878
) -> Optional[TMDBShow]:
7979
url = (
80-
f"https://api.themoviedb.org/3/tv/{tmdb_id}?api_key={api_key}&append_to_response=reviews"
80+
f"https://api.themoviedb.org/3/tv/{tmdb_id}?append_to_response=reviews"
8181
)
82-
resp = await client.get(url)
82+
resp = await client.get(url, headers={"Authorization": f"Bearer {api_key}"})
8383
if resp.is_success:
8484
return TMDBShow.model_validate(resp.json())
8585
return None
@@ -90,8 +90,8 @@ async def _fetch_tmdb_episode(
9090
) -> Optional[TMDBEpisode]:
9191
"""Attempt to fetch TMDb data for a TV episode by its ID."""
9292

93-
url = f"https://api.themoviedb.org/3/tv/episode/{tmdb_id}?api_key={api_key}"
94-
resp = await client.get(url)
93+
url = f"https://api.themoviedb.org/3/tv/episode/{tmdb_id}"
94+
resp = await client.get(url, headers={"Authorization": f"Bearer {api_key}"})
9595
if resp.is_success:
9696
return TMDBEpisode.model_validate(resp.json())
9797
return None
@@ -190,19 +190,25 @@ async def _augment_episode(
190190
results: List[AggregatedItem] = []
191191
async with httpx.AsyncClient(timeout=30) as client:
192192
movie_section = server.library.section("Movies")
193-
movie_tasks = [_augment_movie(client, movie) for movie in movie_section.all()]
193+
movie_tasks = [
194+
_augment_movie(client, movie.fetchItem(movie.ratingKey))
195+
for movie in movie_section.all()
196+
]
194197
if movie_tasks:
195198
results.extend(await _gather_in_batches(movie_tasks, batch_size))
196199

197200
show_section = server.library.section("TV Shows")
198201
for show in show_section.all():
199-
show_ids = _extract_external_ids(show)
202+
full_show = show.fetchItem(show.ratingKey)
203+
show_ids = _extract_external_ids(full_show)
200204
show_tmdb: Optional[TMDBShow] = None
201205
if show_ids.tmdb:
202206
show_tmdb = await _fetch_tmdb_show(client, show_ids.tmdb, tmdb_api_key)
203207
episode_tasks = [
204-
_augment_episode(client, episode, show_tmdb)
205-
for episode in show.episodes()
208+
_augment_episode(
209+
client, episode.fetchItem(episode.ratingKey), show_tmdb
210+
)
211+
for episode in full_show.episodes()
206212
]
207213
if episode_tasks:
208214
results.extend(await _gather_in_batches(episode_tasks, batch_size))
@@ -348,7 +354,7 @@ async def run(
348354
parts.extend(r.get("content", "") for r in getattr(item.tmdb, "reviews", []))
349355
text = "\n".join(p for p in parts if p)
350356
payload = {
351-
"data": item.model_dump(),
357+
"data": item.model_dump(mode="json"),
352358
"title": item.plex.title,
353359
"type": item.plex.type,
354360
}
@@ -357,7 +363,7 @@ async def run(
357363
if item.plex.year is not None:
358364
payload["year"] = item.plex.year
359365
if item.plex.added_at is not None:
360-
payload["added_at"] = item.plex.added_at
366+
payload["added_at"] = int(item.plex.added_at.timestamp())
361367
point_id: int | str = (
362368
int(item.plex.rating_key)
363369
if item.plex.rating_key.isdigit()
@@ -453,7 +459,7 @@ async def run(
453459
else:
454460
logger.info("No points to upsert")
455461

456-
json.dump([item.model_dump() for item in items], fp=sys.stdout, indent=2)
462+
json.dump([item.model_dump(mode="json") for item in items], fp=sys.stdout, indent=2)
457463
sys.stdout.write("\n")
458464

459465

mcp_plex/types.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from __future__ import annotations
33

44
from dataclasses import dataclass
5+
from datetime import datetime
56
from typing import List, Literal, Optional
67

78
from pydantic import BaseModel, Field
@@ -121,7 +122,7 @@ class PlexItem(BaseModel):
121122
title: str
122123
summary: Optional[str] = None
123124
year: Optional[int] = None
124-
added_at: Optional[int] = None
125+
added_at: Optional[datetime] = None
125126
guids: List[PlexGuid] = Field(default_factory=list)
126127
thumb: Optional[str] = None
127128
art: Optional[str] = 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 = "0.26.8"
7+
version = "0.26.9"
88

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

tests/test_load_from_plex.py

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,51 @@
88

99

1010
def test_load_from_plex(monkeypatch):
11-
movie = types.SimpleNamespace(
12-
ratingKey="101",
13-
guid="plex://movie/101",
14-
type="movie",
15-
title="Inception",
16-
guids=[
17-
types.SimpleNamespace(id="imdb://tt1375666"),
18-
types.SimpleNamespace(id="tmdb://27205"),
19-
],
11+
def add_fetch(obj):
12+
obj.fetchItem = lambda key: obj
13+
return obj
14+
15+
movie = add_fetch(
16+
types.SimpleNamespace(
17+
ratingKey="101",
18+
guid="plex://movie/101",
19+
type="movie",
20+
title="Inception",
21+
guids=[
22+
types.SimpleNamespace(id="imdb://tt1375666"),
23+
types.SimpleNamespace(id="tmdb://27205"),
24+
],
25+
)
2026
)
2127

22-
ep1 = types.SimpleNamespace(
23-
ratingKey="102",
24-
guid="plex://episode/102",
25-
type="episode",
26-
title="Pilot",
27-
guids=[
28-
types.SimpleNamespace(id="imdb://tt0959621"),
29-
types.SimpleNamespace(id="tmdb://62085"),
30-
],
28+
ep1 = add_fetch(
29+
types.SimpleNamespace(
30+
ratingKey="102",
31+
guid="plex://episode/102",
32+
type="episode",
33+
title="Pilot",
34+
guids=[
35+
types.SimpleNamespace(id="imdb://tt0959621"),
36+
types.SimpleNamespace(id="tmdb://62085"),
37+
],
38+
)
3139
)
32-
ep2 = types.SimpleNamespace(
33-
ratingKey="103",
34-
guid="plex://episode/103",
35-
type="episode",
36-
title="Cat's in the Bag...",
37-
guids=[types.SimpleNamespace(id="imdb://tt0959622")],
40+
ep2 = add_fetch(
41+
types.SimpleNamespace(
42+
ratingKey="103",
43+
guid="plex://episode/103",
44+
type="episode",
45+
title="Cat's in the Bag...",
46+
guids=[types.SimpleNamespace(id="imdb://tt0959622")],
47+
)
3848
)
3949

40-
show = types.SimpleNamespace(
41-
guids=[types.SimpleNamespace(id="tmdb://1396")],
42-
episodes=lambda: [ep1, ep2],
50+
show = add_fetch(
51+
types.SimpleNamespace(
52+
ratingKey="201",
53+
guids=[types.SimpleNamespace(id="tmdb://1396")],
54+
episodes=lambda: [ep1, ep2],
55+
)
4356
)
4457

4558
movie_section = types.SimpleNamespace(all=lambda: [movie])
@@ -51,6 +64,8 @@ def test_load_from_plex(monkeypatch):
5164

5265
async def handler(request):
5366
url = str(request.url)
67+
if "themoviedb.org" in url:
68+
assert request.headers.get("Authorization") == "Bearer key"
5469
if "tt1375666" in url:
5570
return httpx.Response(
5671
200,

tests/test_loader_unit.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,19 @@ async def imdb_mock(request):
8383
return httpx.Response(404)
8484

8585
async def tmdb_movie_mock(request):
86+
assert request.headers.get("Authorization") == "Bearer k"
8687
if "good" in str(request.url):
8788
return httpx.Response(200, json={"id": 1, "title": "M"})
8889
return httpx.Response(404)
8990

9091
async def tmdb_show_mock(request):
92+
assert request.headers.get("Authorization") == "Bearer k"
9193
if "good" in str(request.url):
9294
return httpx.Response(200, json={"id": 1, "name": "S"})
9395
return httpx.Response(404)
9496

9597
async def tmdb_episode_mock(request):
98+
assert request.headers.get("Authorization") == "Bearer k"
9699
if "good" in str(request.url):
97100
return httpx.Response(200, json={"id": 1, "name": "E"})
98101
return httpx.Response(404)

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)