Skip to main content

YouTube MCP Connector

An MCP server (streamable HTTP) connecting any MCP-compatible client to the YouTube Data API v3: search videos, read subscriptions and playlists, build a "catch me up" digest of recent uploads, and manage playlists — with every write gated behind an explicit approval step.

Works with

This is a standard Model Context Protocol server — nothing in it is tied to any single assistant. Run it yourself (below) and point any MCP-compatible client at http://127.0.0.1:8000/mcp:

  • Meta Muse, Claude / Claude Code (Anthropic), ChatGPT (OpenAI)
  • Cursor, Windsurf, Cline, and other MCP-capable coding assistants
  • Any custom agent built on an MCP SDK (Python, TypeScript, …)

How it works

Any MCP client connects to this server's streamable-HTTP endpoint (/mcp). Credentials are never hard-coded: the server reads YOUTUBE_API_KEY / YOUTUBE_OAUTH_TOKEN from the environment (your local .env file).

Auth split (mirrors the YouTube API itself):

Tool kind Credential Why
Public reads (search_videos, get_video_details) API key Public data needs no user identity
Private reads (my_subscriptions, my_playlists, catch_me_up) OAuth 2.0 user token mine=true calls are per-user
All writes OAuth 2.0 user token Every mutation requires authorization

Approval gating: write tools (create_playlist, add_to_playlist, save_for_later, remove_from_playlist) never execute directly. They return a pending_confirmation payload with a single-use, expiring token (default 600 s). The agent surfaces the action description to the user; on approval it calls confirm_action with the token, which executes exactly once. cancel_action discards a pending action. This maps 1:1 onto the approval-card UX in MCP clients.

Known API limitation: YouTube's Data API cannot read or modify the native Watch Later playlist (support removed August 2016; the API returns watchLaterNotAccessible). save_for_later therefore uses a user-owned playlist named "Watch Later (via 1xAI)" as the supported replacement, creating it on first use.

Setup

You need a Google Cloud project. Do not create anything from this repo — these are manual steps in your own Google account:

  1. Go to Google Cloud Console and create (or pick) a project.

  2. APIs & Services → Library → search "YouTube Data API v3"Enable.

  3. APIs & Services → Credentials → Create Credentials → API key — for public reads. (Optional: restrict the key to the YouTube Data API v3.)

  4. OAuth consent screen → user type External → fill app name, support email, developer contact → add scopes:

    • https://www.googleapis.com/auth/youtube.readonly
    • https://www.googleapis.com/auth/youtube.force-ssl

    Tip: publish the consent screen to Production. Apps left in Testing issue refresh tokens that expire after 7 days.

  5. Create Credentials → OAuth client ID → type Desktop app (personal use) → run Google's OAuth flow once and capture an access token.

  6. Copy .env.example to .env and fill in the values:

    • YOUTUBE_API_KEY — public reads
    • YOUTUBE_OAUTH_TOKEN — private reads + writes (refresh when it expires)
    • YOUTUBE_HOST / YOUTUBE_PORT — bind address (default 127.0.0.1:8000)
    • YOUTUBE_APPROVAL_TTL_SECONDS — approval window (default 600)
    • YOUTUBE_HTTP_PROXY / YOUTUBE_HTTPS_PROXY — only if your host needs an explicit egress proxy (ambient proxy env vars are intentionally ignored)
    • YOUTUBE_CA_BUNDLE — path to a PEM CA bundle, only if your egress proxy re-signs TLS with a private CA (it extends the default trust store)

No billing account is needed: the API is free within the daily quota below.

Quota budget

Default project quota: 10,000 units/day, resetting at midnight Pacific. Costs are per call (each result page costs the full amount again).

Tool API call(s) Units
search_videos search.list 100
my_subscriptions subscriptions.list 1 / page
my_playlists playlists.list 1 / page
get_video_details videos.list (≤50 IDs batched) 1
catch_me_up subscriptions.list + channels.list batch + playlistItems.list per channel ≈ 2 + #channels
create_playlist (on confirm) playlists.insert 50
add_to_playlist (on confirm) playlistItems.insert 50
remove_from_playlist (on confirm) playlistItems.delete 50
save_for_later (on confirm) playlistItems.insert (+ playlists.insert first time) 50 (100 first time)

Example daily budget: 5 searches (500) + 3 digests over 40 subscriptions (~126) + 20 detail lookups (20) + 10 playlist writes (500) ≈ 1,150 units — about 11% of the free quota. Design rule used throughout: never call search.list when videos.list/playlistItems.list can answer the question.

On quotaExceeded (HTTP 403) every tool returns a structured quota_exceeded error telling the user when the quota resets.

Run the server

From PyPI:

pip install youtube-mcp-dj
youtube-mcp   # console entry point; configure via env vars (see .env.example)

Local Python:

python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
cp .env.example .env   # then fill in your keys
.venv/bin/python -m youtube_mcp.server
# Point your MCP client at http://127.0.0.1:8000/mcp

Or Docker (the image bakes in a /healthz liveness probe):

cp .env.example .env   # then fill in your keys
docker build -t youtube-mcp .
docker run --env-file .env -p 8000:8000 youtube-mcp

Connect a client

Point any MCP-compatible client at http://127.0.0.1:8000/mcp (streamable HTTP). Claude Code, Cursor, Windsurf, Cline, and ChatGPT's developer mode all accept a remote MCP server URL in their MCP/integration settings — paste the URL there. See the MCP documentation for your client's exact config shape.

Run the tests

.venv/bin/python -m pytest -q   # unit tests (mocked HTTP): 71 tests

# Integration tests hit the real API (reads only, ~110 units).
# Skipped automatically when credentials are absent.
YOUTUBE_API_KEY=... YOUTUBE_OAUTH_TOKEN=... .venv/bin/python -m pytest -q -m integration

Integration tests are skipped automatically when credentials are absent. Write-path tests run against mocks only — real writes are exercised manually through the propose → approve → confirm_action flow.

Troubleshooting

Learned the hard way while building this:

  • channelNotFound on my_subscriptions / my_playlists / catch_me_up: the Google account that granted OAuth has no YouTube channel. Create one at youtube.com (any name works), then re-run the OAuth flow.
  • quotaExceeded (HTTP 403) immediately: run OAuth against your own Cloud project. Shared/demo projects (e.g. Google's OAuth 2.0 Playground project) can already have their quota exhausted, and there is nothing you can do about it. Your own project gets a fresh 10,000 units/day.
  • Auth errors right after pasting a token: make sure you pasted the full access token — a truncated paste fails every call. You can sanity-check a token's scopes at https://oauth2.googleapis.com/tokeninfo?access_token=TOKEN.
  • Refresh token stops working after ~7 days: your OAuth consent screen is still in Testing mode. Publish it to Production (step 4 of Setup).
  • Behind a corporate egress proxy: the server ignores ambient HTTP_PROXY / HTTPS_PROXY on purpose (they break inside containers). Set YOUTUBE_HTTP_PROXY / YOUTUBE_HTTPS_PROXY explicitly; if the proxy re-signs TLS with a private CA, point YOUTUBE_CA_BUNDLE at your PEM bundle.

Project layout

src/youtube_mcp/
  config.py     # env-var settings (no secrets in code)
  errors.py     # typed errors: auth, quota, not-found, API
  client.py     # httpx client: key/OAuth injection, error mapping, pagination
  quota.py      # quota cost table + daily budget
  approvals.py  # single-use expiring tokens for write approvals
  tools.py      # the 11 tool implementations (pure, fully tested)
  server.py     # FastMCP wiring → streamable HTTP
tests/
  test_client.py       # auth headers, error mapping, pagination
  test_approvals.py    # TTL, single-use, cancel
  test_tools.py        # every tool: happy path, errors, auth/quota, empty, paging
  test_integration.py  # real API, reads only, skipped without credentials

Example prompts

  1. "Catch me up on my subscriptions from this week — what did I miss?"
  2. "Find me a video under 20 minutes that explains how sourdough starter works."
  3. "Save this video for later: https://www.youtube.com/watch?v=…"
  4. "What are the three most-viewed uploads from Marques Brownlee this month, and how long is each?"
  5. "Make a private playlist called 'Weekend cooking' and add the pasta video you found yesterday."

License

MIT — see LICENSE.

Release files for youtube-mcp-dj 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for youtube-mcp-dj 0.1.1
File Size Uploaded
youtube_mcp_dj-0.1.1.tar.gz 26.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for youtube-mcp-dj 0.1.1
File Interpreter ABI Platform
youtube_mcp_dj-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 46.5 kB

Release files / youtube_mcp_dj-0.1.1.tar.gz

Download URL youtube_mcp_dj-0.1.1.tar.gz
Size 26.1 kB
Tags Source
SHA-256 checksum
How to use checksums
09616498fad2467fb0a726667f223b7f07151be313913094f28dfba0ecb80772
BLAKE2b-256 checksum
How to use checksums
6d28a7de0916d7f69650062ca4e02cd1e74c504b19a1a10b67b2ad2bedf65060
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release files / youtube_mcp_dj-0.1.1-py3-none-any.whl

Download URL youtube_mcp_dj-0.1.1-py3-none-any.whl
Size 20.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
66d4285d2c8bd321070cf131f2672262f94d0de8535d61cbc252420736669597
BLAKE2b-256 checksum
How to use checksums
41228ed4f37a6e2e6c70de85251c9b2f5581ef202d31fb9a74c210f845caf3c1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page