MCP server for pulling YouTube video transcripts. Single Rust binary. No Python, Node, or yt-dlp needed.
ironbeard-mcp-youtube
MCP server for pulling YouTube video transcripts. Single Rust binary. No Python, Node, or yt-dlp needed.
Features
- Grabs transcripts from any YouTube video that has captions
- Four output formats: plain text, timestamped, SRT subtitles, JSON
- Language resolution with prefix matching and automatic fallback
- File-based caching with 7-day TTL (SHA-256 hashed keys)
- Retries with exponential backoff on network errors
- Saves transcripts as markdown with YAML frontmatter
- Works on Linux, macOS, and Windows
- Plugs into Claude Desktop, Cursor, or any MCP client
Installation
git clone https://github.com/Alatar86/ironbeard-mcp-youtube.git
cd ironbeard-mcp-youtube
cargo build --release
Binary lands at target/release/ironbeard-mcp-youtube.
Usage
MCP Server Setup
The server runs over stdio. Point your MCP client at the binary.
Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"youtube-transcript": {
"command": "/path/to/ironbeard-mcp-youtube"
}
}
}
Cursor (.cursor/mcp.json in workspace root):
{
"mcpServers": {
"youtube-transcript": {
"command": "/path/to/ironbeard-mcp-youtube"
}
}
}
Tool: get_youtube_transcript
| Parameter | Type | Required | Description |
|------------|----------|----------|-------------|
| url | string | Yes | YouTube video URL or 11-character video ID |
| language | string | No | BCP-47 language code (e.g., en, es, ja). Defaults to the video's primary language. |
| format | string | No | Output format: plain (default), timestamped, srt, json |
Accepted URL formats:
https://www.youtube.com/watch?v=VIDEO_IDhttps://youtu.be/VIDEO_IDhttps://youtube.com/embed/VIDEO_ID- Raw 11-character video ID (e.g.,
dQw4w9WgXcQ)
Command-Line Options
| Flag | Description | Default |
|------|-------------|---------|
| --transcript-dir <PATH> | Where to save transcript .md files | ~/Documents/youtube-transcripts |
| --cache-dir <PATH> | Where to store cached transcript JSON | Platform cache directory (see below) |
As a Rust Library
use ironbeard_mcp_youtube::{TranscriptService, OutputFormat, SaveOutcome};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let service = TranscriptService::new()?;
let result = service
.get_transcript("dQw4w9WgXcQ", Some("en"), OutputFormat::Plain)
.await?;
println!("{}", result.formatted_text);
match &result.save_outcome {
SaveOutcome::Saved(path) => println!("Saved to: {}", path.display()),
SaveOutcome::Failed(err) => eprintln!("Save failed: {}", err),
SaveOutcome::NotAttempted => {}
}
Ok(())
}
Output Formats
Plain
All caption text joined with spaces:
We're no strangers to love You know the rules and so do I...
Timestamped
Each segment prefixed with [HH:MM:SS]:
[00:00:18] We're no strangers to love
[00:00:22] You know the rules and so do I
SRT
Standard SubRip subtitle format:
1
00:00:18,000 --> 00:00:22,000
We're no strangers to love
2
00:00:22,000 --> 00:00:27,000
You know the rules and so do I
JSON
Structured array:
[
{"start": 18.0, "duration": 4.0, "text": "We're no strangers to love"},
{"start": 22.0, "duration": 5.0, "text": "You know the rules and so do I"}
]
Saved Transcripts
Every transcript gets saved as a markdown file named {title} - {author}.md with YAML frontmatter:
---
title: "Never Gonna Give You Up"
author: "Rick Astley"
video_id: "dQw4w9WgXcQ"
url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
language: "en"
duration: "00:03:33"
duration_seconds: 213
date_saved: "2026-02-17"
---
# Never Gonna Give You Up
We're no strangers to love You know the rules and so do I...
Configuration
| Environment Variable | Description | Default |
|---------------------|-------------|---------|
| TRANSCRIPT_DIR | Where .md transcript files get saved | ~/Documents/youtube-transcripts |
You can set this in your MCP client config:
{
"mcpServers": {
"youtube-transcript": {
"command": "/path/to/ironbeard-mcp-youtube",
"env": {
"TRANSCRIPT_DIR": "/path/to/my/transcripts"
}
}
}
}
The --transcript-dir CLI flag takes precedence over the environment variable.
Cache
Transcripts are cached as JSON files in the platform cache directory:
| Platform | Directory |
|----------|-----------|
| Linux | ~/.cache/ironbeard-mcp-youtube/ |
| macOS | ~/Library/Caches/ironbeard-mcp-youtube/ |
| Windows | %LOCALAPPDATA%\ironbeard-mcp-youtube\ |
Cache keys are SHA-256 hashes of video_id + language. Entries expire after 7 days and get cleaned up on access.
How It Works
- Parse the video ID from the URL or raw ID
- Check the file cache for a valid entry
- On miss, hit YouTube's innertube
/playerAPI for metadata and caption track URLs - Fetch the timedtext XML from the resolved caption track
- Parse the XML into transcript lines, decode HTML entities
- Cache the result as JSON
- Save transcript as markdown with YAML frontmatter
- Format and return the transcript in the requested output format
Language resolution tries exact match first, then prefix match (en matches en-US), then falls back to the first available track. Network requests retry up to 3 times with exponential backoff (1s, 2s, 4s).
Architecture
src/
main.rs - Entry point, CLI args, starts MCP server on stdio
lib.rs - Public API exports
server.rs - MCP server (rmcp 0.15), tool definitions
service.rs - TranscriptService: fetch, cache, format, save
config.rs - Configuration (directories, env vars, defaults)
youtube/
mod.rs - Video ID parsing, module exports
api.rs - Transcript fetching with retry logic
client.rs - HTTP client with cookie/consent handling
parser.rs - Player API JSON, timedtext XML, and json3 parsing
proto.rs - Manual protobuf encoding for innertube params
types.rs - Data structures (TranscriptLine, VideoMetadata, etc.)
error.rs - Error types (VideoNotFound, NoCaptions, RateLimited, etc.)
processing/
mod.rs - Output formatters (plain, timestamped, SRT, JSON)
cache/
mod.rs - File-based transcript cache with SHA-256 keys and TTL
tests/
integration_test.rs - End-to-end tests against live YouTube
Dependencies
| Crate | Purpose |
|-------|---------|
| rmcp | MCP server framework (stdio transport) |
| tokio | Async runtime |
| reqwest | HTTP client with cookie support |
| serde / serde_json | Serialization |
| schemars | JSON Schema generation for MCP tool input |
| regex | URL parsing, XML extraction |
| sha2 | Cache key hashing |
| dirs | Platform-specific directory resolution |
| clap | CLI argument parsing |
| chrono | Date formatting for frontmatter |
| anyhow | Application error handling |
| thiserror | Error type derivation |
| tracing / tracing-subscriber | Structured logging |
| base64 / urlencoding | Protobuf param encoding for innertube API |
| slug | Filename slugification for non-Latin titles |
| lazy_static | Static regex compilation |
Development
# Unit tests (offline)
cargo test --lib
# All tests including integration (hits YouTube)
cargo test
# Check formatting
cargo fmt --check
# Lint
cargo clippy
# Build release
cargo build --release
Limitations
- Video needs captions (human or auto-generated)
- Video must be public
- Language availability depends on what YouTube has
- Rapid requests can get rate-limited (server retries automatically)
License
MIT
Contact
Bug reports: GitHub Issues or Ironbeardai@gmail.com