Skip to main content
Glama
README.md
# @mgcrea/mcp-youtube

A [Model Context Protocol](https://modelcontextprotocol.io) 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.

## Setup

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

1. [Create a project](https://console.cloud.google.com/projectcreate).
2. Enable the [YouTube Data API v3](https://console.cloud.google.com/apis/library/youtube.googleapis.com).
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

```bash
pnpm install
pnpm build
```

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

## Develop

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

### Publish

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

## License

MIT

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