@thenavidm/youtube-mcp-cli 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +877 -0
  3. package/SKILL.md +162 -0
  4. package/dist/accounts/store.d.ts +41 -0
  5. package/dist/accounts/store.js +110 -0
  6. package/dist/accounts/store.js.map +1 -0
  7. package/dist/auth.d.ts +14 -0
  8. package/dist/auth.js +206 -0
  9. package/dist/auth.js.map +1 -0
  10. package/dist/cli.d.ts +55 -0
  11. package/dist/cli.js +439 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/config.d.ts +42 -0
  14. package/dist/config.js +130 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/doctor.d.ts +7 -0
  17. package/dist/doctor.js +78 -0
  18. package/dist/doctor.js.map +1 -0
  19. package/dist/index.d.ts +14 -0
  20. package/dist/index.js +134 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/safety.d.ts +50 -0
  23. package/dist/safety.js +84 -0
  24. package/dist/safety.js.map +1 -0
  25. package/dist/server.d.ts +10 -0
  26. package/dist/server.js +28 -0
  27. package/dist/server.js.map +1 -0
  28. package/dist/tools/account.d.ts +10 -0
  29. package/dist/tools/account.js +222 -0
  30. package/dist/tools/account.js.map +1 -0
  31. package/dist/tools/index.d.ts +9 -0
  32. package/dist/tools/index.js +12 -0
  33. package/dist/tools/index.js.map +1 -0
  34. package/dist/tools/kit.d.ts +57 -0
  35. package/dist/tools/kit.js +78 -0
  36. package/dist/tools/kit.js.map +1 -0
  37. package/dist/tools/research.d.ts +16 -0
  38. package/dist/tools/research.js +232 -0
  39. package/dist/tools/research.js.map +1 -0
  40. package/dist/tools/transcripts.d.ts +9 -0
  41. package/dist/tools/transcripts.js +123 -0
  42. package/dist/tools/transcripts.js.map +1 -0
  43. package/dist/tools/types.d.ts +6 -0
  44. package/dist/tools/types.js +2 -0
  45. package/dist/tools/types.js.map +1 -0
  46. package/dist/transport/http.d.ts +23 -0
  47. package/dist/transport/http.js +83 -0
  48. package/dist/transport/http.js.map +1 -0
  49. package/dist/youtube/api.d.ts +47 -0
  50. package/dist/youtube/api.js +176 -0
  51. package/dist/youtube/api.js.map +1 -0
  52. package/dist/youtube/transcripts.d.ts +65 -0
  53. package/dist/youtube/transcripts.js +321 -0
  54. package/dist/youtube/transcripts.js.map +1 -0
  55. package/dist/youtube/ytdlp.d.ts +35 -0
  56. package/dist/youtube/ytdlp.js +145 -0
  57. package/dist/youtube/ytdlp.js.map +1 -0
  58. package/package.json +65 -0
package/README.md ADDED
@@ -0,0 +1,877 @@
1
+ <img src="https://cdn.navid.media/connectors/youtube-icon.png" alt="YouTube" width="88">
2
+
3
+ # YouTube MCP Server & CLI
4
+
5
+ [![npm](https://img.shields.io/npm/v/@thenavidm%2Fyoutube-mcp-cli?color=orange&label=npm)](https://www.npmjs.com/package/@thenavidm/youtube-mcp-cli)
6
+ [![License](https://img.shields.io/badge/License-MIT-green)](./LICENSE)
7
+ [![YouTube](https://img.shields.io/badge/YouTube-@thenavidm-red?logo=youtube&logoColor=white)](https://youtube.com/@thenavidm?sub_confirmation=1)
8
+ [![X](https://img.shields.io/badge/X-@thenavidm-black?logo=x)](https://x.com/thenavidm)
9
+ [![LinkedIn](https://img.shields.io/badge/LinkedIn-thenavidm-0A66C2?logo=linkedin&logoColor=white)](https://linkedin.com/in/thenavidm)
10
+
11
+ YouTube MCP server and CLI for Claude Code and AI agents. 16 tools for transcripts, search, channel research, comments, analytics and multi-channel management.
12
+
13
+ One install gives you both surfaces, the same tools under the same names.
14
+
15
+ Read the transcript of any public YouTube video, with nothing set up. Search
16
+ results come back with view counts attached, which YouTube's own search endpoint
17
+ does not return.
18
+
19
+ Connect as many channels as you run, with one login each and no config file.
20
+
21
+ Built and maintained by [Navid Moazzez](https://navid.me?utm_source=github&utm_medium=readme&utm_campaign=youtube-mcp-cli).
22
+
23
+ ```
24
+ You: what does this video actually say about pricing?
25
+ https://youtu.be/dQw4w9WgXcQ
26
+
27
+ Claude: [search_transcript] Three mentions.
28
+
29
+ [2:14] "we never charge for the first seat"
30
+ [7:41] "the pricing page is deliberately one number"
31
+ [11:02] "annual is not a discount, it is a commitment"
32
+
33
+ Jump straight to 7:41 for the reasoning.
34
+ ```
35
+
36
+ ## Two ways to use it
37
+
38
+ ### Command line
39
+
40
+ `youtube-cli` in your terminal, for scripting, cron, pipes, or a quick question
41
+ without opening anything:
42
+
43
+ ```bash
44
+ youtube-cli # every command, one line each
45
+ youtube-cli get-transcript --video https://youtu.be/dQw4w9WgXcQ
46
+ youtube-cli list-transcript-languages dQw4w9WgXcQ
47
+ youtube-cli search-videos "local-first software" # with view counts
48
+ youtube-cli list-comments --video-id dQw4w9WgXcQ --limit 20
49
+ youtube-cli login # connect a channel, once each
50
+ youtube-cli list-my-videos --account thenavidm --limit 10
51
+ youtube-cli <command> --help # what any command takes
52
+ ```
53
+
54
+ `--confirm` is the shell spelling of the confirmation that replying to a comment
55
+ and deleting a video require. `--agent` gives compact JSON with no prompts, and
56
+ errors are JSON on stderr whichever output you pick.
57
+
58
+ `youtube-cli schema <command>` prints the exact JSON Schema an MCP client
59
+ receives for that tool, which is how you can check the two surfaces really are
60
+ one thing.
61
+
62
+ ### MCP server, for AI agents
63
+
64
+ `youtube-mcp` is what Claude Code, Claude Desktop, Cursor and the rest launch.
65
+ You never run it by hand:
66
+
67
+ ```bash
68
+ claude mcp add youtube -- npx -y @thenavidm/youtube-mcp-cli@latest
69
+ ```
70
+
71
+ Then just ask: _"which of this channel's last 30 videos actually overperformed?"_
72
+
73
+ Channels you connected with `youtube-cli login` on this machine are read
74
+ automatically, so the command needs no credentials. Every other client is in
75
+ [section 4](#4-connect-your-client-).
76
+
77
+ ### Which one
78
+
79
+ | Where you are | What you can reach |
80
+ |---|---|
81
+ | An agent that can run shell commands, like Claude Code or Cursor | Both. The CLI is the cheaper one: it costs almost nothing until you type it |
82
+ | claude.ai, the Claude Desktop chat tab, or a phone | The server only. There is no shell to run a command in |
83
+ | A terminal, a script, cron or CI | The CLI only. There is no MCP client in a shell |
84
+
85
+ They are the same program reading the same tool definitions, so anything one
86
+ can do, the other can.
87
+
88
+ ## Contents 📑
89
+
90
+ | # | Section | What is in it |
91
+ |---|---|---|
92
+ | 1 | [What you can ask it](#1-what-you-can-ask-it-) | Real prompts, not features |
93
+ | 2 | [Quick install](#2-quick-install-) | One line, no account needed |
94
+ | 3 | [Set up your account](#3-set-up-your-account-) | API key, then your channels |
95
+ | 4 | [Connect your client](#4-connect-your-client-) | Claude, Cursor, Windsurf, the rest |
96
+ | 5 | [Check it worked](#5-check-it-worked-) | And the two things that fail |
97
+ | 6 | [Which surface, and what each costs](#6-which-surface-and-what-each-costs-) | Measured in Claude Code, and how to spend less |
98
+ | 7 | [Tools](#7-tools-) | All 16, grouped by what they need |
99
+ | 8 | [Output and exit codes](#8-output-and-exit-codes-) | What scripts branch on |
100
+ | 9 | [Writing safely](#9-writing-safely-) | What is guarded and what is not |
101
+ | 10 | [Several channels](#10-several-channels-) | Login once each, pick by name |
102
+ | 11 | [Notes and gotchas](#11-notes-and-gotchas-) | How YouTube really behaves |
103
+ | 12 | [Your data](#12-your-data-) | What is stored, and where |
104
+ | 13 | [Troubleshooting](#13-troubleshooting-) | Symptom to cause |
105
+ | 14 | [FAQ](#14-faq-) | Including what an MCP server is |
106
+
107
+ ## 1. What you can ask it 💬
108
+
109
+ - Summarize this video in five bullets.
110
+ - Find where she talks about retention in this talk.
111
+ - Pull the transcripts of these six videos and tell me what the openings have in common.
112
+ - Which of this channel's last 30 videos actually overperformed?
113
+ - Compare how these two channels title their videos.
114
+ - Show me videos about local-first software from the last year with over 50,000 views.
115
+ - Read the comments on this video and group them by what people are asking for.
116
+ - What did my last video do on watch time versus the one before?
117
+ - Fix the typo in the title of that video.
118
+ - Which of my videos are still unlisted?
119
+
120
+ The one thing that is impossible without this: reading what was said in somebody
121
+ else's video. YouTube's Captions API only serves videos you own, so every
122
+ official route stops at your own channel. Transcripts here come from the public
123
+ caption tracks instead, so any public video is readable, and it needs no
124
+ credentials at all.
125
+
126
+ ## 2. Quick install ⚡
127
+
128
+ Node 20 or newer, and `yt-dlp` for transcripts.
129
+
130
+ ```bash
131
+ npx -y @thenavidm/youtube-mcp-cli@latest --version
132
+ ```
133
+
134
+ That is the whole install for an MCP client. `npx` fetches it on demand, so
135
+ there is nothing to update later.
136
+
137
+ For the command line, install it globally:
138
+
139
+ ```bash
140
+ npm install -g @thenavidm/youtube-mcp-cli
141
+ youtube-cli
142
+ ```
143
+
144
+ That gives you two commands: `youtube-mcp` is the server your AI tools launch,
145
+ `youtube-cli` is the one you type. They are one program, and the name only
146
+ decides what happens when you pass no arguments.
147
+
148
+ Transcripts also need `yt-dlp`, because YouTube stopped serving caption text
149
+ directly:
150
+
151
+ ```bash
152
+ brew install yt-dlp # macOS
153
+ pipx install yt-dlp # everywhere else
154
+ ```
155
+
156
+ ### Before you start
157
+
158
+ | You need | Check with | If missing |
159
+ |---|---|---|
160
+ | Node 20 or newer | `node -v` | [nodejs.org](https://nodejs.org) |
161
+ | yt-dlp, for transcripts | `yt-dlp --version` | `brew install yt-dlp` or `pipx install yt-dlp` |
162
+ | A Google Cloud project, for search and your channels | [console.cloud.google.com](https://console.cloud.google.com) | Free, no card, see section 3 |
163
+
164
+ ## 3. Set up your account 🔑
165
+
166
+ Three levels. Pick the one that matches what you want, because most people never
167
+ need the third.
168
+
169
+ **Transcripts need nothing.** Skip this whole section. It already works.
170
+
171
+ **Search, research and comments need an API key.** About ten minutes.
172
+
173
+ **Your own channels need OAuth.** About half an hour, most of it forms.
174
+
175
+ [INSTALL.md](INSTALL.md) is the long version, with every click and every failure
176
+ worth knowing about in advance.
177
+
178
+ ### An API key
179
+
180
+ 1. In the [Google Cloud console](https://console.cloud.google.com), create a project.
181
+ 2. In **APIs & Services > Library**, enable **YouTube Data API v3**. Add
182
+ **YouTube Analytics API** too if you will want watch time later.
183
+ 3. In **APIs & Services > Credentials**, choose **Create credentials > API key**,
184
+ then click **Restrict key** and limit it to the YouTube APIs.
185
+ 4. Save it:
186
+
187
+ ```bash
188
+ youtube-cli login --api-key AIza...
189
+ ```
190
+
191
+ It is stored encrypted on this machine, and every MCP client here picks it up.
192
+ On another machine, set `YOUTUBE_API_KEY` in the client config instead.
193
+
194
+ ### Your own channels
195
+
196
+ 1. Go to **Google Auth platform > Branding**, click **Get Started**, choose
197
+ **External** as the audience, and finish.
198
+ 2. Open **Audience**, and under **Test users** add the Google address that owns
199
+ each channel. Skipping this is the single most common reason login fails.
200
+ 3. Go to **Google Auth platform > Clients**, click **Create Client**, choose
201
+ **Desktop app**, and copy the client ID and secret.
202
+ 4. Connect each channel:
203
+
204
+ ```bash
205
+ export YOUTUBE_CLIENT_ID=...apps.googleusercontent.com
206
+ export YOUTUBE_CLIENT_SECRET=...
207
+ youtube-cli login
208
+ ```
209
+
210
+ A browser opens, you pick the channel, and it is saved. **Run `youtube-cli
211
+ login` once per channel.** Then `youtube-cli list-accounts` shows them all.
212
+ [Section 10](#10-several-channels-) covers using several.
213
+
214
+ You do not need Google to verify the app. Testing mode is the correct end state
215
+ for a tool you run yourself.
216
+
217
+ ## 4. Connect your client 🔌
218
+
219
+ Every block below is complete on its own. Pick your client, paste, done.
220
+
221
+ If you ran `youtube-cli login` on the same machine, leave the `env` block out
222
+ entirely: the server reads your saved channels and API key itself. Otherwise put
223
+ in whichever of these you have: `YOUTUBE_API_KEY`, and for your channels
224
+ `YOUTUBE_CLIENT_ID`, `YOUTUBE_CLIENT_SECRET` and `YOUTUBE_ACCOUNTS` (the entry
225
+ `youtube-cli login --print` prints).
226
+
227
+ ### Claude Code
228
+
229
+ ```bash
230
+ claude mcp add youtube -- npx -y @thenavidm/youtube-mcp-cli@latest
231
+ ```
232
+
233
+ With credentials in the environment instead:
234
+
235
+ ```bash
236
+ claude mcp add youtube \
237
+ -e YOUTUBE_API_KEY=your-key \
238
+ -e YOUTUBE_CLIENT_ID=your-client-id \
239
+ -e YOUTUBE_CLIENT_SECRET=your-client-secret \
240
+ -e YOUTUBE_REFRESH_TOKEN=your-refresh-token \
241
+ -- npx -y @thenavidm/youtube-mcp-cli@latest
242
+ ```
243
+
244
+ Run `/mcp` inside Claude Code and `youtube` should be listed. Remove it later
245
+ with `claude mcp remove youtube`.
246
+
247
+ ### Claude Desktop
248
+
249
+ The quickest route is the extension: download the
250
+ [`.mcpb`](https://github.com/thenavidm/youtube-mcp-cli/releases/latest) and
251
+ double-click it. It carries its own dependencies and asks for your API key and
252
+ channels in a form, so there is no config file to edit.
253
+
254
+ To wire it up by hand instead, open **Settings**, then **Developer**, then
255
+ **Edit Config**. That reveals `claude_desktop_config.json`. Or go straight there:
256
+
257
+ | System | Config file |
258
+ |---|---|
259
+ | macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
260
+ | Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
261
+ | Linux | `~/.config/Claude/claude_desktop_config.json` |
262
+
263
+ On macOS, `open -e ~/Library/Application\ Support/Claude/claude_desktop_config.json`
264
+ opens it in TextEdit.
265
+
266
+ If the file is empty, paste all of this. If you ran `youtube-cli login`, drop
267
+ the `env` block:
268
+
269
+ ```json
270
+ {
271
+ "mcpServers": {
272
+ "youtube": {
273
+ "command": "npx",
274
+ "args": ["-y", "@thenavidm/youtube-mcp-cli@latest"],
275
+ "env": {
276
+ "YOUTUBE_API_KEY": "your-key"
277
+ }
278
+ }
279
+ }
280
+ }
281
+ ```
282
+
283
+ If the file already has other servers, add only the `"youtube"` block inside
284
+ `"mcpServers"` and put a comma after the entry before it. One bad comma stops
285
+ every server loading, not just this one.
286
+
287
+ Then quit Claude Desktop completely and reopen it. On macOS use **Cmd+Q**,
288
+ closing the window is not enough. It only reads that file at startup.
289
+
290
+ To confirm, open a new chat, click the tools icon and look for `youtube`, then
291
+ ask: _"list the caption languages of https://youtu.be/dQw4w9WgXcQ"_.
292
+
293
+ > [!TIP]
294
+ > Claude Desktop does not inherit your shell PATH, so if `npx` is not found, run
295
+ > `which npx` and use that absolute path as `command`.
296
+
297
+ When it does not show up, the log says why:
298
+
299
+ | System | Log |
300
+ |---|---|
301
+ | macOS | `tail -f ~/Library/Logs/Claude/mcp*.log` |
302
+ | Windows | `%APPDATA%\Claude\logs\` |
303
+
304
+ The two usual causes are Node not being on the PATH the app sees, which the tip
305
+ above fixes, and malformed JSON, which a missing or extra comma causes.
306
+
307
+ ### Cursor
308
+
309
+ Edit `~/.cursor/mcp.json` for every project, or `.cursor/mcp.json` inside one:
310
+
311
+ ```json
312
+ {
313
+ "mcpServers": {
314
+ "youtube": {
315
+ "command": "npx",
316
+ "args": ["-y", "@thenavidm/youtube-mcp-cli@latest"]
317
+ }
318
+ }
319
+ }
320
+ ```
321
+
322
+ Then reload the window: **Cmd+Shift+P**, **Developer: Reload Window**. The
323
+ server appears under **Settings > MCP**.
324
+
325
+ ### Windsurf
326
+
327
+ Edit `~/.codeium/windsurf/mcp_config.json`, with the same `mcpServers` block as
328
+ Cursor. Then press the refresh button in the MCP panel, or restart Windsurf.
329
+
330
+ ### VS Code
331
+
332
+ Run **MCP: Add Server** from the command palette, or create `.vscode/mcp.json`
333
+ in a project:
334
+
335
+ ```json
336
+ {
337
+ "servers": {
338
+ "youtube": {
339
+ "type": "stdio",
340
+ "command": "npx",
341
+ "args": ["-y", "@thenavidm/youtube-mcp-cli@latest"]
342
+ }
343
+ }
344
+ }
345
+ ```
346
+
347
+ VS Code shows a **Start** link above the entry. Click it, then open Copilot Chat
348
+ in agent mode and the tools are listed.
349
+
350
+ ### Anything else
351
+
352
+ Zed, Cline, Continue and any other MCP client over stdio all work. They each
353
+ want the same three things: `command` (`npx`), `args`
354
+ (`["-y", "@thenavidm/youtube-mcp-cli@latest"]`), and `env`.
355
+
356
+ ### Docker
357
+
358
+ No image is published, so build it yourself. The image includes `yt-dlp`.
359
+
360
+ ```bash
361
+ git clone https://github.com/thenavidm/youtube-mcp-cli.git && cd youtube-mcp-cli
362
+ docker build -t youtube-mcp-cli .
363
+ docker run -i --rm -e YOUTUBE_API_KEY=your-key youtube-mcp-cli
364
+ ```
365
+
366
+ A container cannot read the channels you saved on the host, so pass them as
367
+ `YOUTUBE_ACCOUNTS`, with `YOUTUBE_CLIENT_ID` and `YOUTUBE_CLIENT_SECRET`.
368
+
369
+ ### Self-hosted over HTTP
370
+
371
+ For a machine that is always on:
372
+
373
+ ```bash
374
+ YOUTUBE_HTTP_TOKEN=a-long-random-string youtube-mcp --http --port=8787
375
+ ```
376
+
377
+ It binds to `127.0.0.1` and serves `/health`. To reach it from elsewhere, set
378
+ `YOUTUBE_HTTP_HOST=0.0.0.0` together with `YOUTUBE_HTTP_TOKEN`, and put it
379
+ behind TLS.
380
+
381
+ > [!CAUTION]
382
+ > The HTTP transport holds live credentials for your channels. A connected
383
+ > refresh token can delete videos. Binding it beyond localhost without a token
384
+ > hands your channels to anyone who finds the port.
385
+
386
+ ## 5. Check it worked 🩺
387
+
388
+ ```bash
389
+ youtube-cli doctor
390
+ ```
391
+
392
+ Or, without a global install, `npx -y @thenavidm/youtube-mcp-cli@latest doctor`.
393
+
394
+ It reports each layer separately, transcripts, the API key, then every channel
395
+ by name, so you can see how far you got.
396
+
397
+ Two failures happen far more than the rest. **`yt-dlp not found`** means
398
+ transcripts cannot work until you install it, though everything else still will.
399
+ **`unauthorized_client`** on a channel means that token was issued by a different
400
+ OAuth client than the one used now, which reads like a revoked grant but is
401
+ not: check the client before reconnecting anything.
402
+
403
+ ## 6. Which surface, and what each costs 💰
404
+
405
+ Both surfaces are the same program with the same 16 tools. The
406
+ difference is when the model pays for them. Measured in Claude Code:
407
+
408
+ | | MCP server | CLI |
409
+ |---|---|---|
410
+ | Every message, with every tool loaded | 5,000 tokens | nothing |
411
+ | Every message, Claude Code's default | 490 tokens | nothing |
412
+ | When YouTube comes up | nothing more, or the tools it picks | 2,300 tokens for `SKILL.md`, once |
413
+ | 20 messages with YouTube in 1, every tool loaded | 100,000 tokens | 2,300 tokens |
414
+
415
+ Claude Code's [tool search](https://code.claude.com/docs/en/mcp#scale-with-mcp-tool-search)
416
+ is on by default: it sends only the tool names and the server instructions,
417
+ and loads a tool's full definition when the model reaches for it. An app that
418
+ loads every tool up front pays the first line on every message, whether
419
+ YouTube comes up or not. With the skill added, Claude Code also lists its
420
+ one-line description, about 160 tokens.
421
+
422
+ To spend less, turn the server off when you are not using it, which in Claude
423
+ Code is the `/mcp` panel. `YOUTUBE_READ_ONLY=1` takes the 3 write tools off the list, leaving 13.
424
+ Or install the CLI and add the server on the days it earns its place.
425
+
426
+ Measured on 2026-09-27 with Claude Code 2.1.257 on Claude Opus 5: one
427
+ short prompt with and without the server connected, once with
428
+ `ENABLE_TOOL_SEARCH=false` and once with the default, the difference read
429
+ from the API's own usage figures. `SKILL.md` was measured the same way. Other
430
+ apps and models count tokens a little differently.
431
+
432
+ ## 7. Tools 🛠️
433
+
434
+ Every tool is also a command: the tool name with dashes. `*` marks a write, `!`
435
+ one that needs `--confirm` in the terminal or `confirm: true` through MCP.
436
+
437
+ ### Transcripts
438
+
439
+ No credentials. Reads any public video.
440
+
441
+ | Command | MCP tool | What it does |
442
+ |---|---|---|
443
+ | `get-transcript` | `get_transcript` | The full transcript, as prose or timestamped lines |
444
+ | `list-transcript-languages` | `list_transcript_languages` | Every caption language, and whether it is auto-generated |
445
+ | `search-transcript` | `search_transcript` | Find a phrase, get timestamps that link to the second |
446
+ | `get-transcripts` | `get_transcripts` | Up to 20 videos in one call |
447
+
448
+ ### Research
449
+
450
+ Needs an API key. Reads anyone's public data.
451
+
452
+ | Command | MCP tool | What it does |
453
+ |---|---|---|
454
+ | `search-videos` | `search_videos` | Search, with view counts and duration joined on |
455
+ | `get-channel` | `get_channel` | Subscribers, total views, video count, uploads playlist |
456
+ | `analyze-channel` | `analyze_channel` | Recent videos scored against that channel's own median |
457
+ | `get-video` | `get_video` | Full detail for one video |
458
+
459
+ ### Your channels
460
+
461
+ Needs `youtube-cli login`. `list-comments` also works on any public video with
462
+ just an API key.
463
+
464
+ | Command | MCP tool | What it does |
465
+ |---|---|---|
466
+ | `list-accounts` | `list_accounts` | Every connected channel |
467
+ | `get-my-channel` | `get_my_channel` | Your exact subscriber count, not the rounded public one |
468
+ | `get-channel-analytics` | `get_channel_analytics` | Watch time, retention, traffic sources, subscriber change |
469
+ | `list-my-videos` | `list_my_videos` | Your videos, including private and unlisted |
470
+ | `list-comments` | `list_comments` | Comment threads on a video |
471
+ | `update-video` * | `update_video` | Title, description, tags, privacy |
472
+ | `reply-to-comment` ! | `reply_to_comment` | A public reply |
473
+ | `delete-video` ! | `delete_video` | Permanent |
474
+
475
+ That is 13 read tools and 3 write tools.
476
+
477
+ ### Setup commands
478
+
479
+ These belong to the command line only, since they are what you run before
480
+ anything works.
481
+
482
+ | Command | What it does |
483
+ |---|---|
484
+ | `youtube-cli login` | Connect a channel through OAuth. Run once per channel |
485
+ | `youtube-cli login --api-key KEY` | Save an API key for search and research |
486
+ | `youtube-cli logout <channel>` | Forget a saved channel |
487
+ | `youtube-cli doctor` | Check every layer of the setup |
488
+
489
+ ## 8. Output and exit codes 📤
490
+
491
+ Everything a script needs to branch on.
492
+
493
+ | Flag | What you get |
494
+ |---|---|
495
+ | none | compact text for reads, shaped for a model and readable in a terminal |
496
+ | `--json` | JSON, always |
497
+ | `--compact` | the same JSON on one line |
498
+ | `--agent` | compact JSON, no prompts, no color, in one flag |
499
+ | `--select a,b.c` | only the named fields of a JSON result, dotted paths descend |
500
+
501
+ Results go to stdout. Errors go to stderr, always as JSON, so one parse handles
502
+ both outcomes:
503
+
504
+ ```json
505
+ { "error": "No API key is configured. Run `youtube-cli login --api-key KEY` or set YOUTUBE_API_KEY for public data, or `youtube-cli login` for your own channels." }
506
+ ```
507
+
508
+ | Code | Means |
509
+ |---|---|
510
+ | `0` | it worked |
511
+ | `2` | you typed it wrong, or a guarded write was refused for want of `--confirm` |
512
+ | `3` | not found |
513
+ | `4` | authentication failed: reconnect the channel |
514
+ | `5` | an API error upstream |
515
+ | `7` | rate limited or out of quota: wait |
516
+ | `10` | nothing configured: run `youtube-cli login` or `login --api-key` |
517
+
518
+ So a script can tell a mistake it should fix from a failure it should retry:
519
+
520
+ ```bash
521
+ youtube-cli list-comments --video-id "$ID" --limit 50 --agent > comments.json
522
+ case $? in
523
+ 0) ;;
524
+ 7) echo "quota or rate limit, retry later" >&2 ;;
525
+ 10) echo "run youtube-cli login first" >&2; exit 1 ;;
526
+ *) echo "failed" >&2; exit 1 ;;
527
+ esac
528
+ ```
529
+
530
+ ## 9. Writing safely 🛟
531
+
532
+ Writes work by default, because managing a channel is the point.
533
+
534
+ Two tools are guarded: `reply_to_comment`, because it is public the moment it
535
+ lands and notifies someone, and `delete_video`, because YouTube removes a video
536
+ immediately with no trash and no undo. Both refuse without `--confirm` in the
537
+ terminal or `confirm: true` through MCP. The CLI goes through the same guard as
538
+ the server, so the rules are identical.
539
+
540
+ `update_video` is not guarded. A title is one keystroke to put back, and asking
541
+ to confirm reversible things teaches a model to confirm everything reflexively,
542
+ which is worse protection than not asking.
543
+
544
+ | Setting | Effect |
545
+ |---|---|
546
+ | `YOUTUBE_READ_ONLY=1` | Every write disappears from the tool list and the command list |
547
+ | `YOUTUBE_ALLOW_DESTRUCTIVE=0` | `update_video` stays, the two irreversible tools are blocked |
548
+ | `YOUTUBE_AUDIT_LOG=<path>` | One JSON line per attempted write, allowed and blocked alike |
549
+
550
+ Comments, titles, descriptions and transcripts are written by other people. The
551
+ tool descriptions and the shipped `SKILL.md` tell the model to treat them as
552
+ data, never as instructions. Keep that in mind before wiring this into anything
553
+ that runs unattended.
554
+
555
+ ## 10. Several channels 📺
556
+
557
+ ### Set them up
558
+
559
+ Run `youtube-cli login` once per channel, picking a different one in Google's
560
+ chooser each time. Brand channels on the same Google account show up there too.
561
+
562
+ ```bash
563
+ youtube-cli login # your main channel
564
+ youtube-cli login # the clips channel
565
+ youtube-cli list-accounts
566
+ ```
567
+
568
+ Each one is saved to `~/.youtube-mcp-cli/channels.json` with the OAuth client
569
+ that connected it, so channels connected through different clients still work
570
+ side by side.
571
+
572
+ ### Using them
573
+
574
+ Pass `--account` in the terminal, or `account` through MCP:
575
+
576
+ ```bash
577
+ youtube-cli list-my-videos --account thenavidm
578
+ youtube-cli get-channel-analytics --account clips --start-date 2026-08-01 --end-date 2026-08-31
579
+ ```
580
+
581
+ With two or more connected, every account command refuses without it and names
582
+ the choices. That is deliberate: acting on the wrong channel is not something
583
+ you can take back, so nothing ever picks one for you.
584
+
585
+ ### How a name is matched
586
+
587
+ The saved name is the channel's handle without the `@`, or its title when it
588
+ has no handle. `--account` matches that exactly first, then the channel title,
589
+ then a partial match, so `--account navid` finds `thenavidm` when nothing else
590
+ does.
591
+
592
+ ### Channels from the environment
593
+
594
+ `YOUTUBE_ACCOUNTS` (a JSON array) or `YOUTUBE_REFRESH_TOKEN` (one channel) still
595
+ work, for a container or another machine. They are read alongside the saved
596
+ ones, and an environment channel wins over a saved one with the same name.
597
+
598
+ `youtube-cli logout <name>` forgets a saved channel. Revoke it at
599
+ [Google Account permissions](https://myaccount.google.com/permissions) to cut
600
+ access completely.
601
+
602
+ ## 11. Notes and gotchas ⚠️
603
+
604
+ - **YouTube stopped serving caption text directly.** The track URL now answers
605
+ 200 with an empty body unless the request carries a proof-of-origin token. The
606
+ language list still comes from the watch page; the text comes through `yt-dlp`.
607
+ That is why it is a dependency rather than a nicety.
608
+ - **Search has its own daily allowance.** 100 calls a day, separate from the
609
+ 10,000-unit pool the other endpoints share. It is almost always search that
610
+ runs out first, so do not call it speculatively.
611
+ - **Analytics exists only for your own channels.** Watch time, retention and
612
+ traffic sources are not public for anyone else, at any price. No tool here can
613
+ work around that, and a proxy metric would be a worse answer than none.
614
+ - **Analytics lags about two days.** An empty result for yesterday usually means
615
+ the data has not landed yet, not that nothing happened.
616
+ - **Public subscriber counts are rounded.** YouTube rounds above 1,000 in its
617
+ public API, so `get_channel` and `get_my_channel` disagree on your own channel.
618
+ The second one is exact.
619
+ - **`update_video` replaces the whole snippet.** Passing only a title would blank
620
+ the description, so the current values are read back and merged first. This is
621
+ handled, but it is why the tool makes an extra call.
622
+ - **A refresh token only works with the client that issued it.** Rebuild the
623
+ OAuth client and every existing token dies with `unauthorized_client`, which
624
+ looks exactly like a revoked grant and sends people reconnecting in circles.
625
+ - **Video tags are only visible to the owner.** `get_video` shows them on your
626
+ own videos and returns nothing for anyone else's. The API does this, not a
627
+ permission you are missing.
628
+ - **Shorts skew channel analysis.** Their view counts are not comparable to
629
+ long-form on the same channel, so `analyze_channel` flags them rather than
630
+ quietly averaging them in.
631
+
632
+ ## 12. Your data 💾
633
+
634
+ There is no backend. Every request goes from your machine to Google directly,
635
+ and nothing is collected or sent anywhere else.
636
+
637
+ Two things are written to disk, both only when you ask:
638
+
639
+ | What | Where | When |
640
+ |---|---|---|
641
+ | Saved channels and API key | `~/.youtube-mcp-cli/channels.json`, or `YOUTUBE_MCP_HOME` | `youtube-cli login` |
642
+ | Audit log | wherever `YOUTUBE_AUDIT_LOG` points | only when set |
643
+
644
+ The channel file is written 0600 and encrypted with AES-256-GCM under a key
645
+ derived from your OS account and this machine, which is never stored. A copied
646
+ file is useless elsewhere. It is not a vault: code running as you on this
647
+ machine can derive the same key, which is the same exposure as an environment
648
+ variable. [SECURITY.md](SECURITY.md) has the detail.
649
+
650
+ ## 13. Troubleshooting 🔧
651
+
652
+ Run `youtube-cli doctor` first. It checks each layer separately and most answers
653
+ are in its output.
654
+
655
+ | Symptom | Cause |
656
+ |---|---|
657
+ | `yt-dlp is not installed` | Transcripts need it. `brew install yt-dlp` or `pipx install yt-dlp` |
658
+ | Exit code 10, "No API key is configured" | Run `youtube-cli login --api-key KEY`, or set `YOUTUBE_API_KEY` |
659
+ | Exit code 10, "No account is configured" | Run `youtube-cli login` for that channel |
660
+ | `redirect_uri_mismatch` at login | The OAuth client is a Web client. Use a Desktop app client, or add `http://localhost:8765/callback` |
661
+ | `Access blocked` at the consent screen | The channel's Google address is not in **Test users** |
662
+ | `unauthorized_client` | The token came from a different OAuth client than the one used now |
663
+ | No refresh token returned | Google issues one on first consent only. Revoke at Google Account permissions, then `youtube-cli login` again |
664
+ | The token dies after seven days | The app is still in testing. Publish it under **Audience**, or log in again |
665
+ | 403, API not enabled | YouTube Data API v3 is off in that Cloud project |
666
+ | 403 on captions or comments | The token predates the `force-ssl` scope. Run `youtube-cli login` again |
667
+ | `quotaExceeded` | The pool resets at midnight Pacific |
668
+ | Search stops working before anything else | Search has its own 100-call daily allowance |
669
+ | HTTP 429 on a transcript | YouTube is rate limiting your IP. Waiting is the only fix |
670
+ | A command refuses and lists your channels | Two or more are connected. Pass `--account` |
671
+ | Every write command has vanished | `YOUTUBE_READ_ONLY=1` is set |
672
+ | Server missing in Claude Desktop | Use the absolute path to `npx`, check the JSON, and fully quit the app |
673
+
674
+ ## Environment variables
675
+
676
+ None are needed for transcripts, and none are needed at all on a machine where
677
+ you ran `youtube-cli login`.
678
+
679
+ **Credentials**
680
+
681
+ | Variable | Default | What it does |
682
+ |---|---|---|
683
+ | `YOUTUBE_API_KEY` | the saved key | Public search, channel lookup and comments |
684
+ | `YOUTUBE_CLIENT_ID` | none | Your OAuth client, needed by `login` and by env channels |
685
+ | `YOUTUBE_CLIENT_SECRET` | none | Its secret |
686
+ | `YOUTUBE_OAUTH_CLIENT_ID` | none | The same, under the other common spelling |
687
+ | `YOUTUBE_OAUTH_CLIENT_SECRET` | none | The same, under the other common spelling |
688
+ | `YOUTUBE_ACCOUNTS` | none | JSON array, several channels at once |
689
+ | `YOUTUBE_REFRESH_TOKEN` | none | One channel |
690
+ | `YOUTUBE_ACCESS_TOKEN` | none | One channel, short-lived, for testing |
691
+ | `YOUTUBE_CHANNEL_NAME` | `default` | What to call that one channel |
692
+
693
+ **Safety**
694
+
695
+ | Variable | Default | What it does |
696
+ |---|---|---|
697
+ | `YOUTUBE_READ_ONLY` | `0` | `1` hides every write |
698
+ | `YOUTUBE_ALLOW_DESTRUCTIVE` | `1` | `0` blocks the two irreversible tools |
699
+ | `YOUTUBE_AUDIT_LOG` | none | Append-only log of every attempted write |
700
+
701
+ **Tuning**
702
+
703
+ | Variable | Default | What it does |
704
+ |---|---|---|
705
+ | `YOUTUBE_REQUEST_TIMEOUT_MS` | `30000` | Per-request deadline |
706
+ | `YOUTUBE_TRANSCRIPT_LANG` | `en` | Default transcript language |
707
+ | `YOUTUBE_YTDLP_PATH` | `yt-dlp` on PATH | Where yt-dlp is |
708
+ | `YOUTUBE_MCP_HOME` | `~/.youtube-mcp-cli` | Where `login` saves channels |
709
+ | `YOUTUBE_OAUTH_PORT` | `8765` | The localhost port `login` listens on |
710
+ | `YOUTUBE_HTTP_PORT` | `8787` | For `--http` |
711
+ | `YOUTUBE_HTTP_HOST` | `127.0.0.1` | For `--http` |
712
+ | `YOUTUBE_HTTP_TOKEN` | none | Bearer token required by `--http` |
713
+
714
+ ## Versions
715
+
716
+ See [CHANGELOG.md](CHANGELOG.md).
717
+
718
+ ## 14. FAQ ❓
719
+
720
+ <details>
721
+ <summary><b>What is an MCP server?</b></summary>
722
+
723
+ An MCP server is a standard way to give an AI assistant real access to a tool, so
724
+ it can act rather than guess. You install it once, your assistant gains the
725
+ tools, and it works in Claude, Cursor and anything else speaking MCP.
726
+
727
+ </details>
728
+
729
+ <details>
730
+ <summary><b>What is the CLI?</b></summary>
731
+
732
+ `youtube-cli` is the same program as the MCP server, run as commands. AI agents that run commands, like Claude Code, Codex and OpenCode, use it on their own, and you can type the same commands in a terminal, a script or a cron job. Every tool is a command with dashes, so `get_transcript` runs as `youtube-cli get-transcript`.
733
+
734
+ </details>
735
+
736
+ <details>
737
+ <summary><b>Should I use the MCP server or the CLI?</b></summary>
738
+
739
+ Use the MCP server in an app with no terminal, like Claude Desktop's chat. Use the CLI anywhere commands run: an agent like Claude Code, Codex or OpenCode, a script or a cron job. The MCP server's tools take up context on every message, and the CLI costs nothing until it runs.
740
+
741
+ </details>
742
+
743
+ <details>
744
+ <summary><b>What is the YouTube Data API?</b></summary>
745
+
746
+ The YouTube Data API is Google's official interface to YouTube, covering videos,
747
+ channels, playlists and comments. It is what this uses for everything except
748
+ transcripts, which the API does not offer for videos you do not own.
749
+
750
+ </details>
751
+
752
+ <details>
753
+ <summary><b>Do I need to be technical?</b></summary>
754
+
755
+ You need to run a command or paste a few lines into a config file. Transcripts
756
+ work with no setup whatsoever, so you can install it, try it, and only do the
757
+ credential work if you want search or your own channel.
758
+
759
+ </details>
760
+
761
+ <details>
762
+ <summary><b>Can I connect more than one channel?</b></summary>
763
+
764
+ You can connect as many as you run. Run `youtube-cli login` once per channel,
765
+ and pass `--account` to pick one. With two or more connected the tools refuse to
766
+ guess, which is deliberate: acting on the wrong channel is not something you can
767
+ take back.
768
+
769
+ </details>
770
+
771
+ <details>
772
+ <summary><b>Is my data sent anywhere?</b></summary>
773
+
774
+ Your credentials stay on your machine and go only to Google. There is no
775
+ backend, nothing is collected, and nothing phones anywhere. The code is here to
776
+ read.
777
+
778
+ </details>
779
+
780
+ <details>
781
+ <summary><b>What can it do that youtube.com cannot?</b></summary>
782
+
783
+ It reads the transcript of any public video as text you can search, compare and
784
+ feed to a model. The site shows you captions one video at a time. Pulling twenty
785
+ transcripts to find what their openings have in common is a minute here and an
786
+ afternoon by hand.
787
+
788
+ </details>
789
+
790
+ <details>
791
+ <summary><b>Can it delete one of my videos by accident?</b></summary>
792
+
793
+ It cannot delete anything without `--confirm` or `confirm: true`, which a model
794
+ has to set deliberately after reading a description saying the action is
795
+ permanent. If you want the possibility gone entirely, set `YOUTUBE_READ_ONLY=1`
796
+ and every write disappears.
797
+
798
+ </details>
799
+
800
+ <details>
801
+ <summary><b>Does it cost anything?</b></summary>
802
+
803
+ It costs nothing. The package is MIT, and the YouTube Data API is free within a
804
+ daily quota that ordinary use does not come near. Google does not ask for a card.
805
+
806
+ </details>
807
+
808
+ <details>
809
+ <summary><b>Does it work with ChatGPT and Cursor?</b></summary>
810
+
811
+ It works with any client that speaks MCP, including Cursor, Windsurf and VS Code.
812
+ [Section 4](#4-connect-your-client-) has a block for each one.
813
+
814
+ </details>
815
+
816
+ <details>
817
+ <summary><b>What happens when my token expires?</b></summary>
818
+
819
+ Access tokens last an hour and are refreshed automatically, so you will not
820
+ notice. A refresh token lasts until you revoke it, with one exception: an OAuth
821
+ app still in testing issues refresh tokens that expire after seven days. Publish
822
+ the app to stop that, or run `youtube-cli login` again when it happens.
823
+
824
+ </details>
825
+
826
+ <details>
827
+ <summary><b>How do I disconnect it?</b></summary>
828
+
829
+ Remove the server from your client's config, run `youtube-cli logout` for each
830
+ channel, and revoke the app at
831
+ [Google Account permissions](https://myaccount.google.com/permissions). Deleting
832
+ the Cloud project removes the API key and the OAuth client together.
833
+
834
+ </details>
835
+
836
+ ## Questions 💬
837
+
838
+ Run into a problem or have a question? [Open an issue](https://github.com/thenavidm/youtube-mcp-cli/issues) and I will help.
839
+
840
+ ## About the author 👋
841
+
842
+ Navid Moazzez is a leading AI business strategist, and the host of the AI Creator Summit, watched by 100,000+ creators. He helps creators and founders master AI and build their own AI Operating System (AI OS) to automate their business and life. This YouTube MCP server is one piece of that system.
843
+
844
+ **Links**
845
+
846
+ - Personal website: [navid.me](https://navid.me?utm_source=github&utm_medium=readme&utm_campaign=youtube-mcp-cli)
847
+ - Link in bio: [navid.bio](https://navid.bio?utm_source=github&utm_medium=readme&utm_campaign=youtube-mcp-cli)
848
+ - Navid Media: [navid.media](https://navid.media?utm_source=github&utm_medium=readme&utm_campaign=youtube-mcp-cli)
849
+ - YouTube: [@thenavidm](https://youtube.com/@thenavidm?sub_confirmation=1) and [@thenavidai](https://youtube.com/@thenavidai?sub_confirmation=1)
850
+ - X: [@thenavidm](https://x.com/thenavidm)
851
+ - Instagram: [@thenavidm](https://instagram.com/thenavidm)
852
+ - LinkedIn: [thenavidm](https://linkedin.com/in/thenavidm)
853
+
854
+ If this is useful, star the repo and come say hi on [X](https://x.com/thenavidm).
855
+
856
+ ## Dependencies 📦
857
+
858
+ | Library | License | What it does |
859
+ |---|---|---|
860
+ | [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) | MIT | The MCP server and transports |
861
+ | [zod](https://github.com/colinhacks/zod) | MIT | Tool argument schemas and validation |
862
+ | [zod-to-json-schema](https://github.com/StefanTerdell/zod-to-json-schema) | ISC | Turns those schemas into what an MCP client receives |
863
+
864
+ [yt-dlp](https://github.com/yt-dlp/yt-dlp) is an optional external command, used
865
+ only to fetch caption tracks. It is not bundled and is never loaded into this
866
+ process.
867
+
868
+ ## License ⚖️
869
+
870
+ [MIT](./LICENSE). Free to use, modify, and share.
871
+
872
+ Not affiliated with, endorsed by, or connected to Google LLC. YouTube is a
873
+ trademark of Google LLC.
874
+
875
+ ---
876
+
877
+ © 2026 [NM Media](https://navid.media?utm_source=github&utm_medium=readme&utm_campaign=youtube-mcp-cli). Made with ❤️ by [Navid Moazzez](https://navid.me?utm_source=github&utm_medium=readme&utm_campaign=youtube-mcp-cli).