Skip to main content
Glama
resources.py7.66 kB
"""Movie resources for the Trakt MCP server.""" from __future__ import annotations import logging from collections.abc import Awaitable, Callable from typing import TYPE_CHECKING, TypeAlias if TYPE_CHECKING: from mcp.server.fastmcp import FastMCP from models.types import MovieResponse from pydantic import ValidationError from client.movies import MoviesClient from config.api import DEFAULT_LIMIT from config.mcp.resources import MCP_RESOURCES from models.formatters.movies import MovieFormatters from server.base import BaseToolErrorMixin from server.movies.tools import MovieIdParam from utils.api.error_types import TraktValidationError from utils.api.errors import handle_api_errors_func logger: logging.Logger = logging.getLogger("trakt_mcp") # Type alias for resource handler functions ResourceHandler: TypeAlias = Callable[[], Awaitable[str]] @handle_api_errors_func async def get_trending_movies() -> str: """Returns the most watched movies over the last 24 hours from Trakt. Movies with the most watchers are returned first. Returns: Formatted markdown text with trending movies """ client = MoviesClient() movies = await client.get_trending_movies(limit=DEFAULT_LIMIT) return MovieFormatters.format_trending_movies(movies) @handle_api_errors_func async def get_popular_movies() -> str: """Returns the most popular movies from Trakt. Popularity is calculated using the rating percentage and the number of ratings. Returns: Formatted markdown text with popular movies """ client = MoviesClient() movies = await client.get_popular_movies(limit=DEFAULT_LIMIT) return MovieFormatters.format_popular_movies(movies) @handle_api_errors_func async def get_favorited_movies() -> str: """Returns the most favorited movies from Trakt in the specified time period. Defaults to weekly. All stats are relative to the specific time period. Returns: Formatted markdown text with most favorited movies """ client = MoviesClient() movies = await client.get_favorited_movies(limit=DEFAULT_LIMIT) # Debug log for API response structure analysis if movies: logger.debug( "Favorited movies API response structure", extra={ "first_movie_keys": list(movies[0].keys()), "movie_count": len(movies), "first_movie_type": type(movies[0]).__name__, }, ) return MovieFormatters.format_favorited_movies(movies) @handle_api_errors_func async def get_played_movies() -> str: """Returns the most played movies from Trakt in the specified time period. A single user can watch a single movie multiple times. Defaults to weekly. All stats are relative to the specific time period. Returns: Formatted markdown text with most played movies """ client = MoviesClient() movies = await client.get_played_movies(limit=DEFAULT_LIMIT) return MovieFormatters.format_played_movies(movies) @handle_api_errors_func async def get_watched_movies() -> str: """Returns the most watched (unique users) movies from Trakt. Data is from the specified time period, defaulting to weekly. All stats are relative to the specific time period. Returns: Formatted markdown text with most watched movies """ client = MoviesClient() movies = await client.get_watched_movies(limit=DEFAULT_LIMIT) return MovieFormatters.format_watched_movies(movies) @handle_api_errors_func async def get_movie_ratings(movie_id: str) -> str: """Returns ratings for a specific movie from Trakt. Args: movie_id: Trakt ID of the movie Returns: Formatted markdown text with movie ratings Raises: InvalidParamsError: If movie_id is invalid InternalError: If an error occurs fetching movie or ratings data """ client = MoviesClient() # Validate parameters with Pydantic for normalization and constraints try: params = MovieIdParam(movie_id=movie_id) movie_id = params.movie_id except ValidationError as e: error_details = {str(error["loc"][-1]): error["msg"] for error in e.errors()} raise TraktValidationError( f"Invalid parameters for movie ratings: {', '.join(error_details.keys())}", invalid_params=list(error_details.keys()), validation_details=error_details, ) from e movie = await client.get_movie(movie_id) # Handle transitional case where API returns error strings if isinstance(movie, str): raise BaseToolErrorMixin.handle_api_string_error( resource_type="movie", resource_id=movie_id, error_message=movie, operation="fetch_movie_details", ) # Type narrowing: movie is guaranteed to be MovieResponse after string check movie_data: MovieResponse = movie movie_title = movie_data["title"] ratings = await client.get_movie_ratings(movie_id) # Handle transitional case where API returns error strings if isinstance(ratings, str): raise BaseToolErrorMixin.handle_api_string_error( resource_type="movie_ratings", resource_id=movie_id, error_message=ratings, operation="fetch_movie_ratings", movie_title=movie_title, ) return MovieFormatters.format_movie_ratings(ratings, movie_title) def register_movie_resources( mcp: FastMCP, ) -> tuple[ ResourceHandler, ResourceHandler, ResourceHandler, ResourceHandler, ResourceHandler ]: """Register movie resources with the MCP server. Returns: Tuple of resource handlers for type checker visibility """ @mcp.resource( uri=MCP_RESOURCES["movies_trending"], name="movies_trending", description="Most watched movies over the last 24 hours from Trakt", mime_type="text/markdown", ) async def movies_trending_resource() -> str: return await get_trending_movies() @mcp.resource( uri=MCP_RESOURCES["movies_popular"], name="movies_popular", description="Most popular movies from Trakt based on ratings and votes", mime_type="text/markdown", ) async def movies_popular_resource() -> str: return await get_popular_movies() @mcp.resource( uri=MCP_RESOURCES["movies_favorited"], name="movies_favorited", description="Most favorited movies from Trakt in the current weekly period", mime_type="text/markdown", ) async def movies_favorited_resource() -> str: return await get_favorited_movies() @mcp.resource( uri=MCP_RESOURCES["movies_played"], name="movies_played", description="Most played movies from Trakt in the current weekly period", mime_type="text/markdown", ) async def movies_played_resource() -> str: return await get_played_movies() @mcp.resource( uri=MCP_RESOURCES["movies_watched"], name="movies_watched", description="Most watched movies by unique users from Trakt in the current weekly period", mime_type="text/markdown", ) async def movies_watched_resource() -> str: return await get_watched_movies() # Note: movie_ratings moved to tools.py as @mcp.tool since it requires parameters # Return handlers for type checker visibility return ( movies_trending_resource, movies_popular_resource, movies_favorited_resource, movies_played_resource, movies_watched_resource, )

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wwiens/trakt_mcpserver'

If you have feedback or need assistance with the MCP directory API, please join our Discord server