Skip to main content
Glama

TMDB MCP Server

by dervish666
mcp-server.ts17.9 kB
import axios, { AxiosInstance } from 'axios'; import 'dotenv/config'; // --- Configuration --- const TMDB_API_KEY = process.env.TMDB_API_KEY; const TMDB_BASE_URL = process.env.TMDB_BASE_URL || 'https://api.themoviedb.org/3'; // --- Basic Validation --- if (!TMDB_API_KEY) { console.error("FATAL ERROR: TMDB_API_KEY must be defined in your environment variables."); process.exit(1); } // --- TMDB API Client --- const tmdbApi: AxiosInstance = axios.create({ baseURL: TMDB_BASE_URL, params: { api_key: TMDB_API_KEY, }, }); // --- MCP Tool Definitions --- const tools = [ { name: 'searchMovies', description: 'Search for movies by title or keywords.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'The movie title or keywords to search for.' }, page: { type: 'number', description: 'Page number for pagination (default: 1).' }, year: { type: 'number', description: 'Filter by release year (optional).' } }, required: ['query'] }, }, { name: 'searchTVShows', description: 'Search for TV shows by title or keywords.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'The TV show title or keywords to search for.' }, page: { type: 'number', description: 'Page number for pagination (default: 1).' }, first_air_date_year: { type: 'number', description: 'Filter by first air date year (optional).' } }, required: ['query'] }, }, { name: 'getMovieDetails', description: 'Get detailed information about a specific movie.', inputSchema: { type: 'object', properties: { movieId: { type: 'number', description: 'The TMDB movie ID.' }, append_to_response: { type: 'string', description: 'Additional data to include (e.g., "credits,videos,reviews").' } }, required: ['movieId'] }, }, { name: 'getTVShowDetails', description: 'Get detailed information about a specific TV show.', inputSchema: { type: 'object', properties: { tvId: { type: 'number', description: 'The TMDB TV show ID.' }, append_to_response: { type: 'string', description: 'Additional data to include (e.g., "credits,videos,reviews").' } }, required: ['tvId'] }, }, { name: 'getPopularMovies', description: 'Get a list of popular movies.', inputSchema: { type: 'object', properties: { page: { type: 'number', description: 'Page number for pagination (default: 1).' }, region: { type: 'string', description: 'ISO 3166-1 code for region-specific results (optional).' } }, required: [] }, }, { name: 'getPopularTVShows', description: 'Get a list of popular TV shows.', inputSchema: { type: 'object', properties: { page: { type: 'number', description: 'Page number for pagination (default: 1).' } }, required: [] }, }, { name: 'getTrendingMovies', description: 'Get trending movies for a specific time window.', inputSchema: { type: 'object', properties: { time_window: { type: 'string', enum: ['day', 'week'], description: 'Time window for trending (default: day).' }, page: { type: 'number', description: 'Page number for pagination (default: 1).' } }, required: [] }, }, { name: 'getTrendingTVShows', description: 'Get trending TV shows for a specific time window.', inputSchema: { type: 'object', properties: { time_window: { type: 'string', enum: ['day', 'week'], description: 'Time window for trending (default: day).' }, page: { type: 'number', description: 'Page number for pagination (default: 1).' } }, required: [] }, }, { name: 'getPersonDetails', description: 'Get detailed information about a person (actor, director, etc.).', inputSchema: { type: 'object', properties: { personId: { type: 'number', description: 'The TMDB person ID.' }, append_to_response: { type: 'string', description: 'Additional data to include (e.g., "movie_credits,tv_credits").' } }, required: ['personId'] }, }, { name: 'searchPeople', description: 'Search for people (actors, directors, etc.) by name.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'The person name to search for.' }, page: { type: 'number', description: 'Page number for pagination (default: 1).' } }, required: ['query'] }, } ]; // --- Tool Implementation --- const toolImplementations: { [key: string]: (params: any) => Promise<any> } = { searchMovies: async ({ query, page = 1, year }: { query: string; page?: number; year?: number }) => { const params: any = { query, page }; if (year) params.year = year; const { data } = await tmdbApi.get('/search/movie', { params }); return { page: data.page, total_pages: data.total_pages, total_results: data.total_results, results: data.results.map((movie: any) => ({ id: movie.id, title: movie.title, release_date: movie.release_date, overview: movie.overview, vote_average: movie.vote_average, vote_count: movie.vote_count, poster_path: movie.poster_path ? `https://image.tmdb.org/t/p/w500${movie.poster_path}` : null, backdrop_path: movie.backdrop_path ? `https://image.tmdb.org/t/p/w1280${movie.backdrop_path}` : null, genre_ids: movie.genre_ids, popularity: movie.popularity })) }; }, searchTVShows: async ({ query, page = 1, first_air_date_year }: { query: string; page?: number; first_air_date_year?: number }) => { const params: any = { query, page }; if (first_air_date_year) params.first_air_date_year = first_air_date_year; const { data } = await tmdbApi.get('/search/tv', { params }); return { page: data.page, total_pages: data.total_pages, total_results: data.total_results, results: data.results.map((show: any) => ({ id: show.id, name: show.name, first_air_date: show.first_air_date, overview: show.overview, vote_average: show.vote_average, vote_count: show.vote_count, poster_path: show.poster_path ? `https://image.tmdb.org/t/p/w500${show.poster_path}` : null, backdrop_path: show.backdrop_path ? `https://image.tmdb.org/t/p/w1280${show.backdrop_path}` : null, genre_ids: show.genre_ids, popularity: show.popularity, origin_country: show.origin_country })) }; }, getMovieDetails: async ({ movieId, append_to_response }: { movieId: number; append_to_response?: string }) => { const params: any = {}; if (append_to_response) params.append_to_response = append_to_response; const { data } = await tmdbApi.get(`/movie/${movieId}`, { params }); return { id: data.id, title: data.title, original_title: data.original_title, overview: data.overview, release_date: data.release_date, runtime: data.runtime, vote_average: data.vote_average, vote_count: data.vote_count, popularity: data.popularity, budget: data.budget, revenue: data.revenue, genres: data.genres, production_companies: data.production_companies, production_countries: data.production_countries, spoken_languages: data.spoken_languages, poster_path: data.poster_path ? `https://image.tmdb.org/t/p/w500${data.poster_path}` : null, backdrop_path: data.backdrop_path ? `https://image.tmdb.org/t/p/w1280${data.backdrop_path}` : null, homepage: data.homepage, imdb_id: data.imdb_id, status: data.status, tagline: data.tagline, ...(data.credits && { credits: data.credits }), ...(data.videos && { videos: data.videos }), ...(data.reviews && { reviews: data.reviews }) }; }, getTVShowDetails: async ({ tvId, append_to_response }: { tvId: number; append_to_response?: string }) => { const params: any = {}; if (append_to_response) params.append_to_response = append_to_response; const { data } = await tmdbApi.get(`/tv/${tvId}`, { params }); return { id: data.id, name: data.name, original_name: data.original_name, overview: data.overview, first_air_date: data.first_air_date, last_air_date: data.last_air_date, number_of_episodes: data.number_of_episodes, number_of_seasons: data.number_of_seasons, vote_average: data.vote_average, vote_count: data.vote_count, popularity: data.popularity, genres: data.genres, created_by: data.created_by, episode_run_time: data.episode_run_time, in_production: data.in_production, languages: data.languages, networks: data.networks, origin_country: data.origin_country, production_companies: data.production_companies, production_countries: data.production_countries, seasons: data.seasons, spoken_languages: data.spoken_languages, status: data.status, tagline: data.tagline, type: data.type, poster_path: data.poster_path ? `https://image.tmdb.org/t/p/w500${data.poster_path}` : null, backdrop_path: data.backdrop_path ? `https://image.tmdb.org/t/p/w1280${data.backdrop_path}` : null, homepage: data.homepage, ...(data.credits && { credits: data.credits }), ...(data.videos && { videos: data.videos }), ...(data.reviews && { reviews: data.reviews }) }; }, getPopularMovies: async ({ page = 1, region }: { page?: number; region?: string }) => { const params: any = { page }; if (region) params.region = region; const { data } = await tmdbApi.get('/movie/popular', { params }); return { page: data.page, total_pages: data.total_pages, total_results: data.total_results, results: data.results.map((movie: any) => ({ id: movie.id, title: movie.title, release_date: movie.release_date, overview: movie.overview, vote_average: movie.vote_average, vote_count: movie.vote_count, poster_path: movie.poster_path ? `https://image.tmdb.org/t/p/w500${movie.poster_path}` : null, backdrop_path: movie.backdrop_path ? `https://image.tmdb.org/t/p/w1280${movie.backdrop_path}` : null, genre_ids: movie.genre_ids, popularity: movie.popularity })) }; }, getPopularTVShows: async ({ page = 1 }: { page?: number }) => { const { data } = await tmdbApi.get('/tv/popular', { params: { page } }); return { page: data.page, total_pages: data.total_pages, total_results: data.total_results, results: data.results.map((show: any) => ({ id: show.id, name: show.name, first_air_date: show.first_air_date, overview: show.overview, vote_average: show.vote_average, vote_count: show.vote_count, poster_path: show.poster_path ? `https://image.tmdb.org/t/p/w500${show.poster_path}` : null, backdrop_path: show.backdrop_path ? `https://image.tmdb.org/t/p/w1280${show.backdrop_path}` : null, genre_ids: show.genre_ids, popularity: show.popularity, origin_country: show.origin_country })) }; }, getTrendingMovies: async ({ time_window = 'day', page = 1 }: { time_window?: string; page?: number }) => { const { data } = await tmdbApi.get(`/trending/movie/${time_window}`, { params: { page } }); return { page: data.page, total_pages: data.total_pages, total_results: data.total_results, results: data.results.map((movie: any) => ({ id: movie.id, title: movie.title, release_date: movie.release_date, overview: movie.overview, vote_average: movie.vote_average, vote_count: movie.vote_count, poster_path: movie.poster_path ? `https://image.tmdb.org/t/p/w500${movie.poster_path}` : null, backdrop_path: movie.backdrop_path ? `https://image.tmdb.org/t/p/w1280${movie.backdrop_path}` : null, genre_ids: movie.genre_ids, popularity: movie.popularity })) }; }, getTrendingTVShows: async ({ time_window = 'day', page = 1 }: { time_window?: string; page?: number }) => { const { data } = await tmdbApi.get(`/trending/tv/${time_window}`, { params: { page } }); return { page: data.page, total_pages: data.total_pages, total_results: data.total_results, results: data.results.map((show: any) => ({ id: show.id, name: show.name, first_air_date: show.first_air_date, overview: show.overview, vote_average: show.vote_average, vote_count: show.vote_count, poster_path: show.poster_path ? `https://image.tmdb.org/t/p/w500${show.poster_path}` : null, backdrop_path: show.backdrop_path ? `https://image.tmdb.org/t/p/w1280${show.backdrop_path}` : null, genre_ids: show.genre_ids, popularity: show.popularity, origin_country: show.origin_country })) }; }, getPersonDetails: async ({ personId, append_to_response }: { personId: number; append_to_response?: string }) => { const params: any = {}; if (append_to_response) params.append_to_response = append_to_response; const { data } = await tmdbApi.get(`/person/${personId}`, { params }); return { id: data.id, name: data.name, biography: data.biography, birthday: data.birthday, deathday: data.deathday, place_of_birth: data.place_of_birth, profile_path: data.profile_path ? `https://image.tmdb.org/t/p/w500${data.profile_path}` : null, adult: data.adult, also_known_as: data.also_known_as, gender: data.gender, homepage: data.homepage, imdb_id: data.imdb_id, known_for_department: data.known_for_department, popularity: data.popularity, ...(data.movie_credits && { movie_credits: data.movie_credits }), ...(data.tv_credits && { tv_credits: data.tv_credits }) }; }, searchPeople: async ({ query, page = 1 }: { query: string; page?: number }) => { const { data } = await tmdbApi.get('/search/person', { params: { query, page } }); return { page: data.page, total_pages: data.total_pages, total_results: data.total_results, results: data.results.map((person: any) => ({ id: person.id, name: person.name, profile_path: person.profile_path ? `https://image.tmdb.org/t/p/w500${person.profile_path}` : null, adult: person.adult, gender: person.gender, known_for_department: person.known_for_department, popularity: person.popularity, known_for: person.known_for })) }; } }; // --- MCP Server Implementation --- class MCPServer { private requestId = 0; constructor() { process.stdin.setEncoding('utf8'); process.stdin.on('data', this.handleInput.bind(this)); process.stderr.write('TMDB MCP Server started\n'); } private handleInput(data: string) { const lines = data.trim().split('\n'); for (const line of lines) { if (line.trim()) { try { const request = JSON.parse(line); this.handleRequest(request); } catch (error) { this.sendError(-32700, 'Parse error', null); } } } } private async handleRequest(request: any) { const { jsonrpc, method, params, id } = request; if (jsonrpc !== '2.0') { this.sendError(-32600, 'Invalid Request', id); return; } try { let result; switch (method) { case 'initialize': result = { protocolVersion: '2024-11-05', capabilities: { tools: {} }, serverInfo: { name: 'tmdb-mcp-server', version: '1.0.0' } }; break; case 'tools/list': result = { tools }; break; case 'tools/call': const { name, arguments: args } = params; const toolImplementation = toolImplementations[name]; if (!toolImplementation) { this.sendError(-32601, 'Tool not found', id); return; } const toolResult = await toolImplementation(args || {}); result = { content: [ { type: 'text', text: typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult, null, 2) } ] }; break; case 'notifications/initialized': // Just acknowledge - no response needed for notifications return; default: this.sendError(-32601, 'Method not found', id); return; } this.sendResponse(result, id); } catch (error: any) { process.stderr.write(`Error processing method '${method}': ${error.message}\n`); const code = error.code || -32603; const message = error.response?.data?.status_message || error.response?.data?.message || error.message || 'Internal error'; this.sendError(code, message, id); } } private sendResponse(result: any, id: any) { const response = { jsonrpc: '2.0', result, id }; process.stdout.write(JSON.stringify(response) + '\n'); } private sendError(code: number, message: string, id: any) { const response = { jsonrpc: '2.0', error: { code, message }, id }; process.stdout.write(JSON.stringify(response) + '\n'); } } // Start the MCP server new MCPServer();

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/dervish666/tmdb-mcp-server'

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