youtube-watch-mcp
by Achanandhi-M
README.md
# youtube-watch-mcp
An [MCP](https://modelcontextprotocol.io) server that tracks your favorite YouTube creators and tells you when they've posted something new — right from Claude.
```
You: Add MKBHD to my YouTube watchlist
Claude: Now watching "Marques Brownlee" (UCBJycsmduvYEL83R_U4JriQ).
Latest upload so far: "The Best Car I've Ever Driven: McLaren W1"
— future checks will only report videos newer than this.
You: Did any of my channels post something new?
Claude: Checked 3 channel(s), found 1 new video total.
Marques Brownlee: 1 new video
- "..." (2026-07-05) https://www.youtube.com/watch?v=...
```
No YouTube API key, no Google Cloud project, no quotas — it reads each channel's public RSS feed.
## Features
- `add_channel` — start tracking a creator by channel ID, `@handle`, or channel URL
- `remove_channel` — stop tracking one
- `list_channels` — see everything you're watching
- `check_new_videos` — check one channel, or all of them, for uploads since the last check
- `get_latest_videos` — browse any channel's recent uploads without touching tracking state
- A `watchlist://channels` **resource** exposing your current watchlist as JSON
## How it works internally
```
┌─────────────┐ stdio (JSON-RPC) ┌────────────────────┐ HTTPS ┌──────────────────────┐
│ Claude │ ───────────────────▶ │ youtube-watch-mcp │ ────────▶ │ YouTube (public) │
│ (MCP client) │ ◀─────────────────── │ (this server) │ ◀──────── │ RSS + channel pages │
└─────────────┘ └────────────────────┘ └──────────────────────┘
│
▼
data/watchlist.json
(local, persisted state)
```
**Transport.** The server communicates with its client (Claude Desktop, Claude Code, etc.) over **stdio** — the client spawns `node dist/index.js` as a subprocess and exchanges JSON-RPC messages over stdin/stdout. This is the standard local-server transport in MCP; nothing is exposed over the network.
**Tools vs. resources.** Each capability above (`add_channel`, `check_new_videos`, ...) is registered as an MCP **tool** — a function with a typed input schema (validated with [zod](https://zod.dev)) that the model can decide to call based on your request. `watchlist://channels` is registered as an MCP **resource** instead — a read-only piece of data a client can pull in as context without "calling" anything.
**Resolving a channel with no API key** (`src/youtube.ts`). When you pass a `@handle` or a channel URL, the server fetches that page's plain HTML and extracts the channel's real ID from it. This turned out to be less trivial than it sounds: a channel page's HTML contains dozens of `"channelId":"UC..."` strings for *unrelated* channels (recommended/related-channel shelves), so grabbing the first match resolves to the wrong creator. Instead, the server reads the page's `<link rel="canonical">` tag and `"externalId"` field — both of which specifically identify the page's own owner. If you pass a raw channel ID (`UC...`) directly, none of this scraping happens.
**Fetching uploads** (`src/youtube.ts`). Every YouTube channel exposes a public Atom feed at:
```
https://www.youtube.com/feeds/videos.xml?channel_id=UC...
```
No auth, no quota — but it only returns the ~15 most recent uploads. The server parses this XML with [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) into a simple `{ channelTitle, videos[] }` shape, where `videos` is ordered newest-first.
**Tracking "new since last time"** (`src/index.ts`, `src/storage.ts`). For each watched channel, the server persists the ID of the most recent video it has seen. `check_new_videos` walks the freshly-fetched feed from newest to oldest and collects every video until it hits that last-seen ID (or runs out of feed, if the channel posted more than ~15 videos since the last check). It then updates the stored ID to the current newest video. `add_channel` seeds this "last seen" value immediately with the channel's current latest upload, so adding a channel never immediately reports its entire back-catalog as "new."
**Storage** (`src/storage.ts`). The watchlist lives in `data/watchlist.json`, next to the compiled server — a flat JSON array of `{ channelId, nickname, addedAt, lastVideoId, lastCheckedAt }`. No database; it's just read, mutated, and rewritten on every change.
## Setup
### Prerequisites
- [Node.js](https://nodejs.org) 18+
### Install & build
```bash
git clone https://github.com/Achanandhi-M/youtube-watch-mcp.git
cd youtube-watch-mcp
npm install
npm run build
```
This compiles TypeScript from `src/` into `dist/`.
### Connect it to Claude Code
```bash
claude mcp add youtube-watch -- node /absolute/path/to/youtube-watch-mcp/dist/index.js
```
Restart/reconnect Claude Code and the tools listed above become available.
### Connect it to Claude Desktop
Add this to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS) under `mcpServers`, then restart the app:
```json
{
"mcpServers": {
"youtube-watch": {
"command": "node",
"args": ["/absolute/path/to/youtube-watch-mcp/dist/index.js"]
}
}
}
```
## Usage examples
- "Add MKBHD and Veritasium to my YouTube watchlist"
- "Check if any of my watched creators posted something new"
- "Show me the last 5 videos from @fireship without adding it to my watchlist"
- "Stop tracking Veritasium"
## Limitations
- YouTube's RSS feed only returns the ~15 most recent uploads per channel. If a channel you're tracking goes unchecked for long enough to publish more than that, older uploads in between won't be reported as "new" — check regularly rather than sporadically.
- Channel resolution from `@handle`/URL scrapes public HTML rather than using an official API, so it could break if YouTube changes its page markup. Using a direct channel ID (`UC...`) with `add_channel` avoids this entirely.
- No notifications/push — this is a pull-based tool. Nothing checks in the background; a check only happens when you (via Claude) ask for one.
## Project structure
```
src/
index.ts MCP server: tool + resource definitions
youtube.ts Channel resolution + RSS feed fetching/parsing
storage.ts JSON-file watchlist persistence
data/
watchlist.json Your tracked channels (gitignored, created on first run)
```
## License
MIT — see [LICENSE](LICENSE).
TDQS
A3.9/5.0
Scored across 5 tools
Disambiguation5/5
Each tool targets a distinct action: adding, checking, listing, and removing channels. No overlap in functionality.
Naming Consistency5/5
All tools follow snake_case verb_noun pattern (e.g., add_channel, check_new_videos), perfectly consistent.
Tool Count5/5
5 tools is appropriate for a YouTube watch tracking server, covering core operations without excess.
Completeness4/5
Covers CRUD for channels with add/list/remove, plus two video retrieval methods. Minor gap: no tool explicitly resets or updates watch status, but check_new_videos handles new video detection.
Maintenance
ActivityInactive
ResponsivenessNo issues