fetch_youtube_subtitles
Extract subtitles or transcripts from YouTube videos in SRT, VTT, TXT, or JSON formats with timestamps and language options.
Instructions
Fetch subtitles/transcripts from YouTube videos. Supports multiple output formats (SRT, VTT, TXT, JSON) and language selection. Returns complete subtitle content with timestamps.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | YouTube video URL or video ID. Supported formats: https://www.youtube.com/watch?v=xxx, https://youtu.be/xxx, or direct video ID | |
| format | No | Output format. SRT: subtitle file format (with sequence numbers), VTT: WebVTT format, TXT: plain text (text only), JSON: structured JSON (with timestamps) | JSON |
| lang | No | Subtitle language code (optional). Examples: zh-Hans (Simplified Chinese), zh-Hant (Traditional Chinese), en (English). Auto-detect if not specified |
Implementation Reference
- Core handler function that orchestrates subtitle fetching: validates args, extracts video ID, uses Innertube to get transcript, processes segments into TranscriptItem[], formats output, constructs response with metadata or error message.async run(args: { url: string; format?: string; lang?: string }) { try { // 1️⃣ Parameter validation if (!args.url) { throw new Error("Parameter 'url' is required"); } const format = args.format || "JSON"; const lang = args.lang || undefined; // 2️⃣ Extract video ID const videoId = extractVideoId(args.url); // 3️⃣ Initialize YouTube client const youtube = await Innertube.create(); // 4️⃣ Get video info const info = await youtube.getInfo(videoId); // 5️⃣ Fetch transcript with optional language let transcriptData; try { transcriptData = await info.getTranscript(); } catch (error: any) { throw new Error(`No subtitle data found: ${error.message}`); } if (!transcriptData || !transcriptData.transcript) { throw new Error("No subtitle data found"); } const transcript = transcriptData.transcript; const segments = transcript.content?.body?.initial_segments; if (!segments || segments.length === 0) { throw new Error("No subtitle segments found"); } // 6️⃣ Convert to standard format const transcriptItems: TranscriptItem[] = segments.map((seg: any) => ({ text: seg.snippet?.text || "", offset: seg.start_ms || 0, duration: (seg.end_ms || 0) - (seg.start_ms || 0), })); // 7️⃣ Format output const formattedContent = formatSubtitles(transcriptItems, format); // 8️⃣ Determine actual language let actualLanguage = lang || "auto"; const transcriptDataAny = transcriptData as any; if (transcriptDataAny.transcript_search_panel?.footer?.language_menu) { const selectedLang = transcriptDataAny.transcript_search_panel.footer.language_menu.sub_menu_items?.find( (item: any) => item.selected ); if (selectedLang) { actualLanguage = selectedLang.title || actualLanguage; } } // 9️⃣ Return result const result = { success: true, videoId, format, language: actualLanguage, subtitleCount: transcriptItems.length, content: formattedContent, }; return { content: [ { type: "text" as const, text: `# YouTube Subtitle Extraction Result **Video ID**: ${videoId} **Video Title**: ${info.basic_info.title || 'N/A'} **Format**: ${format} **Language**: ${result.language} **Subtitle Count**: ${result.subtitleCount} --- ${formattedContent}`, }, ], }; } catch (error: any) { return { content: [ { type: "text" as const, text: `❌ Failed to fetch subtitles: ${error.message} **Possible reasons**: - Video has no available subtitles - Video is private or restricted - Specified language code does not exist - Network connection issue **Tips**: - Try without specifying language code (auto-detect) - Verify the video URL is correct - Check if the video has public subtitles`, }, ], isError: true, }; } },
- JSON Schema definition for the tool's input parameters including url (required), optional format and lang.name: "fetch_youtube_subtitles", description: "Fetch subtitles/transcripts from YouTube videos. Supports multiple output formats (SRT, VTT, TXT, JSON) and language selection. Returns complete subtitle content with timestamps.", parameters: { type: "object", properties: { url: { type: "string", description: "YouTube video URL or video ID. Supported formats: https://www.youtube.com/watch?v=xxx, https://youtu.be/xxx, or direct video ID", }, format: { type: "string", enum: ["SRT", "VTT", "TXT", "JSON"], default: "JSON", description: "Output format. SRT: subtitle file format (with sequence numbers), VTT: WebVTT format, TXT: plain text (text only), JSON: structured JSON (with timestamps)", }, lang: { type: "string", description: "Subtitle language code (optional). Examples: zh-Hans (Simplified Chinese), zh-Hant (Traditional Chinese), en (English). Auto-detect if not specified", }, }, required: ["url"], },
- src/index.ts:22-51 (registration)Tool registration in the stdio MCP server using McpServer.registerTool, providing Zod inputSchema and handler wrapper.server.registerTool( fetchYoutubeSubtitles.name, { title: "Fetch YouTube Subtitles", description: fetchYoutubeSubtitles.description, inputSchema: { url: z .string() .describe( "YouTube video URL or video ID. Supported formats: https://www.youtube.com/watch?v=xxx, https://youtu.be/xxx, or direct video ID" ), format: z .enum(["SRT", "VTT", "TXT", "JSON"]) .default("JSON") .optional() .describe( "Output format. SRT: subtitle file format (with sequence numbers), VTT: WebVTT format, TXT: plain text (text only), JSON: structured JSON (with timestamps)" ), lang: z .string() .optional() .describe( "Subtitle language code (optional). Examples: zh-Hans (Simplified Chinese), zh-Hant (Traditional Chinese), en (English). Auto-detect if not specified" ), }, }, async (args) => { return await fetchYoutubeSubtitles.run(args); } );
- src/httpServer.ts:29-59 (registration)Tool registration in the HTTP MCP server using McpServer.registerTool within createMCPServer function, with Zod schema and handler.server.registerTool( fetchYoutubeSubtitles.name, { title: "Fetch YouTube Subtitles", description: fetchYoutubeSubtitles.description, inputSchema: { url: z .string() .describe( "YouTube video URL or video ID. Supported formats: https://www.youtube.com/watch?v=xxx, https://youtu.be/xxx, or direct video ID" ), format: z .enum(["SRT", "VTT", "TXT", "JSON"]) .default("JSON") .optional() .describe( "Output format. SRT: subtitle file format (with sequence numbers), VTT: WebVTT format, TXT: plain text (text only), JSON: structured JSON (with timestamps)" ), lang: z .string() .optional() .describe( "Subtitle language code (optional). Examples: zh-Hans (Simplified Chinese), zh-Hant (Traditional Chinese), en (English). Auto-detect if not specified" ), }, }, async (args) => { const result = await fetchYoutubeSubtitles.run(args); return result; } );
- src/tools/utils.ts:8-22 (helper)Utility function to extract YouTube video ID from various URL formats or plain ID.export function extractVideoId(url: string): string { const patterns = [ /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/, /^([a-zA-Z0-9_-]{11})$/, ]; for (const pattern of patterns) { const match = url.match(pattern); if (match) { return match[1]; } } throw new Error("Unable to extract video ID from URL"); }
- src/tools/formatters.ts:67-80 (helper)Helper function that converts TranscriptItem array to specified subtitle format (SRT, VTT, TXT, JSON) using dedicated converters.export function formatSubtitles(transcript: TranscriptItem[], format: string): string { switch (format.toUpperCase()) { case "SRT": return convertToSRT(transcript); case "VTT": return convertToVTT(transcript); case "TXT": return convertToTXT(transcript); case "JSON": return convertToJSON(transcript); default: throw new Error(`Unsupported format: ${format}. Supported formats: SRT, VTT, TXT, JSON`); } }