search_subtitles
Find subtitles by searching with text, IMDB/TMDB IDs, file hashes, or TV show details. Filter results by language, translation type, accessibility features, and sort preferences.
Instructions
Search for subtitles using OpenSubtitles API with comprehensive parameter support
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | No | Text search query | |
| imdb_id | No | IMDB ID for exact movie/series matching | |
| tmdb_id | No | TMDB ID for exact movie/series matching | |
| parent_imdb_id | No | Parent IMDB ID for TV series | |
| parent_tmdb_id | No | Parent TMDB ID for TV series | |
| season_number | No | Season number for TV episodes | |
| episode_number | No | Episode number for TV episodes | |
| year | No | Release year | |
| moviehash | No | OpenSubtitles file hash for exact matching | |
| moviebytesize | No | File size in bytes for hash matching | |
| languages | No | Comma-separated language codes (e.g., 'en,es,fr') | |
| machine_translated | No | Include machine translated subtitles (exclude, include, only) | |
| ai_translated | No | Include AI translated subtitles (exclude, include, only) | |
| hearing_impaired | No | Include hearing impaired subtitles (exclude, include, only) | |
| foreign_parts_only | No | Include foreign parts only subtitles (exclude, include, only) | |
| trusted_sources | No | Only trusted sources (exclude, include, only) | |
| order_by | No | Sort order (language, download_count, new, rating) | |
| order_direction | No | Sort direction (asc, desc) | |
| username | No | OpenSubtitles.com username for authentication | |
| password | No | OpenSubtitles.com password for authentication |
Implementation Reference
- src/tools/search-subtitles.ts:28-181 (handler)The core handler function `searchSubtitles` that implements the tool logic: input validation, authentication handling, API search call, result formatting, and response generation.export async function searchSubtitles(args: unknown) { try { // Validate input arguments const validatedArgs = SearchArgsSchema.parse(args); // Extract authentication parameters from args const { user_api_key, username, password, ...searchParams } = validatedArgs; // Create API client const client = new OpenSubtitlesKongClient(); // Handle authentication let authValue: string | undefined = user_api_key; let isToken = false; // If username/password provided, login to get token if (username && password && !user_api_key) { try { const loginResponse = await client.login({ username, password }); authValue = loginResponse.token; isToken = true; console.error(`DEBUG: Successfully logged in user ${username}, got token`); } catch (loginError) { console.error("DEBUG: Login failed:", loginError); // Continue with default API key (authValue will be undefined) } } // Perform search const searchResults = await client.searchSubtitles( searchParams as SearchParams, authValue, isToken ); // Format results for MCP response const formattedResults = { total_results: searchResults.total_count, total_pages: searchResults.total_pages, current_page: searchResults.page, per_page: searchResults.per_page, subtitles: searchResults.data.map(subtitle => ({ subtitle_id: subtitle.attributes.subtitle_id, language: subtitle.attributes.language, movie_info: { title: subtitle.attributes.feature_details?.title || "", movie_name: subtitle.attributes.feature_details?.movie_name || "", year: subtitle.attributes.feature_details?.year || 0, imdb_id: subtitle.attributes.feature_details?.imdb_id || 0, tmdb_id: subtitle.attributes.feature_details?.tmdb_id || null, season_number: subtitle.attributes.feature_details?.season_number || null, episode_number: subtitle.attributes.feature_details?.episode_number || null, parent_title: subtitle.attributes.feature_details?.parent_title || null, parent_imdb_id: subtitle.attributes.feature_details?.parent_imdb_id || null, parent_tmdb_id: subtitle.attributes.feature_details?.parent_tmdb_id || null, }, quality_info: { download_count: subtitle.attributes.download_count || 0, new_download_count: subtitle.attributes.new_download_count || 0, hearing_impaired: subtitle.attributes.hearing_impaired || false, hd: subtitle.attributes.hd || false, fps: subtitle.attributes.fps || null, votes: subtitle.attributes.votes || 0, points: subtitle.attributes.points || 0, ratings: subtitle.attributes.ratings || 0, from_trusted: subtitle.attributes.from_trusted || false, foreign_parts_only: subtitle.attributes.foreign_parts_only || false, ai_translated: subtitle.attributes.ai_translated || false, machine_translated: subtitle.attributes.machine_translated || false, }, upload_info: { upload_date: subtitle.attributes.upload_date || "", uploader_name: subtitle.attributes.uploader?.name || "", uploader_rank: subtitle.attributes.uploader?.rank || "", release: subtitle.attributes.release || "", comments: subtitle.attributes.comments || "", }, files: (subtitle.attributes.files || []).map(file => ({ file_id: file.file_id, file_name: file.file_name, })), webpage_url: subtitle.attributes.url || "", view_online: subtitle.attributes.url ? `${subtitle.attributes.url}` : "", })) }; // Create a human-readable summary with clickable links const topSubtitles = formattedResults.subtitles.slice(0, 5); let summaryText = `## Search Results: ${formattedResults.total_results} subtitles found\n\n`; if (topSubtitles.length > 0) { summaryText += `**Top ${topSubtitles.length} Results:**\n\n`; topSubtitles.forEach((subtitle, index) => { const title = subtitle.movie_info.title || 'Unknown Title'; const year = subtitle.movie_info.year ? ` (${subtitle.movie_info.year})` : ''; const downloads = subtitle.quality_info.download_count || 0; const lang = subtitle.language || 'unknown'; const fps = subtitle.quality_info.fps ? ` | ${subtitle.quality_info.fps} FPS` : ''; const hd = subtitle.quality_info.hd ? ' | HD' : ''; const trusted = subtitle.quality_info.from_trusted ? ' | ā Trusted' : ''; const release = subtitle.upload_info.release ? ` | Release: ${subtitle.upload_info.release}` : ''; summaryText += `### ${index + 1}. ${title}${year}\n`; summaryText += `- **Language:** ${lang}${fps}${hd}${trusted}\n`; summaryText += `- **Downloads:** ${downloads.toLocaleString()}\n`; if (release) summaryText += `- **Release:** ${subtitle.upload_info.release}\n`; if (subtitle.upload_info.comments) summaryText += `- **Notes:** ${subtitle.upload_info.comments}\n`; // Add clickable link if (subtitle.webpage_url) { summaryText += `- **š View/Download:** ${subtitle.webpage_url}\n`; } summaryText += '\n'; }); if (formattedResults.total_results > 5) { summaryText += `*...and ${formattedResults.total_results - 5} more results*\n\n`; } } summaryText += `---\nš **Page:** ${formattedResults.current_page}/${formattedResults.total_pages} | **Per page:** ${formattedResults.per_page}\n\n`; summaryText += `**Raw data:**\n\`\`\`json\n${JSON.stringify(formattedResults, null, 2)}\n\`\`\``; return { content: [ { type: "text", text: summaryText, }, ], }; } catch (error) { console.error("Search subtitles error:", error); let errorMessage = "Failed to search subtitles"; if (error instanceof z.ZodError) { errorMessage = `Invalid parameters: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`; } else if (error instanceof Error) { errorMessage = error.message; } return { content: [ { type: "text", text: `Error: ${errorMessage}`, }, ], }; } }
- src/tools/search-subtitles.ts:4-26 (schema)Zod schema defining the input parameters and validation for the search_subtitles tool.const SearchArgsSchema = z.object({ query: z.string().optional(), imdb_id: z.number().optional(), tmdb_id: z.number().optional(), parent_imdb_id: z.number().optional(), parent_tmdb_id: z.number().optional(), season_number: z.number().optional(), episode_number: z.number().optional(), year: z.number().optional(), moviehash: z.string().optional(), moviebytesize: z.number().optional(), languages: z.string().optional(), machine_translated: z.string().optional(), ai_translated: z.string().optional(), hearing_impaired: z.string().optional(), foreign_parts_only: z.string().optional(), trusted_sources: z.string().optional(), order_by: z.string().optional(), order_direction: z.string().optional(), user_api_key: z.string().optional(), username: z.string().optional(), password: z.string().optional(), });
- src/server.ts:21-108 (registration)Tool registration in the MCP server: defines name, description, and inputSchema in the tools array returned by getTools().name: "search_subtitles", description: "Search for subtitles using OpenSubtitles API with comprehensive parameter support", inputSchema: { type: "object", properties: { query: { type: "string", description: "Text search query" }, imdb_id: { type: "number", description: "IMDB ID for exact movie/series matching" }, tmdb_id: { type: "number", description: "TMDB ID for exact movie/series matching" }, parent_imdb_id: { type: "number", description: "Parent IMDB ID for TV series" }, parent_tmdb_id: { type: "number", description: "Parent TMDB ID for TV series" }, season_number: { type: "number", description: "Season number for TV episodes" }, episode_number: { type: "number", description: "Episode number for TV episodes" }, year: { type: "number", description: "Release year" }, moviehash: { type: "string", description: "OpenSubtitles file hash for exact matching" }, moviebytesize: { type: "number", description: "File size in bytes for hash matching" }, languages: { type: "string", description: "Comma-separated language codes (e.g., 'en,es,fr')" }, machine_translated: { type: "string", description: "Include machine translated subtitles (exclude, include, only)" }, ai_translated: { type: "string", description: "Include AI translated subtitles (exclude, include, only)" }, hearing_impaired: { type: "string", description: "Include hearing impaired subtitles (exclude, include, only)" }, foreign_parts_only: { type: "string", description: "Include foreign parts only subtitles (exclude, include, only)" }, trusted_sources: { type: "string", description: "Only trusted sources (exclude, include, only)" }, order_by: { type: "string", description: "Sort order (language, download_count, new, rating)" }, order_direction: { type: "string", description: "Sort direction (asc, desc)" }, username: { type: "string", description: "OpenSubtitles.com username for authentication" }, password: { type: "string", description: "OpenSubtitles.com password for authentication" } }, additionalProperties: false }
- src/server.ts:337-339 (registration)Handler dispatch in handleToolCall switch statement: maps tool name to the searchSubtitles function call.case "search_subtitles": console.error("DEBUG: Calling searchSubtitles"); return await searchSubtitles(args);
- src/server.ts:3-3 (registration)Import of the searchSubtitles handler function into the server module.import { searchSubtitles } from "./tools/search-subtitles.js";