Skip to main content
Glama
djbriane
by djbriane

search_movies

Search your Plex movie library using filters like title, year, genre, actor, or duration to find specific films.

Instructions

Search for movies in your Plex library using optional filters.

Parameters: title: Optional title or substring to match. year: Optional release year to filter by. director: Optional director name to filter by. studio: Optional studio name to filter by. genre: Optional genre tag to filter by. actor: Optional actor name to filter by. rating: Optional rating (e.g., "PG-13") to filter by. country: Optional country of origin to filter by. language: Optional audio or subtitle language to filter by. watched: Optional boolean; True returns only watched movies, False only unwatched. min_duration: Optional minimum duration in minutes. max_duration: Optional maximum duration in minutes.

Returns: A formatted string of up to 5 matching movies (with a count of any additional results), or an error message if the search fails or no movies are found.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
titleNo
yearNo
directorNo
studioNo
genreNo
actorNo
ratingNo
countryNo
languageNo
watchedNo
min_durationNo
max_durationNo
limitNo

Implementation Reference

  • Primary MCP tool handler for searching movies in Plex library. Decorated with @mcp.tool() for automatic registration. Handles parameter validation, filter construction, Plex search execution, error handling, and result formatting.
    @mcp.tool()
    async def search_movies(
        title:        Optional[str]  = None,
        year:         Optional[int]  = None,
        director:     Optional[str]  = None,
        studio:       Optional[str]  = None,
        genre:        Optional[str]  = None,
        actor:        Optional[str]  = None,
        rating:       Optional[str]  = None,
        country:      Optional[str]  = None,
        language:     Optional[str]  = None,
        watched:      Optional[bool] = None,
        min_duration: Optional[int]  = None,
        max_duration: Optional[int]  = None,
        limit:        Optional[int]  = 5,
    ) -> str:
        """
        Search for movies in your Plex library using optional filters.
        
        Parameters:
            title: Optional title or substring to match.
            year: Optional release year to filter by.
            director: Optional director name to filter by.
            studio: Optional studio name to filter by.
            genre: Optional genre tag to filter by.
            actor: Optional actor name to filter by.
            rating: Optional rating (e.g., "PG-13") to filter by.
            country: Optional country of origin to filter by.
            language: Optional audio or subtitle language to filter by.
            watched: Optional boolean; True returns only watched movies, False only unwatched.
            min_duration: Optional minimum duration in minutes.
            max_duration: Optional maximum duration in minutes.
            
        Returns:
            A formatted string of up to 5 matching movies (with a count of any additional results),
            or an error message if the search fails or no movies are found.
        """
    
        # Validate the limit parameter
        limit = max(1, limit) if limit else 5  # Default to 5 if limit is 0 or negative
    
        params = MovieSearchParams(
            title, year, director, studio,
            genre, actor, rating, country,
            language, watched, min_duration, max_duration
        )
        filters = params.to_filters()
        logger.info("Searching Plex with filters: %r", filters)
    
        try:
            plex = await get_plex_server()
            movies = await asyncio.to_thread(plex.library.search, **filters)
        except Exception as e:
            logger.exception("search_movies failed connecting to Plex")
            return f"ERROR: Could not search Plex. {e}"
        
        if not movies:
            return f"No movies found matching filters {filters!r}."
        
        logger.info("Found %d movies matching filters: %r", len(movies), filters)
    
        results: List[str] = []
        for i, m in enumerate(movies[:limit], start=1):
            results.append(f"Result #{i}:\nKey: {m.ratingKey}\n{format_movie(m)}")
    
        if len(movies) > limit:
            results.append(f"\n... and {len(movies)-limit} more results.")
    
        return "\n---\n".join(results)
  • Dataclass for input schema of search_movies tool. Defines all filter parameters and provides to_filters() method to map them to Plex API search arguments, handling type conversions and special logic (e.g., watched inversion, duration ms).
    @dataclass
    class MovieSearchParams:
        title:        Optional[str]  = None
        year:         Optional[int]  = None
        director:     Optional[str]  = None
        studio:       Optional[str]  = None
        genre:        Optional[str]  = None
        actor:        Optional[str]  = None
        rating:       Optional[str]  = None
        country:      Optional[str]  = None
        language:     Optional[str]  = None
        watched:      Optional[bool] = None   # True=only watched, False=only unwatched
        min_duration: Optional[int]  = None   # in minutes
        max_duration: Optional[int]  = None   # in minutes
    
        def to_filters(self) -> Dict[str, Any]:
            FIELD_MAP = {
                "title":        "title",
                "year":         "year",
                "director":     "director",
                "studio":       "studio",
                "genre":        "genre",
                "actor":        "actor",
                "rating":       "rating",
                "country":      "country",
                "language":     "language",
                "watched":      "unwatched",
                "min_duration": "minDuration",
                "max_duration": "maxDuration",
            }
    
            filters: Dict[str, Any] = {"libtype": "movie"}
    
            for field_name, plex_arg in FIELD_MAP.items():
                value = getattr(self, field_name)
                if value is None:
                    continue
    
                if field_name == "watched":
                    # invert for Plex 'unwatched' flag
                    filters["unwatched"] = not value
                    continue
    
                if field_name in ("min_duration", "max_duration"):
                    # convert minutes to milliseconds
                    filters[plex_arg] = value * 60_000
                    continue
    
                filters[plex_arg] = value
    
            return filters
  • Helper function used by search_movies to format each movie result into a readable string including title, year, rating, duration, studio, directors, actors, and summary.
    def format_movie(movie) -> str:
        """
        Format a movie object into a human-readable string.
        
        Parameters:
            movie: A Plex movie object.
            
        Returns:
            A formatted string containing movie details.
        """
        title = getattr(movie, 'title', 'Unknown Title')
        year = getattr(movie, 'year', 'Unknown Year')
        summary = getattr(movie, 'summary', 'No summary available')
        duration = getattr(movie, 'duration', 0) // 60000 if hasattr(movie, 'duration') else 0
        rating = getattr(movie, 'rating', 'Unrated')
        studio = getattr(movie, 'studio', 'Unknown Studio')
        directors = [director.tag for director in getattr(movie, 'directors', [])[:3]]
        actors = [role.tag for role in getattr(movie, 'roles', [])[:5]]
        
        return (
            f"Title: {title} ({year})\n"
            f"Rating: {rating}\n"
            f"Duration: {duration} minutes\n"
            f"Studio: {studio}\n"
            f"Directors: {', '.join(directors) if directors else 'Unknown'}\n"
            f"Starring: {', '.join(actors) if actors else 'Unknown'}\n"
            f"Summary: {summary}\n"
        )
  • Helper utility called by search_movies to asynchronously retrieve the PlexServer instance, using a singleton PlexClient for connection management.
    async def get_plex_server() -> PlexServer:
        """
        Asynchronously get a PlexServer instance via the singleton PlexClient.
        
        Returns:
            A PlexServer instance.
            
        Raises:
            Exception: When the Plex server connection fails.
        """
        try:
            plex_client = get_plex_client()  # Singleton accessor
            plex = await asyncio.to_thread(plex_client.get_server)
            return plex
        except Exception as e:
            logger.exception("Failed to get Plex server instance")
            raise e
  • Package __init__ re-exports search_movies and other tools, allowing imports like 'from plex_mcp import search_movies' as seen in tests.
    from .plex_mcp import (
        search_movies,
        get_movie_details,
        list_playlists,
        get_playlist_items,
        create_playlist,
        delete_playlist,
        add_to_playlist,
        recent_movies,
        get_movie_genres,
        get_plex_server,
        MovieSearchParams,
    )
    
    __all__ = [
        "search_movies",
        "get_movie_details",
        "list_playlists",
        "get_playlist_items",
        "create_playlist",
        "delete_playlist",
        "add_to_playlist",
        "recent_movies",
        "get_movie_genres",
        "get_plex_server",
        "MovieSearchParams",
    ]
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It does reveal that the tool returns 'a formatted string of up to 5 matching movies (with a count of any additional results)' and handles errors, which adds useful context about output format and limitations. However, it doesn't mention performance characteristics, authentication requirements, or whether this is a read-only operation (though 'search' implies it likely is).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with clear sections for purpose, parameters, and returns. It's appropriately sized for a tool with 13 parameters. However, the parameter explanations could be slightly more concise, and the purpose statement could be more front-loaded with key limitations (like the 5-result limit).

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (13 parameters, no annotations, no output schema), the description does a good job of explaining what the tool does, what parameters mean, and what to expect in return. The main gap is the lack of usage guidance relative to sibling tools. The description compensates well for the missing schema descriptions and annotations.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema description coverage is 0%, so the description must fully compensate. It provides detailed semantic explanations for all 13 parameters, including examples like 'rating (e.g., "PG-13")' and clarifications like 'True returns only watched movies, False only unwatched.' This adds significant value beyond the basic parameter names in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Search for movies in your Plex library using optional filters.' This specifies the verb ('search'), resource ('movies'), and context ('Plex library'). However, it doesn't explicitly differentiate from sibling tools like 'recent_movies' or 'get_movie_details', which prevents a perfect score.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention sibling tools like 'recent_movies' for recently added movies or 'get_movie_details' for detailed information about specific movies. There's no context about prerequisites, limitations, or when this search tool is most appropriate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/djbriane/plex-mcp'

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