Skip to main content
Glama
Diterex

youtube-research-mcp

by Diterex

youtube-research-mcp

An MCP server that lets Claude research YouTube channels and read video transcripts, without a YouTube API key and without rendering a browser.

This talks to YouTube through yt-dlp rather than YouTube's official Data API, which is outside what YouTube's Terms of Service contemplate as sanctioned automated access. That's a real thing to know, not a formality:

  • yt-dlp itself is legal to build on and distribute. It's public domain (Unlicense), survived a 2020 DMCA takedown attempt (GitHub reinstated it after EFF intervention, on the grounds it has substantial non-infringing uses), and has millions of users. Individual open-source tools built on it, used for personal research, have a long track record of being left alone.

  • What has consistently drawn legal action is monetizing or centralizing access to YouTube content - commercial "download as a service" sites get sued and shut down repeatedly. This project is released free, MIT-licensed, meant to be cloned and run locally by each user against their own network - not offered as a hosted service. Reselling access to it, or running it as a shared service many people connect to, meaningfully changes that risk picture and isn't something this project endorses.

  • This is not legal advice, and no one associated with this project is liable for how you use it - see the LICENSE's warranty disclaimer. If you're planning anything beyond personal research use, get real legal review first.

  • YouTube's anti-automation measures (PO Tokens, bot-checks, IP-based rate limiting) are real, active, and have escalated in 2026 specifically - see "Notes and limits" below. This tool's reliability is inherently coupled to yt-dlp's ability to keep up; periodic maintenance (pip install --upgrade yt-dlp) is normal, not a sign something is broken.

Related MCP server: VidLens

How it works

Uses yt-dlp to talk to YouTube's own internal JSON endpoints directly, rather than rendering a page in a browser or calling the official Data API - no API key needed. Everything is read only. Video listings come from a flat playlist extraction (titles and IDs only, no video data), and transcripts are read straight from the caption track into memory. Only get_video_frames touches disk, and only a temp file it deletes when the server exits.

Tools

list_channel_videos(channel_url, max_results=50, resolve_all_dates=False)

Lists a channel's uploads, newest first.

  • channel_url accepts a handle (@mwganson), a bare name (mwganson), a channel ID (UCLNPmhURJNIm9wsRunKM8mA), any youtube.com channel URL with or without a /videos, /shorts or /streams tab, or a playlist URL.

  • max_results is 1 to 1000, default 50.

  • resolve_all_dates controls how upload dates are filled in. See the note below.

Returns channel, channel_id, channel_url, total_videos, count, and a videos list. Each video has video_id, title, url, duration_seconds, duration, view_count, and upload_date.

get_video_transcript(video_url_or_id, language="en", include_timestamps=False, max_chars=0)

Fetches a video's transcript as clean plain text.

  • video_url_or_id accepts an 11 character video ID or any watch, youtu.be, shorts, live or embed URL.

  • language is the preferred caption language code. Regional variants match too, so en will accept en-US. If the language is missing entirely, the first available track is used.

  • include_timestamps puts [H:MM:SS] at the start of each paragraph, which is what you want if you plan to cite a moment in the video.

  • max_chars truncates the result. 0 means no limit. Set it when you are scanning many videos, because a long tutorial can run tens of thousands of characters.

A human written caption track is preferred, and YouTube's auto generated one is the fallback. The result reports which you got in transcript_kind, along with title, channel, duration, upload_date, language, char_count, truncated, and the transcript itself as paragraphs of roughly 30 seconds each.

get_video_frames(video_url_or_id, timestamps=None, every_seconds=0, max_frames=6, width=1280, max_height=720, quality=4, output_dir=None, include_images=True)

Returns actual images of what the video shows at chosen moments, and can save them to disk in the same call.

Transcripts alone cannot capture a screen based tutorial. "Click this, then drag it here" has no referent in text. Toolbar clicks are usually silent. Typed dialog values are rarely spoken. And auto captions mangle exactly the technical terms you need, which you can see for yourself in the transcripts this tool returns, where PDWrapper comes through as "pt wrapper" and FreeCAD as "free cad".

So the intended workflow is two tiers, and doing it in this order is what keeps it cheap.

  1. Broad and cheap. get_video_transcript(..., include_timestamps=True) across as many videos as you like, to find which videos and which minutes matter.

  2. Narrow and visual. get_video_frames(video_id, timestamps=[...]) on just those moments.

  • timestamps is a list of 'S', 'M:SS' or 'H:MM:SS' strings, taken from step 1.

  • every_seconds samples evenly instead, for surveying an unfamiliar video. Explicit timestamps are far cheaper.

  • max_frames caps the result at 1 to 50, default 6. Every frame costs context when returned inline - see include_images below if you just want files on disk.

  • width is the output width, 320 to 1920, default 1280. Do not go below about 960 if you need to read menu labels.

  • max_height is the source stream height fetched, default 720. That is enough to read a CAD toolbar and keeps the fetch small.

  • output_dir saves every captured frame as a JPEG to a local directory (created if it doesn't exist), named {video_id}_{HH-MM-SS}.jpg. Written straight from the same ffmpeg output already produced for the in-context images - no separate download or re-extraction needed. None (default) saves nothing, same as before this existed.

  • include_images defaults to True (frames returned inline, as always). Set False only alongside output_dir to skip the context cost when you just want files saved, not looked at in this conversation.

Returns a text summary followed by one image per timestamp (unless include_images is False). Frames that fail to capture, or capture but fail to save, are noted in the summary rather than failing the whole call.

search_youtube(query, max_results=20, broader_terms=None, auto_broaden=True)

Keyword search, for when you do not know the channel or video yet. Returns the same video fields minus the upload date, plus relevance (0.0-1.0) and found_via. Use it to find candidates, then feed a result's channel or URL to one of the other two tools.

Widens itself automatically when a narrow query comes back empty or comes back with results that don't look on-topic — no need to remember to retry with a blunter query by hand. It runs your query as written, and if that's thin it also tries mechanical shortenings of your own words (dropping academic register terms like "optimization", trying the head and the lead of the phrase) plus any broader_terms you pass, then re-ranks everything by topic-word overlap so on-topic results surface and noise gets dropped again.

Always pass broader_terms when the topic matters — 2-3 alternate phrasings (practitioner slang, the blunt everyday name, adjacent tool/software names), e.g. broader_terms=["clay 3d printing", "paste extruder"]. This function is plain Python with no model in it, so it cannot invent a synonym or know that two phrasings name the same subject — only the calling session can do that. Without broader_terms, only mechanical shortening is possible, which is real but weaker.

Set auto_broaden=False to get the exact old behavior: your query, nothing else.

Install and register

The server has its own virtual environment on purpose, so installing yt-dlp here cannot disturb any other MCP server's dependencies. You'll also need ffmpeg on PATH for get_video_frames — the other three tools don't need it.

Windows (PowerShell):

cd path\to\youtube-research-mcp
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install .
winget install Gyan.FFmpeg   # if ffmpeg isn't already on PATH

macOS / Linux:

cd path/to/youtube-research-mcp
python3 -m venv .venv
./.venv/bin/python -m pip install .
brew install ffmpeg   # or apt-get install ffmpeg / your distro's package manager

That installs a yt-research-mcp console command into the venv - register it with Claude Code (user scope, so it is available in every project), replacing path/to with wherever you actually cloned this:

claude mcp add youtube-research --scope user -- `
  path\to\youtube-research-mcp\.venv\Scripts\yt-research-mcp.exe
claude mcp add youtube-research --scope user -- \
  path/to/youtube-research-mcp/.venv/bin/yt-research-mcp

Or add it by hand to ~/.claude.json (Windows path shown; use forward slashes on macOS/Linux and drop the .exe):

"youtube-research": {
  "type": "stdio",
  "command": "C:\\path\\to\\youtube-research-mcp\\.venv\\Scripts\\yt-research-mcp.exe"
}

Testing

Editable install first, so test changes to the code without reinstalling:

.\.venv\Scripts\python.exe -m pip install -e .     # Windows, once
.\.venv\Scripts\python.exe test_smoke.py
./.venv/bin/python -m pip install -e .              # macOS/Linux, once
./.venv/bin/python test_smoke.py

This hits YouTube for real, with no mocking, deliberately - every real failure mode found and fixed in this project (the cookie-database lock, the live-stream hang, the transient 403 on a large fetch) only showed up by testing against the real service; a mocked suite would have asserted the code does what it was written to do, not caught where that turned out to be wrong. It lists two real channels, pulls a real transcript, runs a search, checks every URL shape the parsers are supposed to handle, and rejects whatever's currently live on a 24/7 stream in a couple of seconds rather than attempting to download it. Prints a PASS or FAIL line per check, exits non zero if anything fails.

The real cost of that choice: every run needs network access and YouTube being up, and there's no fast offline loop for iterating on unrelated code. Accepted deliberately rather than fixed - an offline/mocked layer wouldn't have caught anything this pass found, and network-dependent tests are the right shape for a tool whose entire job is talking to a real external service.

Notes and limits

Upload dates. A flat channel listing does not carry upload dates, which is the tradeoff that makes it fast. So by default the server fills them in from the channel's public RSS feed, which is one extra cheap request and covers roughly the 15 most recent videos. Older videos come back with upload_date of null. Set resolve_all_dates=True to date the whole list, but that costs about one request per video, so keep max_results small when you do.

Total video count. total_videos is whatever YouTube reports and is often null. The count field is always accurate for what was returned.

Videos with no captions. Some videos genuinely have no caption track, not even an auto generated one. The server says so explicitly rather than returning an empty string, so there is no point retrying those.

If YouTube asks for a sign in. YouTube sometimes demands a signed in session and yt-dlp will report "Sign in to confirm you're not a bot". Tested against 90 real, distinct videos at meaningful concurrency (10 parallel workers) on 2026-08-08 without ever triggering it - yt-dlp calls YouTube's internal API with client emulation rather than scraping the rendered page, which is a different code path from what the casual "browser automation" bot-check usually catches. So the trigger itself is unverified here; what is verified is the fix and a real failure mode in it.

YT_DLP_COOKIES_FROM_BROWSER=chrome    # or firefox, edge - see the caveat below
YT_DLP_COOKIEFILE=/path/to/cookies.txt

YT_DLP_COOKIES_FROM_BROWSER fails outright while that browser is running, confirmed on this machine: with Chrome open, yt-dlp cannot copy its cookie database (PermissionError, since Chrome holds an exclusive lock on it) and the read fails completely - not silently, it raises CookieLoadError. Pointing at a browser that happens to be closed (edge, when Edge wasn't running) worked cleanly. Since Chrome being open is the normal case, not an edge case, YT_DLP_COOKIEFILE is the more reliable choice - export cookies.txt with a browser extension once, and it works regardless of what's running. The server now recognizes this specific failure (_friendly_error's CookieLoadError branch) and returns an actionable message telling you to close the browser or switch to YT_DLP_COOKIEFILE, instead of crashing with a raw Python traceback the way it did before this was found and fixed.

Why frames are fetched rather than seeked. The obvious design is to have ffmpeg seek the remote stream URL and range request only the bytes it needs. That does not work. YouTube binds a stream URL to the player client that requested it and refuses everyone else, so handing the URL to ffmpeg gets HTTP 403 or a stall that never finishes. yt-dlp's own download_ranges hits the same wall, because it shells out to ffmpeg too. Forcing the android client produces a URL ffmpeg can fetch, but that client only offers 360p, which is too coarse to read a menu label.

What does work is letting yt-dlp fetch the stream itself, since it holds the matching client session. That is cheap because the stream is video only, with no audio track requested. Measured on a 31 minute 720p tutorial: 32 MB in 5.7 seconds, then frames come off the local file in well under a second each. Both the file and its metadata are cached for the life of the server process (3 videos maximum), so a second call on an already-fetched video needs no network round trip at all - measured 0.1 seconds against 10.3 cold (an earlier version of this cache kept only the file and refetched metadata on every hit, which still worked but cost about 1.3s per warm call - fixed in the pre-publish audit).

ffmpeg is required for frames only. The other three tools do not need it. If it is missing, get_video_frames says so and tells you to run winget install Gyan.FFmpeg.

Live streams are rejected before any fetch is attempted. A currently-live or not-yet-started broadcast has no end, so "download the stream" never finishes - confirmed real 2026-08-08 against Lofi Girl's 24/7 stream: it ran for about 90 seconds before ffmpeg exited with a bare, unhelpful code 1. get_video_frames now checks is_live/live_status first and fails in about 1-2 seconds with a clear reason instead. A was_live video (the broadcast has ended and YouTube published the replay) works normally - only genuinely open-ended streams are rejected.

Playlist URLs work end to end, confirmed against a real 11-video playlist - listing, ordering, and channel/count metadata all correct. Their videos follow the same upload-date rule as any other listing (recent ones resolve free via RSS, older ones need resolve_all_dates=True) - there's nothing playlist-specific about it, an older video is an older video whether reached by channel or by playlist.

Region-blocked videos: the fallback path is proven, the specific message is not. Two real attempts to trigger an actual geo-block both missed: a video documented in yt-dlp's own issue tracker as geo-restricted is now blocked everywhere by a copyright claim instead, and BBC's YouTube channel turned out not to be region-locked the way BBC iPlayer is. What is proven is that the general failure path handles it safely - any YoutubeDLError yt-dlp raises comes back as a clean message, never a crash - and _friendly_error has a specific branch for YouTube's documented "not available in your country" phrasing, matched by text since yt-dlp has no dedicated exception type for this. That specific branch is unverified against a real occurrence.

Transient failures on both fetch paths are retried automatically. The large-stream download in get_video_frames (the other three tools are lightweight metadata calls, and 90 of those in a row - including a 10-worker concurrent burst - produced zero failures during testing). Confirmed real on 2026-08-08: a 4-hour video's stream fetch returned HTTP 403 once, then succeeded seconds later with nothing else changed - a signed download URL failing in a way only a fresh extraction clears, not something yt-dlp's own extractor_retries covers, since that only retries metadata calls. _local_stream now retries the whole extraction (not just the byte fetch) up to 3 times with backoff - but skips retrying entirely for failures no retry could fix (cookie lock, live stream, private/unavailable, region-block), so those still fail in one attempt, not three. get_video_transcript's caption-file fetch got the same retry logic in the pre-publish audit, on the same reasoning (caption URLs are signed and time-limited the same way stream URLs are) even though no failure was ever observed there in testing - proactive, not reactive.

Only youtube.com/youtu.be URLs are ever accepted. list_channel_videos hands whatever URL it's given to yt-dlp's extractor, which has a generic fallback capable of fetching arbitrary URLs, not just YouTube's. Every full-URL input is checked against the actual host before anything else happens with it, and a non-YouTube host raises ValueError immediately. This matters specifically because MCP tools can be called by an agent acting on content it read elsewhere - without this check, a crafted playlist URL pointing somewhere else entirely could have made this server issue an outbound request to an attacker-chosen destination. Added in the pre-publish audit; the video-ID path (get_video_transcript, get_video_frames) never had this exposure in the first place, since it only ever extracts an 11-character ID and always re-embeds it into a hardcoded youtube.com URL, discarding whatever host the input actually had.

Two smaller pre-publish audit fixes, both defensive rather than reactive to an observed failure. The temp directory get_video_frames downloads into is now swept for leftovers from a prior run's unclean exit (a forceful kill doesn't fire Python's atexit, so a long-lived install could otherwise accumulate one stray directory per crash). And the stream/metadata cache is now protected by a lock - the MCP stdio transport is normally one request at a time, but nothing in the protocol guarantees a client won't ever pipeline overlapping tool calls, and the cache's check-then-act sequence wasn't safe against that without one.

Keeping yt-dlp current. YouTube changes its internals regularly and yt-dlp keeps up, so if extractions start failing the first thing to try is an upgrade.

.\.venv\Scripts\python.exe -m pip install --upgrade yt-dlp    # Windows
./.venv/bin/python -m pip install --upgrade yt-dlp             # macOS/Linux

Verified

Tested end to end on 2026-08-08 against @mwganson and @MangoJellySolutions. Both listed correctly with real dates and durations. A full auto generated transcript came back from Tbiu_rMJolk, and search returned results. Frames were pulled from Xybk1EJfwHk (Reverse Engineering an STL Fan Impeller) at five transcript chosen moments in 5 seconds, and were legible enough to read the workbench selector, the model tree, property values and the status bar dimensions.

A real MCP stdio session completed a handshake, listed all four tools, returned live listing data, and returned mixed text plus image content from get_video_frames. All four of its error paths (bad timestamp, no timestamp given, timestamp past the end of the video, unavailable video) return proper MCP errors with actionable messages.

Hardening pass, 2026-08-08. Four rough edges from the first verification round, worked through against real content, not synthetic tests - see "Notes and limits" above for the full detail on each:

Area

Result

Playlist URLs

Confirmed working end to end against a real 11-video playlist

Bot-wall trigger

Not reproduced (90 real requests, 10-way concurrent) - the cookie fix was tested instead, and found broken while the named browser is running; fixed

Live streams

Real hang found (90s, unhelpful error) and fixed (rejected in ~1-2s)

Region-locked videos

Not reproduced (2 real attempts) - general failure handling proven safe regardless; the specific message is unverified

Multi-hour videos

Confirmed against a 23-hour transcript and a 4-hour frame fetch (including a frame at the 4:00:00 mark)

Retry/backoff

Added for the one path proven to need it (large-stream fetch, real transient 403 reproduced and fixed) - not added to the metadata/caption paths, which showed zero failures across 90 real requests

One bug found while fixing another: the live-stream check itself could crash unhandled in the same cookie-lock scenario, because it was a bare call with no except. Caught by testing the fix against the earlier finding, not by inspection - fixed in the same pass.

Available Tools

4 tools
get_video_framesA
Read-onlyIdempotent

See what a video actually shows at chosen moments. Returns real images.

Transcripts cannot capture a screen-based tutorial. "Click this, then drag it here" has no referent in text, toolbar clicks are usually silent, typed dialog values are rarely spoken, and auto-captions mangle exactly the technical terms you need. Use this to look at the moments that matter.

The intended workflow is two steps, and doing it in this order is what keeps it cheap:

  1. get_video_transcript(..., include_timestamps=True) to find WHICH moments matter, across as many videos as you like.

  2. get_video_frames(video_id, timestamps=[...]) on just those moments.

The video-only stream is fetched to a temp file first, then every frame comes off it locally. That sounds expensive and is not: video-only means no audio track, so a 31 minute 720p tutorial is about 32 MB and lands in under 10 seconds, and further calls on the same video are instant because the file is kept for the life of the server process (3 videos max, deleted on exit). Asking for many timestamps in ONE call is therefore much cheaper than many calls, and vastly cheaper than one call per frame on different videos.

Args: video_url_or_id: An 11-character video ID or any YouTube video URL. timestamps: The moments to capture, as 'S', 'M:SS' or 'H:MM:SS' strings (e.g. ["4:12", "11:38", "1:02:05"]). Take these from a timestamped transcript. every_seconds: Instead of explicit timestamps, sample evenly this many seconds apart. Use only when surveying an unfamiliar video; explicit timestamps are far cheaper. Ignored if timestamps is given. max_frames: Hard cap on frames returned (1-20, default 6). Every frame costs context, so keep this tight. width: Output width in pixels (320-1920, default 1280). Do not go below about 960 if you need to read menu labels or dialog values. max_height: Source stream height to fetch (default 720, which is enough to read a CAD toolbar and keeps the fetch small). Raise to 1080 only if 720 proves too coarse. quality: JPEG quality, 2 is best and 31 is worst (default 4).

Returns: A list whose first item is a text summary (video title, duration, and the timestamp of each frame in order), followed by one image per timestamp. Frames that could not be captured are reported in the summary text rather than failing the whole call.

Errors: Raises ValueError for bad arguments or unparseable timestamps, and RuntimeError if ffmpeg is missing or the video has no playable stream.

ParametersJSON Schema
NameRequiredDescriptionDefault
widthNo
qualityNo
max_framesNo
max_heightNo
timestampsNo
every_secondsNo
video_url_or_idYes

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the annotations (readOnly, idempotent, openWorld), the description reveals significant behavioral traits: the video-only stream is fetched to a temp file, cached for the server process lifetime (3 videos max, deleted on exit), and multiple timestamps in one call are much cheaper than separate calls. It also discloses failure handling (frames reported in summary) and specific error types.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every sentence earns its place. It is front-loaded with the core purpose, followed by workflow, cost/performance context, detailed parameter semantics, return format, and errors. The structured sections (Args, Returns, Errors) make it easy to scan, and no information is redundant with the annotations or schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool that returns images, the description fully explains the return format (summary text plus images) and how failures are surfaced. It also documents error conditions and performance tradeoffs, covering all the practical context an agent needs to invoke the tool correctly. The presence of an output schema would not add much beyond what is described.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Although the schema has 0% description coverage, the description's Args section thoroughly explains each parameter: video_url_or_id, timestamps format examples, every_seconds semantics, max_frames cap with cost warning, width guidance for readability, max_height tradeoffs, and quality range. This adds substantial meaning beyond the bare schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb+resource: 'See what a video actually shows at chosen moments. Returns real images.' It clearly distinguishes the tool from transcript-based alternatives by explicitly stating what transcripts cannot do and positioning this tool for visual verification.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit when-to-use guidance: use it to look at moments that matter in screen-based tutorials. It names the sibling alternative get_video_transcript and prescribes a two-step workflow with this tool as the second step, including cost-saving rationale. It also gives a conditional use case for every_seconds and warns against misuse.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_video_transcriptA
Read-onlyIdempotent

Fetch a video's transcript as clean plain text. No download, no API key.

Prefers a human-written caption track and falls back to YouTube's auto-generated one. Nothing is written to disk - the caption track is read straight into memory.

Args: video_url_or_id: An 11-character video ID, or any watch/youtu.be/shorts/ live/embed URL. language: Preferred caption language code (default 'en'). Regional variants match too ('en' will accept 'en-US'); if the language is missing entirely, the first available track is used. include_timestamps: True prefixes each paragraph with [H:MM:SS], which is what you want when you intend to cite a moment in the video. max_chars: Truncate the transcript at this many characters (0 = no limit). Set it when scanning many videos, since a long tutorial can run tens of thousands of characters.

Returns: { "video_id": str, "title": str | None, "channel": str | None, "url": str, "duration_seconds": int | None, "duration": str | None, "upload_date": str | None, # "2022-08-14" "transcript_kind": str, # "manual" or "automatic" "language": str, # track actually used, e.g. "en" "char_count": int, "truncated": bool, "transcript": str # blank-line separated ~30s paragraphs }

Errors: Raises ValueError for an unparseable video reference, and RuntimeError when the video is unavailable or has no caption track at all (some videos genuinely have none - listen for that message rather than retrying).

ParametersJSON Schema
NameRequiredDescriptionDefault
languageNoen
max_charsNo
video_url_or_idYes
include_timestampsNo

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Although annotations already mark the tool as read-only, idempotent, and non-destructive, the description adds significant behavioral detail: 'Nothing is written to disk - the caption track is read straight into memory,' preference for manual over auto-generated captions, and language fallback behavior. It also discloses error conditions (ValueError, RuntimeError), which goes well beyond the annotation hints.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with distinct sections for purpose, behavior, Args, Returns, and Errors. The main action is front-loaded in the first sentence, and every section provides necessary information. While it is relatively long, the length is justified by the lack of an output schema and the need to document four parameters and a complex return object.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is comprehensive: it details the full return object with field explanations, lists specific error types and when they occur, and gives practical usage tips such as setting max_chars when scanning many videos. This provides an agent with sufficient context to select and invoke the tool correctly, even without an output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has no descriptions (0% coverage), so the description carries full responsibility. The Args section explains each parameter in detail: accepted video ID/URL formats, language fallback (e.g., 'en' matches 'en-US', first available track if missing), include_timestamps formatting, and max_chars truncation usage. This fully compensates for the schema gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description starts with 'Fetch a video's transcript as clean plain text,' clearly stating a specific verb and resource. It also explains the fallback to auto-generated captions, which helps distinguish this transcript-focused tool from sibling tools that list videos, search, or extract frames.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no explicit comparison to sibling tools such as list_channel_videos, search_youtube, or get_video_frames, nor does it state when to use this tool instead of them. The usage context is implied by the tool's purpose, but there is no explicit 'use when' or 'use instead' guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_channel_videosA
Read-onlyIdempotent

List a YouTube channel's uploads, newest first, without an API key.

Use this to survey what a channel has published before deciding which videos are worth transcribing. Also accepts playlist URLs.

Args: channel_url: A handle ('@mwganson'), a bare name ('mwganson'), a channel ID ('UCxxxx...'), any youtube.com channel URL (with or without a /videos, /shorts or /streams tab), or a playlist URL. max_results: How many videos to return, newest first (1-1000, default 50). resolve_all_dates: False (default) fills upload dates for roughly the 15 most recent videos from the channel's RSS feed, which is one extra cheap request. True fetches every listed video's metadata to date the whole list - accurate but roughly one request per video, so only use it on small max_results.

Returns: { "channel": str | None, # display name "channel_id": str | None, # UC... id "channel_url": str, # URL actually listed "total_videos": int | None, # total on the channel/playlist, if known "count": int, # videos in this response "videos": [ { "video_id": str, # e.g. "Tbiu_rMJolk" "title": str, "url": str, # watch URL, feed straight to get_video_transcript "duration_seconds": int | None, "duration": str | None, # "44:19" "view_count": int | None, "upload_date": str | None # "2022-08-14", None if not resolved } ] }

Errors: Raises ValueError for an unparseable channel reference and RuntimeError with an actionable message if YouTube refuses the listing.

ParametersJSON Schema
NameRequiredDescriptionDefault
channel_urlYes
max_resultsNo
resolve_all_datesNo

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already convey read-only, idempotent, and non-destructive behavior, so the bar is lower. The description adds valuable context beyond annotations: the cheap RSS feed vs. full video metadata trade-off for resolve_all_dates, the fact it works without an API key, and error types (ValueError, RuntimeError). This is meaningful but does not cover rate limits or network behavior details.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is structured with Intro, Args, Returns, and Errors sections. Each section is dense but not redundant. Front-loaded with the core purpose and use case. The length is justified by the tool's complexity and the absence of an output schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description fully covers the tool's behavior, parameters, return value structure, and error conditions. Since there is no output schema, the detailed Returns block is essential and well done. Combined with the usage guidance, the agent has complete context to decide and invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has zero descriptions (coverage 0%), so the description must fully compensate. The Args section explains each parameter with detailed semantics: channel_url accepts handles, bare names, IDs, URLs, and playlist URLs; max_results range and default; resolve_all_dates behavioral trade-offs. This goes far beyond the bare schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool lists a YouTube channel's uploads, newest first, without an API key, and explicitly frames it as a survey step before deciding which videos to transcribe. This specific verb+resource+scope distinguishes it from siblings like get_video_transcript or search_youtube.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It provides a clear use case ('Use this to survey what a channel has published before deciding which videos are worth transcribing') and mentions playlist URL support, but it does not explicitly state when not to use this tool or name alternative tools for different tasks. No exclusions are given, so it earns a 4 rather than a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_youtubeA
Read-only

Search YouTube by keyword when you do not yet know the channel or video.

The entry point for research that starts from a topic rather than a URL: find candidate videos here, then feed their channel or URL to list_channel_videos or get_video_transcript.

Args: query: Free-text search, e.g. "FreeCAD sketcher constraints tutorial". max_results: How many results to return (1-100, default 20).

Returns: { "query": str, "count": int, "videos": [ {video_id, title, url, duration_seconds, duration, view_count, channel} ] } Search results carry no upload date; call get_video_transcript or list_channel_videos if you need one.

Errors: Raises ValueError on an empty query and RuntimeError if the search fails.

ParametersJSON Schema
NameRequiredDescriptionDefault
queryYes
max_resultsNo

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the read-only annotation, the description discloses important behavioral traits: the exact return structure, the fact that results carry no upload date, and error handling (ValueError on empty query, RuntimeError on search failure). This adds value beyond annotations and helps the agent anticipate edge cases.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with logical sections (purpose, usage, args, returns, errors). Every sentence adds value: the usage paragraph explains the workflow, the args are concise, and the return/error sections are compact. Nothing is redundant or wasteful.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description fully specifies the return object and its fields. It also covers error conditions, parameter ranges, and provides context for how the tool fits into a larger workflow. This is complete enough for an agent to select and invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description carries full responsibility for parameter meaning. It provides an example for query ('FreeCAD sketcher constraints tutorial') and precise semantics for max_results ('1-100, default 20'), exceeding the bare schema titles. This is essential and well-executed.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb+resource: 'Search YouTube by keyword when you do not yet know the channel or video.' It clearly frames the tool as the entry point for topic-based research, contrasting with URL-based tools like list_channel_videos and get_video_transcript. This distinguishes it from siblings and states exactly what it does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly states when to use this tool ('when you do not yet know the channel or video') and provides guidance on sequencing: 'find candidate videos here, then feed their channel or URL to list_channel_videos or get_video_transcript.' It also notes a limitation (no upload date) and directs users to alternatives for that missing data.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 4 tool updatesv1.0.0
    • First observedget_video_frames
    • First observedget_video_transcript
    • First observedlist_channel_videos
    • First observedsearch_youtube

TDQS

A4.8/5.0

Scored across 4 tools

Disambiguation5/5

Each tool addresses a distinct stage of a clear research workflow: finding videos (search_youtube), surveying a channel (list_channel_videos), extracting text (get_video_transcript), and capturing visual evidence (get_video_frames). There is no functional overlap; even list_channel_videos and search_youtube produce different result sets with different intents.

Naming Consistency5/5

All tool names follow the consistent verb_noun pattern: list_channel_videos, get_video_transcript, search_youtube, get_video_frames. Verbs are specific to the action, and objects clearly indicate the resource, making the set predictable and easy to navigate.

Tool Count5/5

Four tools is an ideal size for a focused research MCP. Each tool is essential to the workflow and there are no redundant or filler tools, making the set feel tight and purposeful.

Completeness5/5

The tool surface covers the complete research lifecycle from topic-based search to channel exploration to transcript and frame extraction. All returned data includes video metadata, and the workflow is explicitly documented within the descriptions, leaving no obvious dead ends for a research agent.

Maintenance

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    C
    quality
    B
    maintenance
    Enables AI agents to search, analyze, and extract insights from YouTube videos including transcripts, visual frames, and benchmarks without requiring API keys. Supports semantic search across playlists, sentiment analysis, and visual content indexing with automatic fallback chains for reliable access.
    41
    162 npm
    35
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    Enables AI tools to access YouTube content, including transcript extraction, video/channel info, and search.
    4
    10 npm
    MIT