youtube-mcp

youtube-mcp

An MCP server for authenticated YouTube writes, enabling video uploads, caption tracks, playlist management, and metadata edits with safety guards and async jobs.

Category
访问服务器

README

youtube-mcp

An MCP server for authenticated YouTube writes — uploading videos, attaching real caption tracks, managing playlists, editing metadata.

It is a thin, guarded bridge over a working yt_write.py CLI rather than a reimplementation of one, and most of what is interesting here is the guards. Each exists because the obvious implementation fails in a way that produces no error at all.

The failure this server exists to prevent

YouTube channels frequently live under Brand Accounts. At the OAuth consent screen, Google lists brand accounts as peers of your email address, not underneath it — so the row bearing your own name is your personal channel, which usually owns nothing.

Pick that row by mistake and everything still works. The token is valid. Every API call returns HTTP 200. Uploads succeed. They just land on the wrong channel, and you find out days later.

So assert_safe_channel runs before every write, not once at startup — a token can be replaced underneath a running process. It costs 1 quota unit to protect a 1,600-unit upload.

youtube_auth_status()
→ { ok: true, channels: [{ title: "…", allowed: true }], quota: {…} }

Call it first in any session that will write.

Uploads are asynchronous, and that is not a style choice

A 134 MB file takes minutes at 8 MB chunks; hour-long recordings run past 400 MB. A synchronous tool call reports a timeout while the transfer is still running — and the natural response, retrying, burns a second 1,600 quota units against a 10,000/day cap.

job = youtube_upload_video(file="/abs/path.mp4", title="…")
      → { job_id: "a1b2c3", status: "queued" }

youtube_upload_status(job_id="a1b2c3")
      → { status: "uploading", progress: 45 }
      → { status: "done", verified: true, url: "https://youtu.be/…" }

verified: true appears only after a read-back confirms the video exists and is owned by this token. If you are closing a task or a checklist row on the strength of an upload, gate it on verified, not on an id coming back from the insert call.

Jobs are in-memory and do not survive a restart. If you lose one, check the channel before re-uploading.

Quota is the real ceiling

Operation Units
videos.insert 1,600
captions.insert ~400
metadata write ~50
read / list 1

Against a default 10,000/day project quota, that is 4–5 uploads per day. A backfill of any size is a multi-day plan, not an afternoon.

The ledger resets at midnight US Pacific and refuses an upload before spending when the day is gone — a videos.insert that dies mid-transfer still costs the full 1,600, and so does the retry. It counts only what this server spent, so treat it as a floor: usage from the CLI or another client is invisible to it.

Enforced locally, not left to the API

  • privacy defaults to private. Never public by default. Schedule with publish_at instead of publishing straight out.
  • publish_at requires privacy=private. YouTube rejects it otherwise; caught locally so you get a sentence instead of an opaque 400.
  • Maximum 15 tags. The 16th is refused, never silently trimmed — a quietly dropped tag is worse than an error.

What it deliberately does not do

End screens and info cards. They are not in the YouTube Data API at all — Studio-only, permanently. Any tool claiming to set them would be lying, so there isn't one. youtube_upload_status returns a still_manual reminder instead.

Tools

Tool Notes
youtube_auth_status Call first. Bound channel, allowlist verdict, quota
youtube_quota_status Spent / remaining / uploads still affordable
youtube_upload_video Async; returns a job_id
youtube_upload_status Poll a job; verified is the gate
youtube_upload_captions .srt/.sbv/.vtt; replaces a same-named track
youtube_transcript_to_srt Speaker-timestamped .txt.srt
youtube_get_video Read metadata
youtube_update_video Partial edit; unpassed fields keep their values
youtube_playlist_list
youtube_playlist_create Idempotent by title
youtube_playlist_add Skips a video already present

Install

Requires Python 3.11+, uv, and a yt_write.py CLI exposing _service(), _resumable() and channel constants.

uv sync

Add to .mcp.json:

{
  "mcpServers": {
    "youtube": {
      "type": "stdio",
      "command": "uv",
      "args": ["run", "--directory", "/path/to/youtube-mcp", "python", "-m", "youtube_mcp"],
      "env": {
        "YT_WRITE_PATH": "/path/to/yt_write.py"
      }
    }
  }
}

Configuration

Variable Required Meaning
YT_WRITE_PATH yes Absolute path to the yt_write.py this server wraps
YT_WRITE_MIRROR_PATH no A second copy to sha-compare against (see below)
TXT2SRT_PATH no Defaults to txt2srt.py beside YT_WRITE_PATH
YOUTUBE_ALLOWED_CHANNELS no UCxxx=Label,UCyyy=Label; defaults to _KNOWN in the wrapped module
YOUTUBE_DENIED_CHANNELS no Same format; channels to hard-reject

YT_WRITE_PATH has no default on purpose. A default path for the one file this server bridges would be a silent fallback over an endpoint — the class of bug that hides a misconfiguration until it matters.

If your setup keeps two copies of yt_write.py at different paths, set YT_WRITE_MIRROR_PATH. They are separate inodes, so editing one and not the other is a live trap; youtube_auth_status sha-compares them and reports drift rather than picking one silently.

At least one allowed channel must resolve, or the server refuses to start — without an allowlist it cannot distinguish a correct upload target from a silently wrong one, which is the whole point.

Auth

Credentials live wherever the wrapped yt_write.py puts them (CONFIG_DIR), typically client_secret.json + token.json at chmod 600. Scopes: youtube, youtube.force-ssl, youtube.upload.

auth cannot run from a tool call. It opens a browser and blocks on a localhost callback, which would hang the MCP client with no way out. When the token is missing, tools return an actionable error naming the command a human should run:

python /path/to/yt_write.py auth

At the chooser, pick the Brand Account row — never the personal row bearing your own name.

Two related traps worth knowing: Brand Accounts are not members of a Workspace org, so an Internal OAuth app returns Error 403: org_internal — the app must be External. And it must be published to Production; Testing mode expires refresh tokens every 7 days.

Implementation note: stdout is the transport

MCP stdio servers speak JSON-RPC on stdout. The wrapped CLI prints constantly — progress lines, banners, reminders — so one stray print corrupts the stream and the client drops the connection. Every call into that module runs inside a redirect_stdout buffer, surfaced as cli_output where it is useful.

If you fork this to wrap a different CLI, that is the part to keep.

License

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选
mcp-server-qdrant

mcp-server-qdrant

这个仓库展示了如何为向量搜索引擎 Qdrant 创建一个 MCP (Managed Control Plane) 服务器的示例。

官方
精选
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选