yt-curator-mcp
# yt-curator π¬π§Ή
**YouTube playlist curation engine** β inventory, deduplicate, merge, clean up, and reorganise thousands of playlists spanning 20 years.
```
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β yt-curator β
β β
β βββββββββββ βββββββββββ ββββββββββββββββββββββ β
β β CLI β β MCP β β Python Library β β
β β (Click)β β (FastMCP)β β (import yt_curator)β β
β ββββββ¬βββββ ββββββ¬βββββ βββββββββββ¬βββββββββββ β
β β β β β
β ββββββββ¬ββββββββββββββββββββββββ β
β β β
β ββββββββββΌβββββββββ β
β β YouTube Data β β
β β API v3 + OAuthβ β
β β + Local SQLite β β
β βββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
```
## Features
- **Full inventory** β scan all your playlists and every video in them into a local SQLite database
- **Dead video detection** β find deleted, private, and blocked videos across all playlists
- **Cross-playlist deduplication** β find every video that appears in 2+ playlists
- **Intra-playlist dedup** β remove duplicate copies within a single playlist
- **Merge playlists** β move all videos from one playlist into another, then delete the source
- **Bulk delete** β remove empty or unwanted playlists
- **Merge suggestions** β AI-free title-similarity analysis to find consolidation candidates
- **Privacy audit** β see which playlists are public vs private
- **Dry-run everything** β all destructive operations preview before executing
## Three Interfaces
### 1. CLI (`yt-curator`)
```bash
# OAuth setup (one-time)
yt-curator auth
# Full inventory scan
yt-curator inventory
# Reports
yt-curator report
yt-curator find-dupes
yt-curator find-dead
# Curation (try --dry-run first!)
yt-curator merge PL_source_id PL_target_id --dry-run
yt-curator dedup PL_playlist_id --dry-run
yt-curator delete PL_playlist_id --dry-run
# Start MCP server
yt-curator serve-mcp
```
### 2. MCP Server (`yt-curator-mcp`)
Register as a backend for any MCP client (Claude Desktop, Hermes, Cursor, etc.).
**stdio mode** (default β plug-and-play with Claude Desktop):
```json
{
"mcpServers": {
"yt-curator": {
"command": "yt-curator",
"args": ["serve-mcp"]
}
}
}
```
**SSE mode** (register with the mcp-gateway):
```bash
yt-curator serve-mcp --host 0.0.0.0 --port 39401
```
Then add to your gateway config:
```yaml
backends:
- name: yt-curator
type: sse
url: http://127.0.0.1:39401
```
**Available MCP tools:**
| Tool | Description |
| :-- | :-- |
| `list_playlists()` | List all playlists in the inventory DB |
| `get_playlist_contents(id)` | List all videos in a specific playlist with status |
| `scan_inventory()` | **Full scan** β all playlists, items, and video status |
| `curation_report()` | Summary stats from inventory |
| `find_duplicates(min=2)` | Cross-playlist duplicate detection |
| `find_dead_videos()` | Dead/private/blocked videos |
| `find_empty_playlists()` | Zero-video playlists |
| `suggest_merges()` | Title-similarity merge candidates |
| `merge_playlists(src, dst, dry_run=True)` | Merge with dry-run mode |
| `delete_playlist(id, dry_run=True)` | Delete with dry-run mode |
| `remove_dead_videos(dry_run=True)` | Bulk dead video removal |
| `auth_status()` | Check OAuth credentials |
### 3. Python Library (`import yt_curator`)
```python
from yt_curator.core.client import YouTubeClient
from yt_curator.core.inventory import scan_all
client = YouTubeClient()
report = scan_all(client)
print(f"Scanned {report.total_playlists} playlists")
```
## Quota Budget
YouTube Data API v3 default: **10,000 units/day** (free).
| Operation | Cost per call | Your 900-playlist scan |
| :--------- | :-----------: | :--------------------: |
| `playlists.list` | 1 | ~18 calls |
| `playlistItems.list` | 1 | ~900 calls |
| `videos.list` (status check) | 1 | ~120 calls |
| `playlists.insert` / `.update` | 50 | per operation |
| `playlistItems.insert` / `.delete` | 50 | per operation |
| **Full inventory scan** | | **~1,200 units** β
|
A full scan uses ~12% of your daily quota, leaving 8,800 units for curation
writes (about 175 write operations per day).
## Setup
### One-time: Google Cloud + OAuth
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Create a project β Enable **YouTube Data API v3**
3. **APIs & Services β Credentials β Create Credentials β OAuth client ID**
- Application type: **Desktop app**
- Download `client_secret.json`
4. Save it to `~/.config/yt-curator/client_secret.json`
5. Run `yt-curator auth` β opens a browser for Google login
> **Tip for headless servers**: Run `yt-curator auth` once on a desktop machine
> with a browser, then copy `~/.config/yt-curator/token.json` to the server.
## NixOS Module
Add yt-curator to your flake inputs:
```nix
{
inputs.yt-curator = {
url = "github:telos-systems/yt-curator";
inputs.nixpkgs.follows = "nixpkgs";
};
}
```
Then enable the module:
```nix
{
imports = [ yt-curator.nixosModules.default ];
services.yt-curator = {
enable = true;
mcpServer.enable = true;
mcpServer.port = 39401;
credentials.clientSecretPath = config.sops.secrets."yt-curator/client_secret".path;
};
}
```
Or use the flake directly:
```bash
nix run github:telos-systems/yt-curator -- inventory
nix run github:telos-systems/yt-curator#mcp -- serve-mcp
```
## Project Structure
```
yt-curator/
βββ src/yt_curator/
β βββ __init__.py # Package entry
β βββ cli/app.py # Click CLI (11 commands)
β βββ mcp/server.py # FastMCP server (12 tools)
β βββ core/
β β βββ auth.py # OAuth 2.0 + token storage
β β βββ client.py # YouTube API client wrapper
β β βββ inventory.py # Full scan β SQLite
β β βββ curator.py # Merge, delete, dedup operations
β β βββ dedup.py # Cross-playlist dedup + suggestions
β β βββ __init__.py # Data models (dataclasses)
β βββ db/schema.py # SQLite schema
βββ nix/module.nix # NixOS module
βββ flake.nix # Nix flake
βββ pyproject.toml # Python project metadata
βββ LICENSE # MIT
βββ README.md
```
## Why This Exists
YouTube's web UI has no bulk operations. If you have 900+ playlists accumulated
over 20 years, there's no way to:
- Find which videos are dead across all playlists
- See which videos appear in 10 different playlists
- Merge similar playlists
- Delete 50 empty playlists in one go
Existing MCP servers for YouTube are read-only analytics tools or basic CRUD
wrappers. None do inventory, dedup, merge, or bulk curation. This fills that gap.
## Roadmap
- [x] Core: inventory, dedup, merge, delete, dead video removal
- [x] CLI: 11 commands with --dry-run
- [x] MCP server: 12 tools (stdio + SSE)
- [x] Nix flake + NixOS module
- [ ] AI-assisted reorganisation (local LLM via Ollama)
- [ ] Playlist-as-code (declarative YAML β desired state)
- [ ] GitHub release + PyPI publish
- [ ] Scheduled inventory drift detection (weekly cron)
## License
MIT Β© 2026 Telos Systems / Danny Poulson
TDQS
Scored across 12 tools
Most tools target distinct resources/actions, but list_playlists and scan_inventory overlap slightly since both return playlist lists; the descriptions clarify that scan_inventory is the primary data-collection command. Overall, boundaries are clear.
The set mostly follows a verb_noun pattern (list_*, get_*, find_*, merge_*, delete_*, remove_*). Exceptions like 'curation_report' and 'auth_status' are minor and still readable, so consistency is good but not perfect.
With 12 tools, the server is well-scoped for a playlist curation domain. Each tool earns its place, covering inventory scanning, analysis, and actionable curation without unnecessary bloat.
The surface covers the full curation workflow: scan, analyze, merge, delete, and remove dead videos. Minor gaps exist (e.g., no playlist creation/renaming or single-video moves), but these are peripheral to the core purpose.