diff --git a/servers/tmdb/Dockerfile b/servers/tmdb/Dockerfile new file mode 100644 index 0000000..478aaf2 --- /dev/null +++ b/servers/tmdb/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.11-slim + +WORKDIR /app + +# Copy requirements and install dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy server code +COPY server.py . + +# Expose port +EXPOSE 8000 + +# Run the FastMCP server +CMD ["python", "-m", "fastmcp", "run", "server.py", "--transport", "streamable-http", "--port", "8000"] diff --git a/servers/tmdb/README.md b/servers/tmdb/README.md new file mode 100644 index 0000000..34f3ef9 --- /dev/null +++ b/servers/tmdb/README.md @@ -0,0 +1,666 @@ +# TMDB MCP Server + +MCP server for The Movie Database (TMDB) API. Search and discover movies, TV shows, cast information, ratings, and streaming availability from over 1 million titles. + +## Features + +- **Search**: Find movies, TV shows, and people +- **Details**: Get comprehensive information about titles and cast +- **Discovery**: Filter and discover content by genre, year, rating, and more +- **Trending & Popular**: Access trending and popular lists +- **Recommendations**: Find similar content +- **Streaming**: Check where content is available to stream +- **Credits**: Get cast and crew information +- **Genres**: Browse content by genre + +## Setup + +### Prerequisites + +- TMDB account and API key +- 1 million+ movies and TV shows available + +### Environment Variables + +- `TMDB_API_KEY` (required): Your TMDB API key + +**How to get credentials:** +1. Go to [themoviedb.org](https://www.themoviedb.org/) +2. Create an account or log in +3. Go to Settings → API +4. Request an API key (free for non-commercial use) +5. Choose "Developer" API key type +6. Fill in the application details +7. Copy your API key and store as `TMDB_API_KEY` + +## Rate Limits + +- **Free Tier**: 40 requests per 10 seconds +- **Rate limit headers** included in responses +- Consider implementing caching for frequently accessed data + +## Image URLs + +TMDB returns image paths that need to be combined with a base URL: + +**Base URL**: `https://image.tmdb.org/t/p/` + +**Available sizes:** +- **Posters**: w92, w154, w185, w342, w500, w780, original +- **Backdrops**: w300, w780, w1280, original +- **Profiles**: w45, w185, h632, original +- **Logos**: w45, w92, w154, w185, w300, w500, original + +**Example:** +``` +Path: /nBNZadXqJSdt05SHLqgT0HuC5Gm.jpg +Full URL: https://image.tmdb.org/t/p/w500/nBNZadXqJSdt05SHLqgT0HuC5Gm.jpg +``` + +## Available Tools + +### Search Tools + +#### `search_movies` +Search for movies by title. + +**Parameters:** +- `query` (string, required): Movie title to search +- `language` (string, optional): Language code (default: en-US) +- `page` (int, optional): Page number (default: 1) +- `include_adult` (bool, optional): Include adult content (default: false) +- `year` (int, optional): Filter by release year + +**Example:** +```python +results = await search_movies( + query="The Matrix", + year=1999, + language="en-US" +) +``` + +#### `search_tv_shows` +Search for TV shows by title. + +**Parameters:** +- `query` (string, required): TV show title +- `language` (string, optional): Language code (default: en-US) +- `page` (int, optional): Page number (default: 1) +- `include_adult` (bool, optional): Include adult content (default: false) +- `first_air_date_year` (int, optional): Filter by first air date year + +**Example:** +```python +results = await search_tv_shows( + query="Breaking Bad", + first_air_date_year=2008 +) +``` + +#### `search_people` +Search for actors, directors, and crew members. + +**Parameters:** +- `query` (string, required): Person name +- `language` (string, optional): Language code (default: en-US) +- `page` (int, optional): Page number (default: 1) +- `include_adult` (bool, optional): Include adult content (default: false) + +**Example:** +```python +results = await search_people(query="Tom Hanks") +``` + +### Details Tools + +#### `get_movie_details` +Get detailed movie information including runtime, budget, revenue, ratings, and more. + +**Parameters:** +- `movie_id` (int, required): TMDB movie ID +- `language` (string, optional): Language code (default: en-US) +- `append_to_response` (string, optional): Additional data (credits, videos, images, similar, recommendations) + +**Example:** +```python +movie = await get_movie_details( + movie_id=603, # The Matrix + append_to_response="credits,videos,recommendations" +) + +# Returns: +# - title, overview, tagline +# - runtime, budget, revenue +# - release_date, status +# - vote_average, vote_count, popularity +# - genres, production_companies, production_countries +# - spoken_languages +# - credits (cast and crew) +# - videos (trailers, teasers) +# - recommendations +``` + +#### `get_tv_details` +Get TV show details including seasons, episodes, networks, and more. + +**Parameters:** +- `tv_id` (int, required): TMDB TV show ID +- `language` (string, optional): Language code (default: en-US) +- `append_to_response` (string, optional): Additional data (credits, videos, images, similar, recommendations) + +**Example:** +```python +show = await get_tv_details( + tv_id=1396, # Breaking Bad + append_to_response="credits,videos" +) + +# Returns: +# - name, overview, tagline +# - number_of_seasons, number_of_episodes +# - first_air_date, last_air_date, status +# - networks, production_companies +# - seasons (season number, episode count, air date, poster) +# - episode_run_time +# - vote_average, vote_count, popularity +# - genres +``` + +#### `get_person_details` +Get person biography, filmography, and credits. + +**Parameters:** +- `person_id` (int, required): TMDB person ID +- `language` (string, optional): Language code (default: en-US) +- `append_to_response` (string, optional): Additional data (movie_credits, tv_credits, combined_credits, images) + +**Example:** +```python +person = await get_person_details( + person_id=31, # Tom Hanks + append_to_response="movie_credits,tv_credits" +) + +# Returns: +# - name, biography +# - birthday, place_of_birth +# - known_for_department (Acting, Directing, etc.) +# - popularity +# - profile_path (profile image) +# - movie_credits (cast and crew roles) +# - tv_credits (cast and crew roles) +``` + +### Discovery Tools + +#### `get_trending` +Get trending movies or TV shows. + +**Parameters:** +- `media_type` (string, optional): movie, tv, or all (default: movie) +- `time_window` (string, optional): day or week (default: day) +- `language` (string, optional): Language code (default: en-US) +- `page` (int, optional): Page number (default: 1) + +**Example:** +```python +# Trending movies today +trending = await get_trending( + media_type="movie", + time_window="day" +) + +# Trending TV shows this week +trending_tv = await get_trending( + media_type="tv", + time_window="week" +) +``` + +#### `get_popular` +Get popular movies or TV shows. + +**Parameters:** +- `media_type` (string, optional): movie or tv (default: movie) +- `language` (string, optional): Language code (default: en-US) +- `page` (int, optional): Page number (default: 1) +- `region` (string, optional): ISO 3166-1 code (US, GB, etc.) + +**Example:** +```python +popular = await get_popular( + media_type="movie", + region="US" +) +``` + +#### `get_top_rated` +Get top rated movies or TV shows. + +**Parameters:** +- `media_type` (string, optional): movie or tv (default: movie) +- `language` (string, optional): Language code (default: en-US) +- `page` (int, optional): Page number (default: 1) +- `region` (string, optional): ISO 3166-1 code + +**Example:** +```python +top_rated = await get_top_rated(media_type="movie") +``` + +#### `discover_movies` +Discover movies with advanced filters. + +**Parameters:** +- `language` (string, optional): Language code (default: en-US) +- `page` (int, optional): Page number (default: 1) +- `sort_by` (string, optional): Sort results (default: popularity.desc) + - popularity.desc, popularity.asc + - vote_average.desc, vote_average.asc + - release_date.desc, release_date.asc + - revenue.desc, revenue.asc + - primary_release_date.desc, primary_release_date.asc +- `with_genres` (string, optional): Comma-separated genre IDs +- `year` (int, optional): Filter by release year +- `primary_release_year` (int, optional): Filter by primary release year +- `vote_average_gte` (float, optional): Minimum rating (0-10) +- `vote_average_lte` (float, optional): Maximum rating (0-10) +- `with_runtime_gte` (int, optional): Minimum runtime in minutes +- `with_runtime_lte` (int, optional): Maximum runtime in minutes +- `region` (string, optional): ISO 3166-1 code + +**Example:** +```python +# Find highly-rated sci-fi movies from 2020-2024 +movies = await discover_movies( + with_genres="878", # Sci-Fi + primary_release_year=2023, + vote_average_gte=7.0, + sort_by="vote_average.desc" +) + +# Find short comedies (under 90 minutes) +comedies = await discover_movies( + with_genres="35", # Comedy + with_runtime_lte=90, + sort_by="popularity.desc" +) +``` + +#### `discover_tv` +Discover TV shows with advanced filters. + +**Parameters:** +- `language` (string, optional): Language code (default: en-US) +- `page` (int, optional): Page number (default: 1) +- `sort_by` (string, optional): Sort results (default: popularity.desc) +- `with_genres` (string, optional): Comma-separated genre IDs +- `first_air_date_year` (int, optional): Filter by first air date year +- `vote_average_gte` (float, optional): Minimum rating (0-10) +- `vote_average_lte` (float, optional): Maximum rating (0-10) +- `with_runtime_gte` (int, optional): Minimum episode runtime in minutes +- `with_runtime_lte` (int, optional): Maximum episode runtime in minutes +- `with_networks` (string, optional): Comma-separated network IDs (Netflix: 213, HBO: 49, etc.) + +**Example:** +```python +# Find Netflix dramas +shows = await discover_tv( + with_networks="213", # Netflix + with_genres="18", # Drama + sort_by="vote_average.desc", + vote_average_gte=7.5 +) +``` + +### Genre Tools + +#### `get_genres` +Get list of all genres for movies or TV shows. + +**Parameters:** +- `media_type` (string, optional): movie or tv (default: movie) +- `language` (string, optional): Language code (default: en-US) + +**Example:** +```python +genres = await get_genres(media_type="movie") + +# Returns: +# { +# "genres": [ +# {"id": 28, "name": "Action"}, +# {"id": 12, "name": "Adventure"}, +# {"id": 16, "name": "Animation"}, +# {"id": 35, "name": "Comedy"}, +# {"id": 80, "name": "Crime"}, +# {"id": 99, "name": "Documentary"}, +# {"id": 18, "name": "Drama"}, +# {"id": 10751, "name": "Family"}, +# {"id": 14, "name": "Fantasy"}, +# {"id": 36, "name": "History"}, +# {"id": 27, "name": "Horror"}, +# {"id": 10402, "name": "Music"}, +# {"id": 9648, "name": "Mystery"}, +# {"id": 10749, "name": "Romance"}, +# {"id": 878, "name": "Science Fiction"}, +# {"id": 10770, "name": "TV Movie"}, +# {"id": 53, "name": "Thriller"}, +# {"id": 10752, "name": "War"}, +# {"id": 37, "name": "Western"} +# ] +# } +``` + +### Credits Tools + +#### `get_movie_credits` +Get cast and crew for a movie. + +**Parameters:** +- `movie_id` (int, required): TMDB movie ID +- `language` (string, optional): Language code (default: en-US) + +**Example:** +```python +credits = await get_movie_credits(movie_id=603) # The Matrix + +# Returns: +# { +# "cast": [ +# { +# "name": "Keanu Reeves", +# "character": "Neo", +# "order": 0, +# "profile_path": "/path.jpg" +# } +# ], +# "crew": [ +# { +# "name": "Lana Wachowski", +# "job": "Director", +# "department": "Directing" +# } +# ] +# } +``` + +#### `get_tv_credits` +Get cast and crew for a TV show. + +**Parameters:** +- `tv_id` (int, required): TMDB TV show ID +- `language` (string, optional): Language code (default: en-US) + +**Example:** +```python +credits = await get_tv_credits(tv_id=1396) # Breaking Bad +``` + +### Recommendation Tools + +#### `get_recommendations` +Get similar movies or TV shows based on a title. + +**Parameters:** +- `media_type` (string, required): movie or tv +- `media_id` (int, required): TMDB media ID +- `language` (string, optional): Language code (default: en-US) +- `page` (int, optional): Page number (default: 1) + +**Example:** +```python +# Movies similar to The Matrix +similar = await get_recommendations( + media_type="movie", + media_id=603 +) +``` + +#### `get_streaming_providers` +Get streaming availability for a movie or TV show by region. + +**Parameters:** +- `media_type` (string, required): movie or tv +- `media_id` (int, required): TMDB media ID + +**Example:** +```python +providers = await get_streaming_providers( + media_type="movie", + media_id=603 +) + +# Returns providers by region: +# { +# "results": { +# "US": { +# "link": "https://www.themoviedb.org/movie/603/watch?locale=US", +# "flatrate": [ # Streaming (subscription) +# {"provider_id": 8, "provider_name": "Netflix", "logo_path": "/path.jpg"} +# ], +# "rent": [ # Rent +# {"provider_id": 2, "provider_name": "Apple TV", "logo_path": "/path.jpg"} +# ], +# "buy": [ # Buy +# {"provider_id": 3, "provider_name": "Google Play Movies"} +# ] +# }, +# "GB": {...}, +# "CA": {...} +# } +# } +``` + +## Genre IDs + +**Movie Genres:** +- 28: Action +- 12: Adventure +- 16: Animation +- 35: Comedy +- 80: Crime +- 99: Documentary +- 18: Drama +- 10751: Family +- 14: Fantasy +- 36: History +- 27: Horror +- 10402: Music +- 9648: Mystery +- 10749: Romance +- 878: Science Fiction +- 10770: TV Movie +- 53: Thriller +- 10752: War +- 37: Western + +**TV Genres:** +- 10759: Action & Adventure +- 16: Animation +- 35: Comedy +- 80: Crime +- 99: Documentary +- 18: Drama +- 10751: Family +- 10762: Kids +- 9648: Mystery +- 10763: News +- 10764: Reality +- 10765: Sci-Fi & Fantasy +- 10766: Soap +- 10767: Talk +- 10768: War & Politics +- 37: Western + +## Network IDs + +Common streaming networks: +- **213**: Netflix +- **49**: HBO +- **2739**: Disney+ +- **1024**: Amazon Prime Video +- **453**: Hulu +- **2552**: Apple TV+ +- **2697**: Peacock +- **1899**: Max +- **389**: Showtime +- **67**: Paramount+ + +## Language Codes + +Common language codes (ISO 639-1): +- `en-US`: English (United States) +- `es-ES`: Spanish (Spain) +- `fr-FR`: French (France) +- `de-DE`: German (Germany) +- `it-IT`: Italian (Italy) +- `ja-JP`: Japanese (Japan) +- `ko-KR`: Korean (South Korea) +- `pt-BR`: Portuguese (Brazil) +- `zh-CN`: Chinese (Simplified) + +## Region Codes + +Common region codes (ISO 3166-1): +- `US`: United States +- `GB`: United Kingdom +- `CA`: Canada +- `AU`: Australia +- `DE`: Germany +- `FR`: France +- `ES`: Spain +- `IT`: Italy +- `JP`: Japan +- `KR`: South Korea +- `BR`: Brazil +- `MX`: Mexico + +## Pagination + +Most list endpoints support pagination: + +```python +# Page 1 +results_page1 = await search_movies(query="action", page=1) + +# Page 2 +results_page2 = await search_movies(query="action", page=2) + +# Response includes: +# - page: Current page number +# - total_pages: Total number of pages +# - total_results: Total number of results +# - results: Array of items (usually 20 per page) +``` + +## Common Use Cases + +### Find a Movie and Get Full Details +```python +# Search for the movie +search_results = await search_movies(query="Inception") +movie_id = search_results["results"][0]["id"] + +# Get full details with credits and videos +details = await get_movie_details( + movie_id=movie_id, + append_to_response="credits,videos,recommendations" +) + +# Get streaming availability +streaming = await get_streaming_providers( + media_type="movie", + media_id=movie_id +) +``` + +### Discover New Content +```python +# Find popular sci-fi movies from 2023 +movies = await discover_movies( + with_genres="878", + primary_release_year=2023, + sort_by="popularity.desc", + vote_average_gte=6.0 +) + +# Find Netflix original series +shows = await discover_tv( + with_networks="213", + first_air_date_year=2023, + sort_by="vote_average.desc" +) +``` + +### Get Trending Content +```python +# What's trending today? +trending_today = await get_trending( + media_type="all", + time_window="day" +) + +# Popular movies in the US +popular_us = await get_popular( + media_type="movie", + region="US" +) +``` + +### Research Actors and Filmography +```python +# Search for an actor +search = await search_people(query="Leonardo DiCaprio") +person_id = search["results"][0]["id"] + +# Get full filmography +person = await get_person_details( + person_id=person_id, + append_to_response="movie_credits,tv_credits" +) + +# Returns all movies and TV shows they appeared in +``` + +## Best Practices + +1. **Cache frequently accessed data**: Genre lists, popular content, and trending lists +2. **Use append_to_response**: Combine multiple requests into one +3. **Respect rate limits**: 40 requests per 10 seconds +4. **Store image sizes appropriately**: Use smaller sizes for thumbnails +5. **Filter by region**: Get relevant streaming providers for your users +6. **Use language codes**: Provide localized content +7. **Implement pagination**: Handle large result sets properly +8. **Check vote_count**: High ratings with few votes may not be reliable + +## Error Handling + +Common errors: + +- **401 Unauthorized**: Invalid API key +- **404 Not Found**: Movie, show, or person not found +- **429 Too Many Requests**: Rate limit exceeded (wait and retry) +- **500 Internal Server Error**: TMDB service issue (retry later) + +## Data Freshness + +- **Daily updates**: New releases, trending content, popularity scores +- **Real-time streaming data**: Provider availability updates +- **User ratings**: Vote averages update continuously +- **Content metadata**: Updated as information becomes available + +## API Documentation + +- [TMDB API Documentation](https://developer.themoviedb.org/docs) +- [API Reference](https://developer.themoviedb.org/reference/intro/getting-started) +- [Image Documentation](https://developer.themoviedb.org/docs/image-basics) +- [API Terms of Use](https://www.themoviedb.org/terms-of-use) + +## Support + +- [TMDB Support](https://www.themoviedb.org/talk) +- [API Forums](https://www.themoviedb.org/talk/category/5047958519c29526b50017d6) +- [Status Page](https://status.themoviedb.org/) +- [Contact](https://www.themoviedb.org/about/staying-in-touch) diff --git a/servers/tmdb/requirements.txt b/servers/tmdb/requirements.txt new file mode 100644 index 0000000..42f0167 --- /dev/null +++ b/servers/tmdb/requirements.txt @@ -0,0 +1,4 @@ +fastmcp>=0.2.0 +httpx>=0.27.0 +python-dotenv>=1.0.0 +uvicorn>=0.30.0 diff --git a/servers/tmdb/server.json b/servers/tmdb/server.json new file mode 100644 index 0000000..4e5ba7a --- /dev/null +++ b/servers/tmdb/server.json @@ -0,0 +1,87 @@ +{ + "$schema": "https://registry.nimbletools.ai/schemas/2025-09-22/nimbletools-server.schema.json", + "name": "ai.nimbletools/tmdb", + "version": "1.0.0", + "description": "TMDB API: movies, TV shows, cast info, ratings, and streaming availability from 1M+ titles", + "status": "active", + "repository": { + "url": "https://github.com/NimbleBrainInc/mcp-tmdb", + "source": "github", + "branch": "main" + }, + "websiteUrl": "https://www.themoviedb.org/", + "packages": [ + { + "registryType": "oci", + "registryBaseUrl": "https://docker.io", + "identifier": "nimbletools/mcp-tmdb", + "version": "1.0.0", + "transport": { + "type": "streamable-http", + "url": "https://mcp.nimbletools.ai/mcp" + }, + "environmentVariables": [ + { + "name": "TMDB_API_KEY", + "description": "TMDB API key (get from https://www.themoviedb.org/settings/api)", + "isRequired": true, + "isSecret": true, + "example": "your_tmdb_api_key_here" + } + ] + } + ], + "_meta": { + "ai.nimbletools.mcp/v1": { + "container": { + "healthCheck": { + "path": "/health", + "port": 8000 + } + }, + "capabilities": { + "tools": true, + "resources": false, + "prompts": false + }, + "resources": { + "limits": { + "memory": "256Mi", + "cpu": "250m" + }, + "requests": { + "memory": "128Mi", + "cpu": "100m" + } + }, + "deployment": { + "protocol": "http", + "port": 8000, + "mcpPath": "/mcp" + }, + "display": { + "name": "TMDB", + "category": "data-intelligence", + "tags": [ + "tmdb", + "movies", + "tv-shows", + "entertainment", + "streaming", + "cast", + "ratings", + "recommendations", + "cinema", + "requires-api-key" + ], + "branding": { + "logoUrl": "https://static.nimbletools.ai/logos/tmdb.png", + "iconUrl": "https://static.nimbletools.ai/icons/tmdb.png" + }, + "documentation": { + "readmeUrl": "https://raw.githubusercontent.com/NimbleBrainInc/mcp-tmdb/main/README.md" + } + } + } + } +} diff --git a/servers/tmdb/server.py b/servers/tmdb/server.py new file mode 100644 index 0000000..6f74b19 --- /dev/null +++ b/servers/tmdb/server.py @@ -0,0 +1,484 @@ +import os +from typing import Optional, List, Dict, Any +import httpx +from fastmcp import FastMCP + +mcp = FastMCP("TMDB") + +API_KEY = os.getenv("TMDB_API_KEY") +BASE_URL = "https://api.themoviedb.org/3" + + +def get_url(endpoint: str, **params) -> str: + """Build URL with API key and parameters.""" + params["api_key"] = API_KEY + param_str = "&".join([f"{k}={v}" for k, v in params.items() if v is not None]) + return f"{BASE_URL}/{endpoint}?{param_str}" + + +@mcp.tool() +async def search_movies( + query: str, + language: str = "en-US", + page: int = 1, + include_adult: bool = False, + year: Optional[int] = None +) -> dict: + """Search for movies by title. + + Args: + query: Movie title to search for + language: Language code (default: en-US) + page: Page number for pagination + include_adult: Include adult content + year: Filter by release year + """ + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get( + get_url( + "search/movie", + query=query, + language=language, + page=page, + include_adult=include_adult, + year=year + ) + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def search_tv_shows( + query: str, + language: str = "en-US", + page: int = 1, + include_adult: bool = False, + first_air_date_year: Optional[int] = None +) -> dict: + """Search for TV shows by title. + + Args: + query: TV show title to search for + language: Language code (default: en-US) + page: Page number for pagination + include_adult: Include adult content + first_air_date_year: Filter by first air date year + """ + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get( + get_url( + "search/tv", + query=query, + language=language, + page=page, + include_adult=include_adult, + first_air_date_year=first_air_date_year + ) + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def search_people( + query: str, + language: str = "en-US", + page: int = 1, + include_adult: bool = False +) -> dict: + """Search for actors, directors, and crew members. + + Args: + query: Person name to search for + language: Language code (default: en-US) + page: Page number for pagination + include_adult: Include adult content + """ + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get( + get_url( + "search/person", + query=query, + language=language, + page=page, + include_adult=include_adult + ) + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def get_movie_details( + movie_id: int, + language: str = "en-US", + append_to_response: Optional[str] = None +) -> dict: + """Get detailed movie information including runtime, budget, revenue, ratings, and more. + + Args: + movie_id: TMDB movie ID + language: Language code (default: en-US) + append_to_response: Comma-separated list of additional data (credits, videos, images, etc.) + """ + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get( + get_url( + f"movie/{movie_id}", + language=language, + append_to_response=append_to_response + ) + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def get_tv_details( + tv_id: int, + language: str = "en-US", + append_to_response: Optional[str] = None +) -> dict: + """Get TV show details including seasons, episodes, networks, and more. + + Args: + tv_id: TMDB TV show ID + language: Language code (default: en-US) + append_to_response: Comma-separated list of additional data (credits, videos, images, etc.) + """ + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get( + get_url( + f"tv/{tv_id}", + language=language, + append_to_response=append_to_response + ) + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def get_person_details( + person_id: int, + language: str = "en-US", + append_to_response: Optional[str] = None +) -> dict: + """Get person biography, filmography, and credits. + + Args: + person_id: TMDB person ID + language: Language code (default: en-US) + append_to_response: Comma-separated list of additional data (movie_credits, tv_credits, images, etc.) + """ + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get( + get_url( + f"person/{person_id}", + language=language, + append_to_response=append_to_response + ) + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def get_trending( + media_type: str = "movie", + time_window: str = "day", + language: str = "en-US", + page: int = 1 +) -> dict: + """Get trending movies or TV shows. + + Args: + media_type: Type of media (movie, tv, or all) + time_window: Time window for trending (day or week) + language: Language code (default: en-US) + page: Page number for pagination + """ + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get( + get_url( + f"trending/{media_type}/{time_window}", + language=language, + page=page + ) + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def get_popular( + media_type: str = "movie", + language: str = "en-US", + page: int = 1, + region: Optional[str] = None +) -> dict: + """Get popular movies or TV shows. + + Args: + media_type: Type of media (movie or tv) + language: Language code (default: en-US) + page: Page number for pagination + region: ISO 3166-1 code for region filtering + """ + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get( + get_url( + f"{media_type}/popular", + language=language, + page=page, + region=region + ) + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def get_top_rated( + media_type: str = "movie", + language: str = "en-US", + page: int = 1, + region: Optional[str] = None +) -> dict: + """Get top rated movies or TV shows. + + Args: + media_type: Type of media (movie or tv) + language: Language code (default: en-US) + page: Page number for pagination + region: ISO 3166-1 code for region filtering + """ + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get( + get_url( + f"{media_type}/top_rated", + language=language, + page=page, + region=region + ) + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def discover_movies( + language: str = "en-US", + page: int = 1, + sort_by: str = "popularity.desc", + with_genres: Optional[str] = None, + year: Optional[int] = None, + primary_release_year: Optional[int] = None, + vote_average_gte: Optional[float] = None, + vote_average_lte: Optional[float] = None, + with_runtime_gte: Optional[int] = None, + with_runtime_lte: Optional[int] = None, + region: Optional[str] = None +) -> dict: + """Discover movies with filters for genre, year, rating, and more. + + Args: + language: Language code (default: en-US) + page: Page number for pagination + sort_by: Sort results (popularity.desc, vote_average.desc, release_date.desc, revenue.desc, etc.) + with_genres: Comma-separated genre IDs + year: Filter by release year + primary_release_year: Filter by primary release year + vote_average_gte: Minimum rating (0-10) + vote_average_lte: Maximum rating (0-10) + with_runtime_gte: Minimum runtime in minutes + with_runtime_lte: Maximum runtime in minutes + region: ISO 3166-1 code for region filtering + """ + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get( + get_url( + "discover/movie", + language=language, + page=page, + sort_by=sort_by, + with_genres=with_genres, + year=year, + primary_release_year=primary_release_year, + **{ + "vote_average.gte": vote_average_gte, + "vote_average.lte": vote_average_lte, + "with_runtime.gte": with_runtime_gte, + "with_runtime.lte": with_runtime_lte, + "region": region + } + ) + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def discover_tv( + language: str = "en-US", + page: int = 1, + sort_by: str = "popularity.desc", + with_genres: Optional[str] = None, + first_air_date_year: Optional[int] = None, + vote_average_gte: Optional[float] = None, + vote_average_lte: Optional[float] = None, + with_runtime_gte: Optional[int] = None, + with_runtime_lte: Optional[int] = None, + with_networks: Optional[str] = None +) -> dict: + """Discover TV shows with filters for genre, year, rating, and more. + + Args: + language: Language code (default: en-US) + page: Page number for pagination + sort_by: Sort results (popularity.desc, vote_average.desc, first_air_date.desc, etc.) + with_genres: Comma-separated genre IDs + first_air_date_year: Filter by first air date year + vote_average_gte: Minimum rating (0-10) + vote_average_lte: Maximum rating (0-10) + with_runtime_gte: Minimum runtime in minutes + with_runtime_lte: Maximum runtime in minutes + with_networks: Comma-separated network IDs + """ + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get( + get_url( + "discover/tv", + language=language, + page=page, + sort_by=sort_by, + with_genres=with_genres, + first_air_date_year=first_air_date_year, + with_networks=with_networks, + **{ + "vote_average.gte": vote_average_gte, + "vote_average.lte": vote_average_lte, + "with_runtime.gte": with_runtime_gte, + "with_runtime.lte": with_runtime_lte + } + ) + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def get_genres( + media_type: str = "movie", + language: str = "en-US" +) -> dict: + """Get list of all genres for movies or TV shows. + + Args: + media_type: Type of media (movie or tv) + language: Language code (default: en-US) + """ + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get( + get_url( + f"genre/{media_type}/list", + language=language + ) + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def get_movie_credits( + movie_id: int, + language: str = "en-US" +) -> dict: + """Get cast and crew for a movie. + + Args: + movie_id: TMDB movie ID + language: Language code (default: en-US) + """ + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get( + get_url( + f"movie/{movie_id}/credits", + language=language + ) + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def get_tv_credits( + tv_id: int, + language: str = "en-US" +) -> dict: + """Get cast and crew for a TV show. + + Args: + tv_id: TMDB TV show ID + language: Language code (default: en-US) + """ + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get( + get_url( + f"tv/{tv_id}/credits", + language=language + ) + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def get_recommendations( + media_type: str, + media_id: int, + language: str = "en-US", + page: int = 1 +) -> dict: + """Get similar movies or TV shows based on a title. + + Args: + media_type: Type of media (movie or tv) + media_id: TMDB media ID + language: Language code (default: en-US) + page: Page number for pagination + """ + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get( + get_url( + f"{media_type}/{media_id}/recommendations", + language=language, + page=page + ) + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def get_streaming_providers( + media_type: str, + media_id: int +) -> dict: + """Get streaming availability for a movie or TV show by region. + + Args: + media_type: Type of media (movie or tv) + media_id: TMDB media ID + """ + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get( + get_url(f"{media_type}/{media_id}/watch/providers") + ) + response.raise_for_status() + return response.json() + + +if __name__ == "__main__": + mcp.run() diff --git a/servers/tmdb/test.json b/servers/tmdb/test.json new file mode 100644 index 0000000..e1e8708 --- /dev/null +++ b/servers/tmdb/test.json @@ -0,0 +1,64 @@ +{ + "tests": [ + { + "name": "Search Movies", + "tool": "search_movies", + "params": { + "query": "The Matrix", + "language": "en-US", + "page": 1 + }, + "expectedFields": [ + "results", + "page", + "total_results" + ], + "assertions": [ + { + "type": "exists", + "path": "results" + }, + { + "type": "exists", + "path": "page" + } + ] + }, + { + "name": "Get Trending Movies", + "tool": "get_trending", + "params": { + "media_type": "movie", + "time_window": "day", + "language": "en-US" + }, + "expectedFields": [ + "results", + "page" + ], + "assertions": [ + { + "type": "exists", + "path": "results" + } + ] + }, + { + "name": "Get Movie Genres", + "tool": "get_genres", + "params": { + "media_type": "movie", + "language": "en-US" + }, + "expectedFields": [ + "genres" + ], + "assertions": [ + { + "type": "exists", + "path": "genres" + } + ] + } + ] +}