youtube-music-mcp
An MCP server that enables AI applications to search YouTube music videos and manage playlists via the YouTube Data API v3, with OAuth support for playlist and subscription management.
README
YouTube Playlist Generator MCP Server
A Model Context Protocol (MCP) server that enables AI applications to search for YouTube music videos and manage playlists using the official YouTube Data API v3.
Features
- Search Music Videos: Find music videos on YouTube with customizable search parameters
- Get Video Details: Retrieve comprehensive information about specific videos
- Get Playlist Items: List videos from public playlists
- Playlist Management: Complete playlist management including creating, editing, and managing playlists (requires OAuth setup)
Setup
Prerequisites
- Node.js 18 or higher
- A Google Cloud Project with YouTube Data API v3 enabled
- YouTube Data API key
- OAuth 2.0 Client credentials (optional, required for playlist management features)
Installation
- Clone this repository:
git clone https://github.com/nazaryanenko/youtube-music-mcp
- Install dependencies:
npm install
-
Get a YouTube Data API key:
- Go to the Google Cloud Console
- Create a new project or select an existing one
- Enable the YouTube Data API v3
- Create credentials (API key)
- Copy the API key
-
Set up environment variables:
# Create a .env file
echo "YOUTUBE_API_KEY=your_api_key_here" > .env
echo "YOUTUBE_CLIENT_ID=your_client_id_here" >> .env
echo "YOUTUBE_CLIENT_SECRET=your_client_secret_here" >> .env
- Build the TypeScript code:
npm run build
Running the Server
For development with auto-rebuild:
npm run dev
For production:
npm start
Using with MCP Clients
This server uses the stdio transport, so it can be used with any MCP client that supports stdio.
Claude Desktop Configuration
Add to your Claude Desktop configuration file:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"youtube-playlist-generator": {
"command": "node",
"args": ["/absolute/path/to/yt-playlist-generator/build/index.js"],
"env": {
"YOUTUBE_API_KEY": "your_youtube_api_key_here",
"YOUTUBE_CLIENT_ID": "your_client_id_here",
"YOUTUBE_CLIENT_SECRET": "your_client_secret_here"
}
}
}
}
Visual Studio Code with GitHub Copilot Configuration
To use this MCP server with GitHub Copilot in Visual Studio Code, you need to configure it in your VS Code settings:
-
Open VS Code Settings: Press
Ctrl+,(Windows/Linux) orCmd+,(macOS) -
Search for MCP: Type "mcp" in the search bar
-
Add MCP Server Configuration: Add the following to your VS Code settings JSON:
{
"github.copilot.chat.mcp.servers": {
"youtube-playlist-generator": {
"command": "node",
"args": ["/absolute/path/to/yt-playlist-generator/build/index.js"],
"env": {
"YOUTUBE_API_KEY": "your_youtube_api_key_here",
"YOUTUBE_CLIENT_ID": "your_client_id_here",
"YOUTUBE_CLIENT_SECRET": "your_client_secret_here"
}
}
}
}
Alternative: Use Settings UI
- Go to File > Preferences > Settings (or use
Ctrl+,) - Search for "GitHub Copilot MCP"
- Click "Edit in settings.json" next to "Github › Copilot › Chat: Mcp Servers"
- Add the server configuration as shown above
Environment Variables Setup
For security, you can also set environment variables system-wide instead of in the config:
Windows (PowerShell):
[Environment]::SetEnvironmentVariable("YOUTUBE_API_KEY", "your_api_key_here", "User")
[Environment]::SetEnvironmentVariable("YOUTUBE_CLIENT_ID", "your_client_id_here", "User")
[Environment]::SetEnvironmentVariable("YOUTUBE_CLIENT_SECRET", "your_client_secret_here", "User")
Windows (Command Prompt):
setx YOUTUBE_API_KEY "your_api_key_here"
setx YOUTUBE_CLIENT_ID "your_client_id_here"
setx YOUTUBE_CLIENT_SECRET "your_client_secret_here"
macOS/Linux:
export YOUTUBE_API_KEY="your_api_key_here"
export YOUTUBE_CLIENT_ID="your_client_id_here"
export YOUTUBE_CLIENT_SECRET="your_client_secret_here"
# Add to ~/.bashrc, ~/.zshrc, or ~/.profile to persist
echo 'export YOUTUBE_API_KEY="your_api_key_here"' >> ~/.bashrc
echo 'export YOUTUBE_CLIENT_ID="your_client_id_here"' >> ~/.bashrc
echo 'export YOUTUBE_CLIENT_SECRET="your_client_secret_here"' >> ~/.bashrc
Then remove the env section from the VS Code configuration:
{
"github.copilot.chat.mcp.servers": {
"youtube-playlist-generator": {
"command": "node",
"args": ["/absolute/path/to/yt-playlist-generator/build/index.js"]
}
}
}
Usage in VS Code
Once configured, you can use the MCP server through GitHub Copilot Chat:
- Open Copilot Chat (
Ctrl+Alt+Ior click the chat icon) - Use natural language to interact with YouTube:
- "Search for music videos about jazz"
- "Create a new playlist called 'My Favorites'"
- "Add this video to my playlist"
- "Show me details about this YouTube video"
Available Tools
Read-Only Tools (API Key Authentication)
search_music_videos
Search YouTube for music videos with specified criteria.
Parameters:
query(string): Search query for music videosmaxResults(number, 1-50, default: 10): Maximum number of resultsorder(enum): Sort order - "relevance", "date", "rating", "viewCount", "title"
get_video_details
Get detailed information about a specific YouTube video.
Parameters:
videoId(string): YouTube video ID
get_playlist_items
Retrieve videos from a public YouTube playlist.
Parameters:
playlistId(string): YouTube playlist IDmaxResults(number, 1-50, default: 25): Maximum number of items
OAuth-Required Tools (Playlist Management)
authenticate_youtube
Authenticate with YouTube OAuth to enable playlist management features.
Parameters:
getAuthUrl(boolean, default: false): Set to true to get the authorization URLauthCode(string, optional): Authorization code from OAuth flow
get_auth_status
Check current OAuth authentication status.
Parameters: None
create_playlist
Create a new YouTube playlist (requires OAuth authentication).
Parameters:
title(string): Playlist titledescription(string, optional): Playlist descriptionprivacy(enum): "private", "public", or "unlisted"
edit_playlist
Edit existing playlist information (requires OAuth authentication).
Parameters:
playlistId(string): ID of the playlist to edittitle(string, optional): New title for the playlistdescription(string, optional): New description for the playlistprivacy(enum, optional): New privacy setting - "private", "public", or "unlisted"
Note: At least one field (title, description, or privacy) must be provided to update.
add_to_playlist
Add a video to an existing playlist (requires OAuth authentication).
Parameters:
playlistId(string): Target playlist IDvideoId(string): Video ID to add
list_playlists
List user's playlists (requires OAuth authentication).
Parameters:
maxResults(number, 1-50, default: 25): Maximum number of playlistsmine(boolean, default: true): List authenticated user's playlists
remove_from_playlist
Remove a video from a playlist (requires OAuth authentication).
Parameters:
playlistItemId(string): ID of the playlist item to remove (get from get_playlist_items)
Channel Subscription Tools (OAuth)
All subscription tools work under the existing https://www.googleapis.com/auth/youtube scope — no re-consent required. Every response includes a _quotaCost field; the running session total is also logged to stderr.
subscribe_to_channel
Subscribe the authenticated user to a channel. Idempotent: an already-subscribed channel returns { alreadySubscribed: true, subscriptionId, channelTitle } instead of erroring.
Parameters:
channel(string): channelId (UC...),@handle, channel URL, or channel name.
Quota: ~51 units on a new subscribe (50 insert + 1 duplicate lookup), 1–2 units when already subscribed.
unsubscribe_from_channel
Unsubscribe the authenticated user from a channel. If not subscribed, returns { success: false, reason: "not_subscribed" } rather than throwing.
Parameters:
subscriptionId(string, optional): preferred — skips the lookup.channelId(string, optional): used when subscriptionId is unknown.
At least one of the two must be supplied.
Quota: 50 units when subscriptionId is passed, ~51 units when only channelId is passed.
list_subscriptions
List the authenticated user's channel subscriptions. When called without pageToken, transparently auto-paginates up to 10 pages (500 subscriptions) and returns the merged list; pass an explicit pageToken to fetch a single page and drive pagination yourself.
Parameters:
maxResults(number, 1–50, default: 50): page size.pageToken(string, optional): if supplied, only one page is returned.order(enum):"alphabetical"(default) |"relevance"|"unread".
Quota: 1 unit per page.
get_channel_latest_videos
Get the latest uploads from a channel. Uses the uploads playlist instead of search.list — 1–2 units versus 100.
Parameters:
channel(string): channelId (UC...),@handle, or channel name.maxResults(number, 1–50, default: 10).publishedAfter(ISO 8601 string, optional): client-side filter on publish date.excludeShorts(boolean, default: false): filter out videos shorter than 60 seconds. Adds 1 quota unit per 50 fetched.
Quota: ~2 units baseline (channels.list + playlistItems.list), +1 per additional 50 items when excludeShorts is on.
Watch History Tools (Local Takeout Import)
The YouTube Data API stopped exposing watch history in August 2016 — the relatedPlaylists.watchHistory field was removed and the legacy HL playlist ID returns no items. Instead of pretending otherwise, this server imports data from a Google Takeout export into a local SQLite database and answers queries against that.
Getting a Takeout export
- Go to takeout.google.com.
- Deselect all, then select YouTube and YouTube Music.
- Click All YouTube data included → uncheck everything except history.
- Click Multiple formats → set history to JSON (default is HTML — the server refuses HTML with a clear error, since HTML parsing would be fragile).
- Export. When ready, download the archive and locate
Takeout/YouTube and YouTube Music/history/watch-history.json.
The DB file lives at ~/.yt-playlist-mcp-history.db by default (override with YOUTUBE_HISTORY_DB_PATH).
check_history_api_availability
Runtime probe to prove the Data API still lacks watch-history access. Runs three checks and returns their raw evidence — never hard-codes the verdict. If Google ever restores access, apiAvailable: true will bubble up automatically.
Parameters: None.
Returns { apiAvailable, checkedAt, evidence[], conclusion, alternative }. Evidence entries include step, request, statusCode, result.
import_watch_history
Streams the Takeout JSON file (safe for exports >100 MB) into SQLite. Deduplicates by (videoId, watchedAt), so re-importing is idempotent — running it again after a fresh Takeout adds new events without touching existing rows.
Parameters:
filePath(string): absolute or~-expandable path towatch-history.json.
Skips ad-driven watches (details[].name starting with "From Google Ads") and marks deleted/removed videos separately. Returns:
{
"success": true,
"imported": 12345,
"adsExcluded": 87,
"deletedVideos": 42,
"skippedDuplicates": 0,
"dateRange": { "from": "…", "to": "…" },
"dbPath": "…",
"filePath": "…"
}
On a repeat import of the same or an overlapping file, skippedDuplicates reports how many events were already in the DB (deduplicated by (videoId, watchedAt)).
Errors — distinct codes so the caller can give the right advice:
error |
Meaning |
|---|---|
file_not_found |
Path echoed back as filePath so you can see how ~ expanded. |
html_not_json |
Takeout gave HTML instead of JSON — re-export with format = JSON. |
unexpected_extension |
Not .html, not .json — probably wrong file. |
parse_error |
Broken JSON. Includes bytePosition when stream-json can locate the failure. |
db_locked |
SQLite reports SQLITE_BUSY/SQLITE_LOCKED — another process holds the DB. |
query_watch_history
Aggregate queries over the imported history.
Parameters:
groupBy(enum):"channel"(default) |"month"|"hour_of_day"|"weekday".channel(string, optional): exact-match channel filter.from,to(ISO 8601 strings, optional): inclusive/exclusive date bounds.limit(number, 1–1000, default 30).timezone(string, IANA, default"Europe/Kyiv"): governshour_of_day/weekdaybucketing; date arithmetic honours DST.
Rows marked deleted are excluded from aggregates. Returns { groupBy, timezone, totalEventsMatched, filters, groups[] }.
watch_history_status
Fast probe: does the DB exist, how many events, source split (youtube vs youtube_music), date range, per-file import log. Use this before deciding whether to re-run import_watch_history.
Parameters: None.
Watch-history analytics (pre-aggregated for LLM context)
All five analytics tools return a compact envelope — ≤ 50 KB per response — with heavy trimming when needed. Raw events are only returned by sample_watch_events. Every tool accepts the shared filter set:
from/to(ISO 8601): date-range bounds.source:"youtube"|"youtube_music"|"all"(default).timezone(IANA, default"Europe/Kyiv"): governs hour-of-day, weekday, month buckets.
Every response is wrapped as:
{
"meta": {
"dbEvents": 12345,
"dateRange": { "from": "2020-01-...", "to": "2026-07-..." },
"filtered": 8901,
"timezone": "Europe/Kyiv",
"truncated": false
},
"data": { /* tool-specific */ }
}
get_watch_overview
One-call snapshot. Returns totals, source split, deleted / ad-excluded counts, month-by-month volume, hour-of-day distribution split into weekday/weekend, and the peak day. Start any exploration here.
get_channel_stats
Per-channel breakdown, grouped by stable channelId — a channel that renamed still lands in one row.
Fields per row: channelId (UC…, null for old Takeouts without a /channel/ URL), keyType ("id" when grouped by UC, "name" fallback for URL-less rows), channel (freshest display name — pulled from the row with MAX(watchedAt)), aliases[] (older display names for the same channelId, if any), channelUrl, watches, uniqueVideos, firstWatch, lastWatch, last90d, share.
sortBy: "watches" (default) surfaces long-term favorites; sortBy: "recent" surfaces channels with the newest activity (fresh obsessions). The client derives "dead interests" (watches ≥ 20 with old lastWatch) locally rather than asking for a separate endpoint.
Migration note: the underlying watch_events.channelId column is added and backfilled from channelUrl the first time the server opens an older DB (log line [history migration] backfilled channelId for N/M rows). Rows whose Takeout URL was an @handle (no UC) stay NULL and fall back to name-based grouping.
get_sessions_analysis
Sessions are contiguous runs of watches separated by gaps under gapMinutes (default 30). Returns:
- session medians (length in minutes, videos per session)
rabbitHoles: top same-channel streaks of≥ minStreakconsecutive videos within one sessionchannelCooccurrence: top 20 channel pairs that show up in the same session, withsessionsTogether ≥ 3— raw material for topic clustering, which the client performs itself
All aggregation runs inside SQLite via LAG() window functions; the events table is never materialized in JS memory.
get_taste_divergence
Compares watching against a live pull of videos.list?myRating=like (up to 500 videos, cached in-process for 1 hour). Matching is by channelId for both directions — a rename doesn't break the join. Rows without a channelId (old Takeouts) fall back to name matching and are flagged with keyType: "name-fallback" (weaker match — draw softer conclusions from those).
Returns:
quietDominants: heavily watched channels you have never liked (habit-consumption signal) — withchannelId,keyType,channel,watches,likes: 0.likedButAbandoned: channels with likes but no watch activity in the last ~6 months (drifting-away signal) — withchannelId,keyType,channel,likes,lastWatch.likesCachedAt: ISO timestamp of the current in-process cache — subtract from wall time for age.
Requires OAuth. Costs 1 quota unit per 50 liked videos fetched, once per hour.
sample_watch_events
The only tool that returns raw event rows (title, channel, watchedAt, videoId), newest-first. Optional exact-match channel filter. Hard limit: 100 rows — asking for more returns error: "limit_too_high" rather than a silent truncation.
Quota Tracking
Every subscription tool returns _quotaCost — the estimated cost of that single call — and the server logs a running session total to stderr after each API call, e.g.:
[quota] +50 for subscriptions.insert (session total: 61 units)
The default YouTube Data API daily quota is 10 000 units. Structured errors are returned for the following conditions:
| Condition | error value |
Notes |
|---|---|---|
| Daily quota exhausted | quota_exceeded |
Resets at midnight Pacific Time. |
| Channel disabled subscriptions | subscription_forbidden |
|
| Already subscribed (on insert) | Handled transparently as alreadySubscribed: true. |
|
| OAuth token expired/invalid | auth_expired |
Re-run authenticate_youtube. |
| Channel deleted / not found | not_found |
|
| Not subscribed (on delete) | success: false, reason: "not_subscribed" |
Not an MCP error. |
OAuth Authentication Setup
For full playlist management capabilities, you need to set up OAuth 2.0:
1. Google Cloud Console Setup
- Go to Google Cloud Console
- Create a new project or select existing one
- Enable the YouTube Data API v3
- Go to "Credentials" → "Create Credentials" → "OAuth 2.0 Client ID"
- Choose "Desktop Application" as application type
- Download the credentials or copy the Client ID and Client Secret
2. Environment Configuration
Add your OAuth credentials to the .env file:
YOUTUBE_API_KEY=your_api_key_here
YOUTUBE_CLIENT_ID=your_client_id_here
YOUTUBE_CLIENT_SECRET=your_client_secret_here
3. Authentication Flow
- Use the
authenticate_youtubetool withgetAuthUrl: true - Open the provided URL in your browser
- Sign in to your Google account and grant permissions
- Copy the authorization code
- Use
authenticate_youtubetool again with theauthCodeparameter
4. Using Authenticated Features
Once authenticated, you can:
- Create new playlists
- Edit existing playlist information (title, description, privacy)
- Add videos to your playlists
- List your private playlists
- Remove videos from playlists
API Limitations
- Quota Limits: YouTube API has daily quota limits (10,000 units/day by default)
- Rate Limiting: API calls are subject to rate limiting
- OAuth Tokens: Access tokens expire and need refresh (handled automatically)
- Permissions: OAuth scope determines available operations
Development
Building
npm run build
stdio transport: never write to stdout
The server uses the MCP stdio transport — stdout is reserved for JSON-RPC frames. A single console.log, process.stdout.write, or a logger that defaults to stdout (pino/winston/etc. without explicit stderr config) will corrupt the message framing and the client will silently drop responses, making every tool appear to hang.
When adding new tools:
- Log to stderr only:
console.error(...). - If you must use a logger, pin its destination stream to
process.stderr. - Verify with:
node build/index.js 2>/dev/null | head -c 200— at idle this MUST print nothing.
Development Mode
npm run dev
Project Structure
src/
└── index.ts # Main server implementation
build/ # Compiled JavaScript output
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Test thoroughly
- Submit a pull request
License
MIT License - see LICENSE file for details
Troubleshooting
Common Issues
-
"YOUTUBE_API_KEY environment variable is required"
- Make sure you've set the YOUTUBE_API_KEY environment variable
- Verify the API key is valid and has YouTube Data API access
-
"Quota exceeded" errors
- You've hit the daily API quota limit
- Wait for quota reset or request quota increase
-
"Playlist not found" errors
- Ensure the playlist ID is correct
- Verify the playlist is public (private playlists require OAuth)
-
Build errors
- Make sure you have Node.js 18+ installed
- Run
npm installto ensure all dependencies are installed - Check TypeScript compilation with
npm run build
Security Considerations
- Never commit API keys to version control
- Use environment variables for sensitive configuration
- Validate all user inputs
- Implement proper error handling and logging
This project was created using GitHub Copilot
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。