Skip to content

feat(yt-dlp-mcp): in-tree MCP server + /add-ytdlp installer#2306

Open
CrAzyScreamx wants to merge 8 commits into
nanocoai:mainfrom
CrAzyScreamx:feat/yt-dlp
Open

feat(yt-dlp-mcp): in-tree MCP server + /add-ytdlp installer#2306
CrAzyScreamx wants to merge 8 commits into
nanocoai:mainfrom
CrAzyScreamx:feat/yt-dlp

Conversation

@CrAzyScreamx

Copy link
Copy Markdown

Type of Change

  • Feature skill - adds a channel or integration (source code changes + SKILL.md)
  • Utility skill - adds a standalone tool (code files in .claude/skills/<name>/, no source changes)
  • Operational/container skill - adds a workflow or agent skill (SKILL.md only, no source changes)
  • Fix - bug fix or security fix to source code
  • Simplification - reduces or simplifies source code
  • Documentation - docs, README, or CONTRIBUTING changes only

Description

Adds /add-ytdlp — installs yt-dlp (search YouTube, fetch metadata, download video/audio from YouTube, Vimeo, X, TikTok, ~1000 other sites) as an MCP tool for agent groups.

Two pieces:

  1. Install skill (.claude/skills/add-ytdlp/SKILL.md) — patches container/Dockerfile to install the standalone PyInstaller yt-dlp binary (~30MB, no Python on PATH needed, pinned via YTDLP_VERSION
    arg), then wires the in-tree MCP server into selected agent groups' container.json under mcpServers. Idempotent install/removal with anchored Dockerfile blocks.

  2. In-tree MCP server (container/agent-runner/src/yt-dlp-mcp/) — stdio transport, spawned by Bun directly off the source bind mount,
    Curated 4-tool surface tuned for chat agents:

    • ytdlp_search — paginated search with sort/duration/views filters, md or json output, maxChars cap
    • ytdlp_get_metadata — full json or compact summary, maxChars cap
    • ytdlp_download_video — mp4/H.264 (avc1) default for broad device compatibility, falls back to best-quality on failure
    • ytdlp_download_audio — mp3 default via ffmpeg, falls back to native bestaudio if ffmpeg missing or transcode fails

YTDLP_DOWNLOADS_DIR defaults to /tmp so downloaded files are ready for mcp__nanoclaw__send_file.

For Skills

  • SKILL.md contains instructions, not inline code (code goes in separate files)
  • SKILL.md is under 500 lines
  • I tested this skill on a fresh clone

CrAzyScreamx and others added 8 commits May 5, 2026 13:51
Mirrors /add-ffmpeg's tactic: patches container/Dockerfile to install
the standalone yt-dlp_linux binary, adds @kevinwatt/yt-dlp-mcp as an
agent-runner runtime dep (pinned), and wires it as a stdio MCP server
per agent group via mcpServers entry. The MCP server self-describes
its 8 ytdlp_* tools (search, metadata, subtitles, transcripts, video,
audio), so no container skill is shipped alongside.

YTDLP_DOWNLOADS_DIR is set to /workspace/agent/tmp so downloads are
ready for mcp__nanoclaw__send_file. Idempotent install via the
'# ---- yt-dlp' Dockerfile anchor; removal section reverses each
phase symmetrically.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OneCLI's gateway intercepts HTTPS with a self-signed CA and sets
SSL_CERT_FILE at the container level so tools trust it. The MCP SDK's
StdioClientTransport, however, strips most env vars when spawning MCP
server children — only HOME/LOGNAME/PATH/SHELL/TERM/USER propagate by
default — so yt-dlp inside the MCP child never sees the CA bundle and
fails with CERTIFICATE_VERIFY_FAILED, even on plain YouTube URLs.

Re-inject SSL_CERT_FILE, REQUESTS_CA_BUNDLE, and CURL_CA_BUNDLE
through the per-MCP env block in container.json so they survive the
filter. The bundle is already mounted by OneCLI at
/tmp/onecli-combined-ca.pem; we just need to point yt-dlp's three
network paths (stdlib ssl, requests/urllib3, curl fallback) at it.

Also adds a troubleshooting entry for the cert verify failure mode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous attempt (4361425) re-injected SSL_CERT_FILE,
REQUESTS_CA_BUNDLE, and CURL_CA_BUNDLE through the per-MCP env block
on the theory that yt-dlp would honor them and trust OneCLI's
intercepting CA. Empirical test in a live agent container showed
this didn't work: standalone yt-dlp (PyInstaller binary) uses
certifi's bundled CA store baked into the binary itself, ignoring
all three env vars. curl trusts OneCLI's CA via the same env vars
fine, but yt-dlp doesn't.

The actual fix is to bypass the proxy entirely for yt-dlp via
NO_PROXY=*. yt-dlp is fetching public video from YouTube/Vimeo/etc.
— there's no credentialed API for OneCLI to inject into, so routing
it through the gateway has no upside, only the cert friction.
Both upper and lower case are set because Python stdlib checks
NO_PROXY while some libs check no_proxy.

Verified inside a live container: yt-dlp --simulate against YouTube
returns the title with NO_PROXY=*; without it, fails with
CERTIFICATE_VERIFY_FAILED on every retry.

Updates the troubleshooting entry to reflect the actual cause and
fix (binary's bundled certifi store cannot be overridden via env).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switch the default download directory to /tmp, which is container-
internal (not bind-mounted from the host). Previously, /workspace/agent/tmp
was a RW bind-mount of groups/<folder>/tmp/, so every video the agent
downloaded persisted on the host indefinitely — there is no auto-sweep
for non-ffmpeg files in that directory, so downloads accumulated.

/tmp lives only inside the running container, so --rm cleans everything
up on container exit. send_file already copies the bytes to
/workspace/outbox/<msg-id>/ before delivery, so downloads don't need
to outlive the container — there's no functional regression, just less
host clutter.

Updates Phase 4 default + the explanation paragraph + troubleshooting.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the upstream @kevinwatt/yt-dlp-mcp dependency with an in-tree MCP
server under container/agent-runner/src/yt-dlp-mcp/. Mirrors the existing
ffmpeg-mcp pattern (stdio transport, per-group opt-in via container.json,
spawned by Bun directly off the source bind mount).

Curated tool surface tuned for chat agents:
  ytdlp_search          search with pagination, sort, duration/views filters,
                        md or json output, maxChars cap
  ytdlp_get_metadata    full json or compact summary, maxChars cap
  ytdlp_download_video  mp4 (default) → best-quality fallback on failure
  ytdlp_download_audio  mp3 (default) → native bestaudio fallback if ffmpeg
                        is missing or transcoding fails

Skill rewritten to drop the bun add step; container.json now points at
/app/src/yt-dlp-mcp/server.ts. Dockerfile patch (yt-dlp binary install) is
unchanged.
The previous mp4 selector (`bv*[ext=mp4]+ba[ext=m4a]`) resolved to whatever
codec yt-dlp found inside the mp4 container. On most modern sources that's
VP9 or AV1, which does not play in QuickTime, iOS Photos, or render
inline in WhatsApp/Telegram — the user has to re-encode before sending.

Add a `codec` parameter (default `h264`) that injects a vcodec filter into
the format selector. Other codecs are exposed (h265, av1, vp9, any) for
when the caller wants something else, but the chat-agent default now
produces a file that plays everywhere out of the box.

The existing best-quality fallback still runs unchanged when the codec
constraint can't be satisfied, so requests don't fail just because a
source lacks an H.264 ladder.
@github-actions github-actions Bot added follows-guidelines PR was created using the current contributing template PR: Feature New feature or enhancement PR: Skill Skill package or skill-related changes labels May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

follows-guidelines PR was created using the current contributing template PR: Feature New feature or enhancement PR: Skill Skill package or skill-related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant