mcp server
youtube-ai-agent
MCP server: 7 AI tools to ingest, transcribe, summarize and analyze YouTube videos — powered by n8n, PostgreSQL, Groq and yt-dlp
Set up
Runs on your computer. Pick your agent and operating system.
This project does not publish an install method we can turn into a config. Follow the README instead.
README
synced Oct 23, 2026youtube-ai-agent
An MCP server that gives AI agents 7 tools to ingest, transcribe, summarize, and analyze YouTube videos — powered by n8n, PostgreSQL, Groq, and yt-dlp, running entirely on your own VPS.
Pipeline d'analyse YouTube piloté par des agents IA via MCP. Transcription, résumés, recherche full-text et analyse visuelle de frames, propulsés par n8n, PostgreSQL et Groq API sur votre propre serveur.
How it works
Claude Code / any MCP client
│
│ MCP Streamable HTTP (Bearer auth)
▼
n8n ── /mcp/youtube-agent
│
├── ingest_video yt-dlp + Groq Whisper + LLaMA + PostgreSQL
├── list_videos paginated DB listing
├── search_videos PostgreSQL GIN full-text search
├── get_raw_transcript full transcript from DB
├── get_summary Groq LLaMA 3.3 70B (cached in DB)
├── analyze_frames ffmpeg + Claude Vision (cached in DB)
└── compare_videos Groq LLaMA comparative synthesis
Tools
| Tool | Description | Parameters |
|---|---|---|
ingest_video | Download, transcribe and store a YouTube video. Call this first. | url |
list_videos | Browse the video library with pagination. | limit, offset |
search_videos | Full-text search across all transcripts and titles. | query |
get_raw_transcript | Return the full raw transcript. ⚠️ Can be 20K+ tokens. | video_id |
get_summary | Generate or retrieve a cached summary. | video_id, type |
analyze_frames | Extract frames and analyze with vision AI. Use sparingly. | video_id |
compare_videos | Compare up to 5 videos on a specific question. | video_ids[], question |
Summary types: global (~300 words) · detailed (chapters + key points) · book (long-form article)
Stack
| Component | Role |
|---|---|
| n8n 2.x (Docker) | Workflow engine + MCP server endpoint |
| PostgreSQL (Docker) | Transcript and analysis storage |
| yt-dlp | Video download, metadata, subtitle extraction |
| Groq Whisper large-v3 | Audio transcription (free tier available) |
| Groq LLaMA 3.3 70B | Summarization and synthesis |
| Groq llama-4-scout | Vision — frame analysis only |
Prerequisites
- VPS with Docker + Docker Compose
- A domain pointing to your VPS (n8n needs a public HTTPS URL for webhooks)
- Groq API key — free tier is sufficient
- Anthropic API key — not required,
analyze_framesuses Groq Vision
Deployment
1. Clone and configure
git clone https://github.com/your-username/youtube-ai-agent.git
cd youtube-ai-agent
cp .env.example .env
nano .env
Required values in .env:
GROQ_API_KEY=gsk_...
# ANTHROPIC_API_KEY — not needed, analyze_frames uses Groq Vision
N8N_VPS_URL=https://your-domain.com
N8N_MCP_TOKEN= # openssl rand -base64 32
POSTGRES_PASSWORD=changeme
DATA_DIR=/data/youtube
2. Start Docker services
The n8n/ directory contains a Docker Compose setup with a custom image that includes yt-dlp, ffmpeg, jq, and python3 baked in.
cd n8n/
docker compose up -d --build
This starts two containers: n8n (port 5678) and n8n-postgres.
3. Create the database schema
docker exec -i n8n-postgres psql -U n8n -d n8n < ../database/schema.sql
The schema creates
youtube_videosandyoutube_analysestables plus GIN full-text search indexes.
4. Create the n8n workflows
cd ..
python3 scripts/create_mcp_workflows.py
This script creates all 8 n8n workflows via the API and publishes them:
YouTube MCP Server— the main mcpTrigger with 7 toolWorkflow nodestool_ingest_video,tool_list_videos,tool_search_videos,tool_get_raw_transcript,tool_get_summary,tool_analyze_frames,tool_compare_videos— sub-workflows
n8n 2.x note: The script also sets
activeVersionIdin the database so workflows are treated as "published" and their triggers register on startup. Without this step, workflows appear active in the UI but their webhooks don't register.
5. Verify
# Initialize an MCP session and list tools
SESSION=$(curl -si -X POST https://your-domain.com/mcp/youtube-agent \
-H "Authorization: Bearer $N8N_MCP_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}' \
| grep -i mcp-session-id | awk '{print $2}' | tr -d '\r')
curl -s -X POST https://your-domain.com/mcp/youtube-agent \
-H "Authorization: Bearer $N8N_MCP_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $SESSION" \
-d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":2}'
You should see all 7 tools in the response.
Connecting to Claude Code
Create .mcp.json in your project root (or ~/.mcp.json for global access):
{
"mcpServers": {
"youtube-agent": {
"type": "http",
"url": "https://your-domain.com/mcp/youtube-agent",
"headers": {
"Authorization": "Bearer YOUR_N8N_MCP_TOKEN"
}
}
}
}
Restart Claude Code. The 7 tools appear automatically.
Usage examples:
> Use ingest_video with url "https://youtube.com/watch?v=XXXX"
> Use get_summary with video_id "XXXX" and type "detailed"
> Use search_videos with query "machine learning transformers"
> Use compare_videos with video_ids ["id1","id2"] and question "Which approach is better for beginners?"
Connecting other MCP clients
Any client supporting MCP Streamable HTTP works:
URL: https://your-domain.com/mcp/youtube-agent
Transport: Streamable HTTP
Auth: Bearer YOUR_N8N_MCP_TOKEN
See mcp/openclaw-connector.md for OpenClaw-specific instructions.
Ingestion pipeline
YouTube URL
→ yt-dlp metadata (title, duration, uploader, id)
→ yt-dlp download audio MP3
→ yt-dlp try subtitles (auto/manual) ─── found → parse VTT
└── not found → Groq Whisper
→ Groq LLaMA 3.3 70B global summary (auto)
→ PostgreSQL INSERT (youtube_videos + youtube_analyses)
Audio splitting: videos > 25MB are split into 20-min segments before Whisper (API limit).
Subtitle-first strategy: YouTube subtitles are tried before Whisper — saves ~90% of Groq audio API calls for captioned content.
YouTube bot detection (2026): yt-dlp requires additional flags to bypass YouTube's JS challenge solver. All yt-dlp calls use:
yt-dlp --js-runtimes node --remote-components ejs:github --cookies /tmp/yt-cookies.txt ...
--js-runtimes node— uses Node.js (bundled in the n8n Docker image) to solve JS challenges--remote-components ejs:github— downloads the EJS challenge solver script from GitHub on first run--cookies /path/to/cookies.txt— bypasses bot detection; export from your browser with the Get cookies.txt LOCALLY extension, then copy into the container:docker cp cookies.txt n8n:/tmp/yt-cookies.txt
Note:
/tmp/yt-cookies.txtis lost on container restart. Re-copy the file after each restart, or mount it as a volume indocker-compose.yml.
Map-reduce chunking: transcripts > 16K chars are split, summarized per chunk, then synthesized.
Database schema
youtube_videos
video_id (UNIQUE) url title duration uploader
transcript_raw frames_dir status created_at
youtube_analyses
video_id (FK) output_type (global | detailed | book | frames)
content model_used tokens_used created_at
Full-text search uses a GIN index on transcript_raw || title.
Performance
| Operation | Typical time |
|---|---|
ingest_video (with subtitles) | 30–90s |
ingest_video (Whisper fallback) | 2–5 min |
get_summary / analyze_frames (cached) | < 1s |
get_summary (new, detailed) | 10–30s |
analyze_frames (new, video already on disk) | 1–3 min |
analyze_frames (new, video download required) | 5–15 min |
search_videos | < 1s |
Scripts
| Script | Purpose |
|---|---|
scripts/create_mcp_workflows.py | Create and publish all 8 n8n workflows via API |
scripts/fix_subworkflows.py | Patch sub-workflows (add Parse Input node for n8n 2.x DynamicTool) |
scripts/setup.sh | Create data directories, check system dependencies |
scripts/test_pipeline.sh | End-to-end smoke test |
Optimizations built in
- Subtitle-first transcription — YouTube subtitles tried before Whisper, saving ~90% of audio API calls.
- Audio splitting — MP3s > 25MB split into 20-min segments transparently.
- Map-reduce summarization — transcripts chunked with 200-char overlap, summarized per chunk, then synthesized.
- Full caching layer — all analysis results cached in PostgreSQL; subsequent calls return instantly at zero API cost.
- Token guard — MCP responses capped at ~4000 tokens (16K chars) with a truncation warning.
- Idempotent ingestion — re-ingesting a known URL returns the cached result without re-downloading.
- Frame limit guard —
analyze_framescaps at 60 frames to protect against runaway vision API costs.
Troubleshooting
"Signature solving failed" / "Only images are available for download" → yt-dlp can't solve YouTube's JS challenge. Make sure you're using the full flags:
yt-dlp --js-runtimes node --remote-components ejs:github --cookies /tmp/yt-cookies.txt ...
→ If cookies are missing: docker cp cookies.txt n8n:/tmp/yt-cookies.txt
"Cannot read properties of undefined (reading 'value')" in Call Groq Whisper
→ The HTTP Request node with formBinaryData can't read files from a path. The node must be a Code node using fs.readFileSync + https.request with multipart encoding.
"The workflow did not return a response" from a sub-workflow
→ A Merge node with chooseBranch: waitForBoth is blocking — it waits for both branches but only one runs. Remove the Merge node and let both terminal nodes return directly.
compare_videos always returns an empty comparison or crashes
→ The Parse Input node must extract video_ids (array) and question from the raw string input. YouTube video IDs are exactly 11 characters matching [a-zA-Z0-9_-]{11}. The question is detected via the question: keyword or by stripping all video IDs from the input. Without this, $json.video_ids.join(...) throws on undefined.
analyze_frames returns frame_count: 0 even after ingestion
→ Three possible causes:
- No video file on disk —
ingest_videoonly downloads audio (-x --audio-format mp3). TheExtract Framesnode must download the video itself via yt-dlp if no.mp4/.webm/.mkvis found in the video directory. - JS template literal interpolation — In n8n Code nodes,
${INTERVAL}inside a template literal is parsed as a JavaScript variable (evaluates toundefined), not a shell variable. Always compute the value in JavaScript first:const interval = Math.max(10, Math.floor(duration / 30))then use${interval}in the shell string. - Stale empty cache — A failed run may have stored a
framesentry withframe_count: 0. Purge it:docker exec n8n-postgres psql -U n8n -d youtube_agent -c "DELETE FROM youtube_analyses WHERE video_id='<id>' AND output_type='frames';"
"process is not defined" in a Code node
→ process.env is unavailable in n8n 2.x Code nodes. Hardcode the API key directly in the node.
"/tmp/yt-cookies.txt: No such file or directory"
→ Container was restarted and /tmp was cleared. Re-copy: docker cp /path/to/cookies.txt n8n:/tmp/yt-cookies.txt
Roadmap
- Batch ingestion: entire playlists or channels
- Transcript timestamps: align text to video timeline
- Multi-language summaries
- Export to PDF / EPUB / Markdown
- Auto-delete audio files after transcription
- Ollama fallback when Groq rate limits are hit
Alternative - Local Hosting (Fallback Strategy)
Si les limites des API gratuites (Groq Whisper) sont atteintes (~2h de vidéo/jour), le projet prévoit une transition vers l'auto-hébergement :
- Target : Migration vers
Faster-Whisperen local sur le VPS. - Hardware requis : Upgrade vers 4 CPU Cores / 8 Go RAM (permet la transcription en temps réel avec le modèle
SmallouMedium). - Bénéfice : Transcription illimitée 24h/24 sans dépendance aux clés API tierces.
License
MIT — see LICENSE
Built with n8n · Groq · yt-dlp · PostgreSQL