Skip to main content
Glama

@mgcrea/mcp-youtube

A Model Context Protocol server that lets an agent upload videos to a YouTube channel and manage them: metadata, privacy and scheduling, thumbnails, playlists.

The server is read-only by default. Uploading and every other mutating tool are not merely refused when writes are off: they are never registered, so an agent cannot call them at all.

Features

  • Resumable, chunked uploads that survive dropped connections. The server asks YouTube how far it got and resumes from there, so a failure costs one chunk instead of the whole file. Large files upload in the background: youtube_upload_video waits up to wait_seconds, then hands back an upload_id for youtube_get_upload. MCP progress notifications are sent when the client asks for them.

  • Safe defaults for an agent. Uploads default to private (the API itself defaults to public). The made-for-kids declaration is required, with no default. Only video files can be uploaded, from absolute paths.

  • The channel is always visible. The Google consent screen picks the channel, so the token decides where an upload lands. Every response carries channel. YOUTUBE_CHANNEL_ID makes every tool refuse when the token belongs to a different channel.

  • Merge-safe updates. videos.update replaces every part it is sent, so youtube_update_video reads the video first and changes only the fields you pass.

  • Remedies, not just errors. Google's reason codes (quotaExceeded, uploadLimitExceeded, invalid_grant, ...) come back with what to do next.

Related MCP server: YouTube MCP

Setup

YouTube uploads need an OAuth client from a Google Cloud project you own. This takes about five minutes, once.

  1. Create a project.

  2. Enable the YouTube Data API v3.

  3. Configure the consent screen in Google Auth Platform:

    • Branding: an app name and your email.

    • Audience: External, then Publish app. While the app is in Testing, Google expires refresh tokens after 7 days. An unverified app in production is fine for your own account; you click past one warning at login.

  4. Go to Clients → Create client and choose application type Desktop app. You don't register a redirect URI, because Desktop clients accept any loopback port.

  5. Set YOUTUBE_CLIENT_ID and YOUTUBE_CLIENT_SECRET, and start the server.

  6. Ask the agent to call youtube_auth_login. It opens the consent screen. Pick the channel uploads should go to; Brand Account channels are listed separately.

The refresh token is stored with mode 600 in ~/.config/youtube/tokens.json. You don't need to restart afterwards.

Configure

Environment first, then ~/.config/youtube/config.json (camelCase keys, strict), field by field.

Variable

Required

Description

YOUTUBE_CLIENT_ID

yes

Desktop-app OAuth client id (….apps.googleusercontent.com).

YOUTUBE_CLIENT_SECRET

yes

Its secret. Google requires it even with PKCE.

YOUTUBE_ALLOW_WRITES

no

1 to register the upload and other write tools, and ask for the youtube scope.

YOUTUBE_CHANNEL_ID

no

Expected channel (UC…). Every tool refuses if the token acts as another.

YOUTUBE_REFRESH_TOKEN

no

Use this refresh token (memory only) instead of the token file.

YOUTUBE_TOKEN_FILE

no

Where the login is stored. Defaults to ~/.config/youtube/tokens.json.

YOUTUBE_CHUNK_SIZE_MB

no

Upload chunk size, default 16.

YOUTUBE_CONFIG

no

Config file path.

A read-only install asks for youtube.readonly only. If you turn writes on after logging in, youtube_auth_status reports the missing scope; log in again.

Quota

Quota is counted per Google Cloud project and resets at midnight Pacific time.

  • Uploads have their own bucket of 100 videos.insert calls per day (since 2026-06-01).

  • Everything else shares 10,000 units per day. A list or get costs 1 unit. An update, delete, thumbnail or playlist write costs 50.

YouTube also applies its own per-channel upload limit (uploadLimitExceeded), separate from API quota.

Tools

Tool

What it does

Writes

youtube_auth_status

Configured? Signed in? Which channel, scopes, quota model, setup steps

no

youtube_auth_login

Google consent in the browser, then stores the refresh token

no

youtube_auth_logout

Revokes the token at Google and deletes it locally

no

youtube_get_channel

Your channel, including whether videos over 15 minutes are allowed

no

youtube_list_videos

Your uploads, including private, unlisted and scheduled ones

no

youtube_get_video

One video in full, including processing progress

no

youtube_list_playlists

Your playlists

no

youtube_list_categories

Assignable categories for a region

no

youtube_request

Any Data API v3 endpoint (GET only unless writes are on)

gated

youtube_upload_video

Upload a local video, optionally with a thumbnail and playlist

yes

youtube_get_upload

Follow an upload: progress, then video id and processing state

no¹

youtube_update_video

Merge-update title, description, tags, privacy, schedule, disclosures

yes

youtube_set_thumbnail

Custom thumbnail (needs a verified channel)

yes

youtube_create_playlist

New playlist, private by default

yes

youtube_add_to_playlist

Add a video to a playlist

yes

youtube_delete_video

Permanent delete, requires confirm: true

yes

¹ Registered only with writes on, since without them no upload can exist.

Things worth knowing

  • YouTube has no Shorts flag. A vertical or square video up to 3 minutes becomes a Short automatically.

  • A channel without phone verification can't upload videos longer than 15 minutes or set custom thumbnails. Verify at https://www.youtube.com/verify.

  • Uploads live in the server process. If the server restarts mid-upload, the upload is gone; start it again. An incomplete upload never creates a video.

  • youtube_upload_video is not idempotent: every call creates a new video.

Quick start

pnpm install
pnpm build

Then copy .mcp.json.example to .mcp.json and fill it in.

Develop

pnpm dev            # tsdown --watch
pnpm test           # vitest
pnpm typecheck
pnpm lint
pnpm format

Publish

pnpm dlx release-it       # bump, commit, tag
git push --follow-tags    # CI publishes to npm + GHCR from the tag

License

MIT

Available Tools

9 tools
youtube_auth_loginYouTube: LoginA
Idempotent

Sign in with Google: opens the consent screen in the browser and waits for it to finish, then stores a refresh token (mode 600) and reports which channel it acts as. On the account chooser, pick the channel uploads should go to — a Brand Account channel is listed separately from the personal one. The app is unverified, so Google shows a warning first: Advanced → Go to (app). If no browser can be opened, or the wait runs out, returns the authorize URL instead and keeps listening for 5 minutes; call youtube_auth_status afterwards. Logging in again replaces the stored login.

Inputs: open, wait_seconds.

youtube_auth_logoutYouTube: LogoutA
DestructiveIdempotent

Revoke the stored refresh token at Google and delete it locally. Recoverable by calling youtube_auth_login again. Uploads already in progress fail at their next chunk.

Inputs: no named parameters.

youtube_auth_statusYouTube: Auth StatusA
Read-only

Whether the server is configured and signed in, which channel the token acts as (the token picks the channel, so this is where uploads land), the granted scopes, whether write tools are registered, and the daily quota model. When unconfigured, returns the exact setup steps. Call this first when a tool you expected is missing. Costs 1 quota unit the first time it resolves the channel, then nothing.

Inputs: no named parameters.

youtube_get_channelYouTube: Get ChannelA
Read-only

The channel the signed-in token acts as: id, handle, uploads playlist, subscriber/video/view counts, and long_uploads — whether videos longer than 15 minutes are allowed. Check that before uploading anything long: an unverified channel (disallowed or eligible) cannot, and the failure only shows after the whole file has been sent. Verify by phone at https://www.youtube.com/verify. Costs 1 quota unit.

Inputs: no named parameters.

youtube_get_videoYouTube: Get VideoA
Read-only

One video in full: snippet, status (privacy, uploadStatus, failure/rejection reason, publishAt, madeForKids), contentDetails, statistics, and — for your own videos — processingDetails (processing progress after an upload) and fileDetails. Use it to follow processing after youtube_upload_video finishes: processingStatus goes processing → succeeded. Costs 1 quota unit.

Inputs: video_id (required).

youtube_list_categoriesYouTube: List CategoriesA
Read-only

The video categories that can be assigned to an upload in a region, as {id, title}. Pass the id as categoryId to youtube_upload_video or youtube_update_video. Categories vary slightly by region, and some listed ones (e.g. Movies, Shows) are not assignable and are filtered out here. Costs 1 quota unit.

Inputs: region, language.

youtube_list_playlistsYouTube: List PlaylistsA
Read-only

This channel's playlists, including private ones: id, title, privacy and item count. The id is what youtube_add_to_playlist and youtube_upload_video's playlistId take. Costs 1 quota unit per page.

Inputs: limit, page_token.

youtube_list_videosYouTube: List VideosA
Read-only

This channel's uploads, newest first, including private, unlisted and scheduled ones: id, title, privacy, upload status, duration, views/likes/comments and URL. A video still processing shows upload_status uploaded; one that failed shows failure_reason or rejection_reason. A just-uploaded video can take a minute or more to appear here, because the uploads playlist is eventually consistent — youtube_get_video finds it at once. Costs 2 quota units per page (+1 the first time, to find the uploads playlist). For one video's full detail use youtube_get_video.

Inputs: limit, page_token.

youtube_requestYouTube: RequestA
Read-only

Escape hatch: call any YouTube Data API v3 endpoint (https://developers.google.com/youtube/v3/docs) that has no dedicated tool, e.g. GET /captions, GET /commentThreads, GET /search. The path is relative to /youtube/v3; parameters go in query. Returns the raw response, unshaped — prefer the dedicated tools. Media uploads (captions, banners) are not possible through it. Watch the cost: GET /search costs 100 units and has its own daily bucket. Writes are DISABLED: only GET is permitted. Set YOUTUBE_ALLOW_WRITES=1 to allow mutations.

Inputs: path (required), body, query, method.

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    A4.1/5.0

    Scored across 9 tools

    Disambiguation4/5

    The existing tools are cleanly separated: three auth tools, three read tools targeting distinct resources (channel, videos, playlists), a category lookup, and an explicit escape hatch. The only overlap is youtube_request, which can duplicate every other read tool, but it is clearly framed as a fallback that should be preferred less. The bigger confusion source is that several descriptions point at tools that do not exist (youtube_upload_video, youtube_update_video, youtube_add_to_playlist), sending an agent hunting for phantom tools.

    Naming Consistency4/5

    All names are snake_case with a consistent youtube_ prefix, and get_/list_ verb patterns are used predictably for reads. Minor deviations: the auth tools use a noun-namespace form (auth_login/status/logout) rather than verb_noun, and youtube_request is a bare noun. These are readable and conventional enough not to cause misselection.

    Tool Count4/5

    Nine tools is a well-scoped count for a YouTube server and nothing feels padded. The composition is slightly lopsided — three of nine tools are auth lifecycle — but each earns its place given the OAuth flow described. The number itself is appropriate; the issue is which tools are present rather than how many.

    Completeness2/5

    The surface is read-only in practice: no upload, update, delete, playlist mutation, or comment tools exist, yet descriptions extensively document upload behavior (long_uploads checks, categoryId passing, processing follow-up) for tools that are absent. Writes are even disabled in youtube_request unless an env var is set, so there is no workaround path. This will cause agent failures on the server's apparent primary use case.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers