OAuth-authenticated YouTube MCP server for channel owners — edit video metadata, reply to and moderate comments, manage playlists, query channel analytics, and set AI-generated thumbnails via a ComfyUI bridge.
Strong tool definitions with comprehensive descriptions and well-structured schemas. All 12 tools have clear verb-noun naming (query_, generate_, list_, upload_, delete_, reply_, moderate_, create_, add_, get_). Descriptions are detailed (100-400 chars), explaining WHAT, WHEN, and side effects. Input schemas use proper JSON Schema with types and descriptions for all parameters. However, output schemas are not formally documented, responses are described in prose (e.g., 'returns a plain-text table', 'returns confirmation lines') rather than as structured JSON schemas. Error handling guidance is minimal. Tool annotations (readOnlyHint, destructiveHint, idempotentHint) are absent from the code, though risk levels are documented in descriptions.
Appends a video to an existing playlist via the YouTube Data API. Write operation: it inserts a playlistItem (not idempotent — calling it twice adds the same video twice). Requires OAuth with the youtube.force-ssl scope and consumes write quota (~50 Data API units). Returns a confirmation line with the video and playlist IDs. Use create_playlist first (or an existing playlist ID) to get playlist_id, and list_my_videos/get_video to get video_id; both arguments are YouTube IDs, not URLs.
Creates a new (empty) playlist on the authenticated channel via the YouTube Data API. Write operation: it inserts a new playlist resource owned by the authenticated channel (not idempotent — each call creates a separate playlist, even with identical titles; there is no delete-playlist tool here, so cleanup must be done manually in YouTube Studio). Defaults to 'private' visibility. Requires OAuth with the youtube.force-ssl scope and consumes write quota (~50 Data API units). Returns a confirmation line with the new playlist's title and ID — capture that ID to pass to add_to_playlist. Use this to start a new playlist, then add_to_playlist to populate it.
DESTRUCTIVE and IRREVERSIBLE: permanently deletes a caption track by ID via the YouTube Data API (captions.delete). The track is removed from the video for viewers with no undo; re-adding requires uploading it again. Requires OAuth with the youtube.force-ssl scope and consumes write quota (~50 Data API units). Returns a confirmation line with the deleted track ID. Use list_captions first to find the correct caption_id; note caption_id is a caption track ID, not a video ID.
Output schemas not formally documented. Responses described in prose ('returns plain-text table', 'returns confirmation lines') rather than as structured JSON Schema. LLMs cannot reliably parse unstructured text responses or plan downstream tool calls without knowing exact field names and types.
Tool annotations (readOnlyHint, destructiveHint, idempotentHint) not present in code. Risk levels documented in descriptions but not machine-readable. Agents cannot automatically determine which tools are safe to retry or have irreversible consequences.
Error handling guidance minimal. Descriptions mention prerequisites (OAuth scopes, API quotas) but do not provide recovery paths. E.g., 'Requires OAuth with youtube.force-ssl scope' but no guidance on what to do if auth fails or quota is exceeded.
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 66 | <=2025-11-25 | v2 |
Generates a thumbnail image from a text prompt on the configured ComfyUI server, then uploads it as the video's custom thumbnail via the YouTube Data API. Two side effects: (1) it runs a text-to-image generation job on ComfyUI (this tool is only registered when a ComfyUI URL is configured; the call can take many seconds to minutes and may time out after ~10 minutes), and (2) it overwrites the video's current custom thumbnail — a write operation that replaces any existing thumbnail immediately and publicly (the prior thumbnail is not preserved). Requires OAuth with the youtube.upload scope and consumes YouTube Data API write quota (~50 units for thumbnails.set), plus the video must have custom-thumbnail privileges (verified channel). Defaults to 1280x720 (YouTube's recommended 16:9). Returns confirmation lines with the video ID, generated filename/dimensions, and the ComfyUI prompt_id. Use this to create-and-apply a thumbnail in one step; there is no separate upload-only thumbnail tool here.
Queries the YouTube Analytics API for the authenticated channel restricted to Shorts, by forcing filters=creatorContentType==SHORTS on top of the supplied start_date/end_date/metrics/dimensions. Read-only; no mutations. Requires OAuth with the yt-analytics.readonly scope and uses YouTube Analytics API quota (separate from the Data API). Returns the raw Analytics response as pretty-printed JSON (column headers plus rows). Use this for Shorts-only performance reporting; use query_channel_analytics for whole-channel or custom-filtered reports, and list_my_shorts to enumerate the Shorts themselves.
Lists the caption tracks on a video with their language, name, track kind, status, and draft flag. Read-only; no mutations. Requires OAuth and costs ~50 YouTube Data API units (captions.list). Returns a text list, one line per track: caption track ID, language, name, kind, status, and a [draft] marker, or a message that the video has no caption tracks. Use this to get a caption track ID before delete_caption, or to check existing tracks before upload_caption.
Lists top-level comment threads on a video, newest first. Read-only; no mutations. Requires OAuth and costs ~1 YouTube Data API read unit. Returns a text list, one line per thread: comment ID, author display name, like count, reply count, and a truncated (~160 char) preview of the comment text. Use the returned comment IDs as parent_id for reply_to_comment or comment_id for moderate_comment. Note: this returns only the top-level comment of each thread, not nested replies, and does not paginate beyond max_results.
Lists the authenticated channel's recent Shorts by scanning the most recent uploads (up to max_candidates) and keeping videos with a duration ≤ 60s. Read-only; no mutations. Requires OAuth and costs YouTube Data API read units proportional to how many uploads are scanned (it pages through uploads in batches of 50). Returns a text list, one line per Short: video ID, title, duration in seconds, and view count, or a message that no Shorts were found. This is a heuristic (the Data API exposes no direct Shorts filter), so very recent or edge-case Shorts may be missed; use list_my_videos for all videos and get_shorts_analytics for Shorts performance stats.
Sets the moderation status of a comment on the authenticated channel's videos via the Data API. Write/moderation operation that changes the comment's public visibility: 'heldForReview' hides it pending approval, 'published' approves/restores it, 'rejected' removes it from public view (effectively a soft delete — reversibility via the API is not guaranteed). Requires OAuth with the youtube.force-ssl scope and consumes write quota (~50 Data API units). Returns a confirmation line with the comment ID and new status. Use list_comments to find comment IDs; use this to approve/hide/reject viewer comments rather than to reply (use reply_to_comment for replies).
Queries the YouTube Analytics API (reports endpoint, ids=channel==MINE) for the authenticated channel over a date range. Read-only; no mutations. Requires OAuth with the yt-analytics.readonly scope; uses YouTube Analytics API quota (separate from the Data API). Returns a plain-text table: a header row of column names followed by one row per result, '|'-separated, scoped by the supplied metrics/dimensions/filters. Use this for views/watch-time/retention/subscriber/traffic-source reporting across the channel; pass filters='video==VIDEO_ID' to scope to one video, or use get_shorts_analytics to report on Shorts only.
Posts a public reply to an existing top-level YouTube comment via the Data API. Side effects: creates a publicly visible comment under the authenticated channel's identity (not idempotent — calling twice posts two replies and there is no built-in undo). Requires OAuth with the youtube.force-ssl scope and consumes write quota (~50 Data API units). Returns a confirmation line referencing the parent comment ID. Use list_comments first to get a valid parent_id; this tool replies within an existing thread only (it cannot start a new top-level comment, and parent_id must be a top-level comment, not another reply).
Uploads a new caption/subtitle track (SubRip SRT or WebVTT) to a video via the YouTube Data API. Write operation: it always inserts a new track (not idempotent — it does not replace an existing same-language track, so repeated calls create duplicates; use a distinct name per track or is_draft=true while iterating, and delete_caption to remove extras). Newly uploaded non-draft tracks become visible to viewers. Requires OAuth with the youtube.force-ssl scope and consumes heavy write quota (~400 YouTube Data API units). Returns confirmation lines with the new caption track ID, video ID, language, name, format, and status. Use list_captions first to check what already exists; use delete_caption to remove a track.
Pagination not fully specified. list_comments accepts max_results but description states 'does not paginate beyond max_results', no next_cursor or offset mechanism for retrieving additional results. list_my_shorts similarly lacks pagination support.
Idempotency not guaranteed. upload_caption, reply_to_comment, and create_playlist explicitly state they are NOT idempotent ('each call creates a separate playlist', 'calling twice posts two replies'). No idempotency keys or deduplication mechanism provided.