Skip to content
Conduit

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

bran9910/youtube-ai-agentcommunitylistedunreviewed

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, 2026

youtube-ai-agent

License: MIT n8n Groq MCP

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

ToolDescriptionParameters
ingest_videoDownload, transcribe and store a YouTube video. Call this first.url
list_videosBrowse the video library with pagination.limit, offset
search_videosFull-text search across all transcripts and titles.query
get_raw_transcriptReturn the full raw transcript. ⚠️ Can be 20K+ tokens.video_id
get_summaryGenerate or retrieve a cached summary.video_id, type
analyze_framesExtract frames and analyze with vision AI. Use sparingly.video_id
compare_videosCompare 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

ComponentRole
n8n 2.x (Docker)Workflow engine + MCP server endpoint
PostgreSQL (Docker)Transcript and analysis storage
yt-dlpVideo download, metadata, subtitle extraction
Groq Whisper large-v3Audio transcription (free tier available)
Groq LLaMA 3.3 70BSummarization and synthesis
Groq llama-4-scoutVision — 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_frames uses 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_videos and youtube_analyses tables 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 nodes
  • tool_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 activeVersionId in 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.txt is lost on container restart. Re-copy the file after each restart, or mount it as a volume in docker-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

OperationTypical 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

ScriptPurpose
scripts/create_mcp_workflows.pyCreate and publish all 8 n8n workflows via API
scripts/fix_subworkflows.pyPatch sub-workflows (add Parse Input node for n8n 2.x DynamicTool)
scripts/setup.shCreate data directories, check system dependencies
scripts/test_pipeline.shEnd-to-end smoke test

Optimizations built in

  1. Subtitle-first transcription — YouTube subtitles tried before Whisper, saving ~90% of audio API calls.
  2. Audio splitting — MP3s > 25MB split into 20-min segments transparently.
  3. Map-reduce summarization — transcripts chunked with 200-char overlap, summarized per chunk, then synthesized.
  4. Full caching layer — all analysis results cached in PostgreSQL; subsequent calls return instantly at zero API cost.
  5. Token guard — MCP responses capped at ~4000 tokens (16K chars) with a truncation warning.
  6. Idempotent ingestion — re-ingesting a known URL returns the cached result without re-downloading.
  7. Frame limit guard — analyze_frames caps 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:

  1. No video file on disk — ingest_video only downloads audio (-x --audio-format mp3). The Extract Frames node must download the video itself via yt-dlp if no .mp4/.webm/.mkv is found in the video directory.
  2. JS template literal interpolation — In n8n Code nodes, ${INTERVAL} inside a template literal is parsed as a JavaScript variable (evaluates to undefined), 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.
  3. Stale empty cache — A failed run may have stored a frames entry with frame_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-Whisper en 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 Small ou Medium).
  • 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

↑↓ navigate openesc close