yt-media-info-mcp
by bartivs
README.md
# yt-media-info MCP
[](LICENSE)
[](https://modelcontextprotocol.io)
[](https://github.com/yt-dlp/yt-dlp)
[](#docker-compose)
[](#prerequisites)
> Extract rich metadata, transcripts, and search from any yt-dlp-supported media URL — for Claude, Anthropic, and any MCP-compatible AI assistant.
**yt-media-info MCP** is a [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that lets AI assistants extract structured metadata from media URLs across **1800+ sites** using [yt-dlp](https://github.com/yt-dlp/yt-dlp) — YouTube, Vimeo, Twitch, podcasts, and more. Given a URL (video, playlist, channel, podcast), it returns title, description, duration, chapters, subtitles/captions, formats, and statistics that models can reason over.
Built to sit alongside web-search tools as a **media-enrichment step** in an information-gathering pipeline. Works with Claude Desktop, Claude Code, LiteLLM, and any MCP client over stdio or SSE.
## Works with
Compatible with any client that speaks the Model Context Protocol:
- **[Claude Desktop](https://claude.ai/download)** — via stdio transport
- **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** — via SSE transport
- **[LiteLLM](https://github.com/BerriAI/litellm)** — as an `mcp` model in the gateway config
- **Open WebUI** and any MCP-aware agent framework
- **Custom apps** — via the MCP SDK (SSE) or the plain JSON `POST /api` shortcut
## Table of Contents
- [Features](#features)
- [Use Cases](#use-cases)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Configuration](#configuration)
- [Usage with Claude Desktop, Claude Code, LiteLLM, and the Direct API](#usage-with-claude-desktop-claude-code-litellm-and-the-direct-api)
- [Docker Compose](#docker-compose)
- [Available MCP Tools](#available-mcp-tools)
- [Available Prompts](#available-prompts)
- [Output Conventions: snake_case fields and ISO 8601 dates](#output-conventions-snake-case-fields-and-iso-8601-dates)
- [Cookie Management and Authentication](#cookie-management-and-authentication)
- [Scope: metadata and transcripts only, no downloads](#scope-metadata-and-transcripts-only-no-downloads)
- [Development](#development)
- [License: MIT](#license-mit)
## Features
- **Extract rich metadata** from any yt-dlp-supported URL (YouTube, Vimeo, Twitch, and ~1800 more sites)
- **Fetch transcripts** with timestamps or as full text
- **Search for media** across supported platforms (supplementary discovery)
- **Curated + raw output**: focused summary at the top level, full yt-dlp info dict nested under `raw`
- **Snake_case fields, ISO 8601 dates** — matches yt-dlp's native format
- **Optional two-layer auth**: yt-dlp site credentials + bearer API key for your own endpoints
- **Multiple transport options**: stdio for Claude Desktop, SSE for web clients
- **Direct API endpoint** (`POST /api`) for quick testing without MCP protocol
- **Persistent Python backend**: no cold-start per call (imports yt-dlp once at startup)
## Use Cases
- **RAG over video** — pull a video's transcript and metadata into a retrieval pipeline so an LLM can answer questions about the content without watching it.
- **Summarize lectures, talks, and podcasts** — feed the transcript to a model for key points, notable quotes, and takeaways (see the `summarize_transcript` prompt).
- **Podcast & lecture indexing** — extract titles, descriptions, chapters, and durations to build searchable catalogs of audio/video content.
- **Accessibility via captions** — retrieve subtitles (manual or auto-generated) in any available language for transcription and translation workflows.
- **Channel & playlist research** — expand a playlist or channel into structured per-video metadata for analysis, deduplication, or ranking.
- **Media enrichment in search pipelines** — pair with a web-search tool: discover candidate URLs, then enrich each one with full metadata and transcripts before summarization.
- **Content discovery** — use `search_media` to find videos on YouTube or Google Video by query, then drill into the ones that matter.
## Prerequisites
- Node.js 18+
- Python 3.12+ (for standalone development)
- Docker + Docker Compose (for recommended deployment)
## Installation
```bash
# Clone the repository
cd yt-media-info-mcp
# Install Node dependencies
npm install
# Build the Python service Docker image
docker compose build yt-dlp-service
```
### Standalone Python service (without Docker)
If you want to run the Python service directly:
```bash
cd service
pip install -r requirements.txt
uvicorn main:app --host 0.0.0.0 --port 8000
```
Then in another terminal:
```bash
YT_MEDIA_INFO_SERVICE_URL=http://localhost:8000 npm start
```
## Configuration
### Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `ENABLE_SSE` | Use SSE transport (vs stdio) | `0` |
| `YT_MEDIA_INFO_PORT` | HTTP server port (SSE mode) | `9423` |
| `YT_MEDIA_INFO_HOST` | HTTP server host (SSE mode) | `0.0.0.0` |
| `YT_MEDIA_INFO_SERVICE_URL` | URL of the Python yt-dlp service | `http://yt-media-info-service:8000` |
| `YT_MEDIA_INFO_API_KEY` | Optional bearer API key for HTTP endpoints | (empty = no auth) |
| `YT_MEDIA_INFO_USERNAME` | Default username for yt-dlp site auth | (empty) |
| `YT_MEDIA_INFO_PASSWORD` | Default password for yt-dlp site auth | (empty) |
| `LOG_LEVEL` | Winston log level (error, warn, info, debug) | `info` |
Copy `.env.example` to `.env` and customize. `.env` is gitignored — use `.env.local` for per-machine secrets not tracked by git.
## Usage with Claude Desktop, Claude Code, LiteLLM, and the Direct API
### Claude Desktop (stdio)
```json
{
"mcpServers": {
"yt-dlp": {
"command": "node",
"args": ["/path/to/yt-media-info-mcp/src/index.js"],
"env": {
"ENABLE_SSE": "0"
}
}
}
}
```
### Claude Code (SSE)
```json
{
"mcpServers": {
"yt-dlp": {
"type": "sse",
"url": "http://localhost:9423/sse"
}
}
}
```
### LiteLLM
```yaml
# config.yaml
model_list:
- model_name: yt-dlp
litellm_params:
model: mcp
mcp_servers:
yt-dlp:
transport: sse
url: http://host.docker.internal:9423/sse
```
### Direct API
The `POST /api` endpoint bypasses the MCP protocol and returns results directly:
```bash
# Extract info
curl -X POST http://localhost:9423/api \
-H "Content-Type: application/json" \
-d '{
"tool": "extract_info",
"args": {
"url": "https://www.youtube.com/watch?v=YE7VzlLtp-4"
}
}'
# Get transcript
curl -X POST http://localhost:9423/api \
-H "Content-Type: application/json" \
-d '{
"tool": "get_transcript",
"args": {
"url": "https://www.youtube.com/watch?v=YE7VzlLtp-4",
"language": "en"
}
}'
# Search media
curl -X POST http://localhost:9423/api \
-H "Content-Type: application/json" \
-d '{
"tool": "search_media",
"args": {
"query": "python tutorial",
"limit": 5
}
}'
```
### Web clients (MCP SSE)
The server exposes standard MCP SSE endpoints:
| Endpoint | Purpose |
|----------|---------|
| `GET /sse` | SSE connection stream (MCP transport) |
| `POST /messages` | Send MCP JSON-RPC messages to the server |
| `POST /api` | Direct JSON API (bypasses MCP) |
| `GET /health` | Health check |
```javascript
// Connect via MCP SDK
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
const transport = new SSEClientTransport(new URL('http://localhost:9423/sse'));
const client = new Client({ name: 'web-app', version: '1.0' });
await client.connect(transport);
const result = await client.request(
{ method: 'tools/call', params: { name: 'extract_info', arguments: { url: 'https://www.youtube.com/watch?v=YE7VzlLtp-4' } } },
resultSchema
);
```
## Docker Compose
```bash
docker compose up -d # start both services
docker compose logs -f # tail logs
docker compose down # stop
docker compose build # rebuild after changes
```
The `yt-dlp-service` container is persistent and stays warm. The `yt-media-info-mcp` container waits for the health check on the Python service before accepting connections.
## Available MCP Tools
### extract_info
Extracts rich metadata from a media URL.
**Parameters:**
| Parameter | Type | Description | Default |
|-----------|------|-------------|---------|
| `url` | string | Media URL to extract information from | (required) |
| `include_raw` | boolean | Include the full yt-dlp sanitized info_dict under `raw` | `true` |
| `username` | string? | Username for site authentication | `null` |
| `password` | string? | Password for site authentication | `null` |
**Output:** Curated metadata (title, description, duration, uploader, statistics, chapters, thumbnails, formats summary, subtitles available, playlist info) + optional raw info dict.
### get_transcript
Fetches subtitles or transcript text for a media URL.
**Parameters:**
| Parameter | Type | Description | Default |
|-----------|------|-------------|---------|
| `url` | string | Media URL to fetch transcript from | (required) |
| `language` | string | Preferred subtitle language code | `"en"` |
| `timestamps` | boolean | Include timestamp segments in response | `true` |
| `username` | string? | Username for site authentication | `null` |
| `password` | string? | Password for site authentication | `null` |
**Output:** Language, duration, subtitle segments (with timestamps if requested), and concatenated full_text.
### search_media
Supplementary discovery tool. Searches for media using yt-dlp's search prefixes (e.g. `ytsearch:`). This is a companion to general-purpose web search — it finds candidate URLs for further enrichment.
**Parameters:**
| Parameter | Type | Description | Default |
|-----------|------|-------------|---------|
| `query` | string | Search query | (required) |
| `limit` | integer | Maximum number of results (max 50) | `10` |
| `platform` | string | Platform to search. Supported: `youtube`, `google_videos` | `"youtube"` |
**Output:** Results array with url, title, duration_seconds, uploader, upload_date, thumbnail, view_count.
## Available Prompts
- **analyze_video**: Analyze a video/media item from its available metadata (title, description, duration, uploader, categories, optional transcript summary).
- **summarize_transcript**: Summarize a video transcript to extract key points, notable quotes, and practical takeaways.
## Output Conventions: snake_case fields and ISO 8601 dates
- **snake_case** field names (matches yt-dlp's native format)
- **ISO 8601** date strings (e.g. `"2024-01-15"` for upload_date, `"2024-01-15T14:30:00Z"` for timestamps)
- **Best-effort error handling**: complete failures return an error response; missing fields are `null`; playlist entries that fail are collected in a `failures` array
## Cookie Management and Authentication
When running in SSE mode, the server provides a web-based cookie upload form at `http://<host>:<port>/` (default `http://localhost:9423/`) for uploading Netscape-format cookie files.
### Web Upload Flow
1. **Export cookies** from your browser using yt-dlp:
```bash
yt-dlp --cookies-from-browser chrome --cookies cookies.txt
```
Or use a browser extension like [Get cookies.txt LOCALLY](https://chrome.google.com/webstore/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc).
2. **Open the form** at `http://localhost:9423/` in your browser.
3. **Upload** the `cookies.txt` file — the form validates the file format, writes it atomically to the shared Docker volume at `/data/cookies.txt`, and displays parsed cookie info (domains, count, earliest expiry).
4. **Delete** cookies via the form's delete button when needed.
### Endpoints
| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/` | HTML upload form |
| `POST` | `/upload-cookies` | Upload a cookies.txt file (multipart/form-data, field name `cookies`) |
| `POST` | `/delete-cookies` | Delete the cookie file |
All endpoints are protected by the same `YT_MEDIA_INFO_API_KEY` bearer auth as the other HTTP endpoints (when configured).
### Cookie File Format
The file must:
- Start with `# Netscape HTTP Cookie File`
- Be under 1 MB
- Use tab-separated Netscape cookie format
### Cookie-bot Sidecar
If you have the [cookie-bot sidecar](AGENTS.md#cookie-bot-optional-sidecar) running (opt-in via `docker compose --profile cookies up -d`), it will periodically refresh cookies from the shared volume. The web upload form is a convenient way to seed the initial cookie file — the cookie-bot then takes over automated refreshes.
> **Note:** The cookie-bot's automated refresh will overwrite a manually uploaded file. Use the web form for initial seeding, then let the bot handle refreshes.
## Scope: metadata and transcripts only, no downloads
**This server does NOT download media files.** It is a metadata enrichment and transcript extraction tool designed to work alongside other search and retrieval tools. No ffmpeg is required.
## Development
```bash
npm run dev # nodemon auto-restart
npm run lint # ESLint
npm run lint:fix # ESLint auto-fix
```
## License: MIT
This project is licensed under the [MIT License](LICENSE).
TDQS
A3.9/5.0
Scored across 3 tools
Disambiguation5/5
Each tool has a distinct purpose: metadata extraction, transcript retrieval, and media search. No overlap in functionality.
Naming Consistency5/5
All tool names follow a consistent verb_noun pattern (extract_info, get_transcript, search_media) using lowercase snake_case.
Tool Count5/5
Three tools is appropriate for a focused media info server, covering core operations without excess.
Completeness5/5
The tool set covers the main use cases for media information: metadata extraction, transcript fetching, and search. No obvious gaps for its stated purpose.
Maintenance
ActivityStale
ResponsivenessNo issues