youtube-knowledge-mcp 1.1.1 → 1.2.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 (134) hide show
  1. package/README.md +219 -175
  2. package/dist/http.d.ts +64 -0
  3. package/dist/http.d.ts.map +1 -0
  4. package/dist/http.js +283 -0
  5. package/dist/http.js.map +1 -0
  6. package/dist/index.d.ts +19 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +342 -164
  9. package/dist/index.js.map +1 -1
  10. package/dist/prompts.d.ts +3 -0
  11. package/dist/prompts.d.ts.map +1 -0
  12. package/dist/prompts.js +167 -0
  13. package/dist/prompts.js.map +1 -0
  14. package/dist/resources.d.ts +10 -0
  15. package/dist/resources.d.ts.map +1 -0
  16. package/dist/resources.js +72 -0
  17. package/dist/resources.js.map +1 -0
  18. package/dist/schemas.d.ts +326 -0
  19. package/dist/schemas.d.ts.map +1 -0
  20. package/dist/schemas.js +128 -0
  21. package/dist/schemas.js.map +1 -0
  22. package/dist/tools/batch.d.ts +111 -0
  23. package/dist/tools/batch.d.ts.map +1 -0
  24. package/dist/tools/batch.js +187 -0
  25. package/dist/tools/batch.js.map +1 -0
  26. package/dist/tools/check-health.d.ts +59 -0
  27. package/dist/tools/check-health.d.ts.map +1 -0
  28. package/dist/tools/check-health.js +38 -0
  29. package/dist/tools/check-health.js.map +1 -0
  30. package/dist/tools/clips.d.ts +158 -0
  31. package/dist/tools/clips.d.ts.map +1 -0
  32. package/dist/tools/clips.js +279 -0
  33. package/dist/tools/clips.js.map +1 -0
  34. package/dist/tools/download-video.d.ts +43 -12
  35. package/dist/tools/download-video.d.ts.map +1 -1
  36. package/dist/tools/download-video.js +32 -3
  37. package/dist/tools/download-video.js.map +1 -1
  38. package/dist/tools/fetch-videos.d.ts +39 -7
  39. package/dist/tools/fetch-videos.d.ts.map +1 -1
  40. package/dist/tools/fetch-videos.js +20 -3
  41. package/dist/tools/fetch-videos.js.map +1 -1
  42. package/dist/tools/get-channel-info.d.ts +11 -7
  43. package/dist/tools/get-channel-info.d.ts.map +1 -1
  44. package/dist/tools/get-channel-info.js +5 -3
  45. package/dist/tools/get-channel-info.js.map +1 -1
  46. package/dist/tools/get-chapters.d.ts +28 -6
  47. package/dist/tools/get-chapters.d.ts.map +1 -1
  48. package/dist/tools/get-chapters.js +24 -4
  49. package/dist/tools/get-chapters.js.map +1 -1
  50. package/dist/tools/get-comments.d.ts +26 -7
  51. package/dist/tools/get-comments.d.ts.map +1 -1
  52. package/dist/tools/get-comments.js +10 -4
  53. package/dist/tools/get-comments.js.map +1 -1
  54. package/dist/tools/get-playlist-info.d.ts +13 -6
  55. package/dist/tools/get-playlist-info.d.ts.map +1 -1
  56. package/dist/tools/get-playlist-info.js +4 -2
  57. package/dist/tools/get-playlist-info.js.map +1 -1
  58. package/dist/tools/get-transcript.d.ts +42 -7
  59. package/dist/tools/get-transcript.d.ts.map +1 -1
  60. package/dist/tools/get-transcript.js +139 -10
  61. package/dist/tools/get-transcript.js.map +1 -1
  62. package/dist/tools/get-video-info.d.ts +17 -6
  63. package/dist/tools/get-video-info.d.ts.map +1 -1
  64. package/dist/tools/get-video-info.js +5 -2
  65. package/dist/tools/get-video-info.js.map +1 -1
  66. package/dist/tools/library.d.ts +127 -0
  67. package/dist/tools/library.d.ts.map +1 -0
  68. package/dist/tools/library.js +118 -0
  69. package/dist/tools/library.js.map +1 -0
  70. package/dist/tools/list-library.d.ts +40 -6
  71. package/dist/tools/list-library.d.ts.map +1 -1
  72. package/dist/tools/list-library.js +8 -2
  73. package/dist/tools/list-library.js.map +1 -1
  74. package/dist/tools/save-to-library.d.ts +9 -6
  75. package/dist/tools/save-to-library.d.ts.map +1 -1
  76. package/dist/tools/save-to-library.js +9 -2
  77. package/dist/tools/save-to-library.js.map +1 -1
  78. package/dist/tools/search-channels.d.ts +33 -7
  79. package/dist/tools/search-channels.d.ts.map +1 -1
  80. package/dist/tools/search-channels.js +11 -4
  81. package/dist/tools/search-channels.js.map +1 -1
  82. package/dist/tools/search-transcript.d.ts +52 -0
  83. package/dist/tools/search-transcript.d.ts.map +1 -0
  84. package/dist/tools/search-transcript.js +103 -0
  85. package/dist/tools/search-transcript.js.map +1 -0
  86. package/dist/tools/search-videos.d.ts +39 -7
  87. package/dist/tools/search-videos.d.ts.map +1 -1
  88. package/dist/tools/search-videos.js +21 -3
  89. package/dist/tools/search-videos.js.map +1 -1
  90. package/dist/utils/clips.d.ts +69 -0
  91. package/dist/utils/clips.d.ts.map +1 -0
  92. package/dist/utils/clips.js +202 -0
  93. package/dist/utils/clips.js.map +1 -0
  94. package/dist/utils/context.d.ts +24 -0
  95. package/dist/utils/context.d.ts.map +1 -0
  96. package/dist/utils/context.js +19 -0
  97. package/dist/utils/context.js.map +1 -0
  98. package/dist/utils/errors.d.ts +58 -0
  99. package/dist/utils/errors.d.ts.map +1 -0
  100. package/dist/utils/errors.js +152 -0
  101. package/dist/utils/errors.js.map +1 -0
  102. package/dist/utils/format.d.ts +34 -5
  103. package/dist/utils/format.d.ts.map +1 -1
  104. package/dist/utils/format.js +45 -0
  105. package/dist/utils/format.js.map +1 -1
  106. package/dist/utils/preflight.d.ts +30 -0
  107. package/dist/utils/preflight.d.ts.map +1 -0
  108. package/dist/utils/preflight.js +79 -0
  109. package/dist/utils/preflight.js.map +1 -0
  110. package/dist/utils/search-index.d.ts +58 -0
  111. package/dist/utils/search-index.d.ts.map +1 -0
  112. package/dist/utils/search-index.js +192 -0
  113. package/dist/utils/search-index.js.map +1 -0
  114. package/dist/utils/storage.d.ts +83 -6
  115. package/dist/utils/storage.d.ts.map +1 -1
  116. package/dist/utils/storage.js +204 -21
  117. package/dist/utils/storage.js.map +1 -1
  118. package/dist/utils/transcript.d.ts +142 -0
  119. package/dist/utils/transcript.d.ts.map +1 -0
  120. package/dist/utils/transcript.js +216 -0
  121. package/dist/utils/transcript.js.map +1 -0
  122. package/dist/utils/validate.d.ts +16 -0
  123. package/dist/utils/validate.d.ts.map +1 -0
  124. package/dist/utils/validate.js +64 -0
  125. package/dist/utils/validate.js.map +1 -0
  126. package/dist/utils/youtube.d.ts +15 -1
  127. package/dist/utils/youtube.d.ts.map +1 -1
  128. package/dist/utils/youtube.js +231 -174
  129. package/dist/utils/youtube.js.map +1 -1
  130. package/dist/utils/ytdlp.d.ts +51 -0
  131. package/dist/utils/ytdlp.d.ts.map +1 -0
  132. package/dist/utils/ytdlp.js +191 -0
  133. package/dist/utils/ytdlp.js.map +1 -0
  134. package/package.json +19 -14
package/README.md CHANGED
@@ -13,22 +13,52 @@ Supports both **local** (stdio) and **remote** (Streamable HTTP) transports.
13
13
 
14
14
  ## Features
15
15
 
16
- - **Search videos** by keyword or phrase
17
- - **Fetch videos** from playlists or channels
18
- - **Get video info** (title, channel, duration, views, likes, comments, description, tags)
19
- - **Extract transcripts** (auto-generated or manual captions)
20
- - **Get chapters** (timestamps and structure)
21
- - **Get comments** (top comments sorted by popularity)
22
- - **Get channel info** (name, handle, subscriber count, description)
23
- - **List formats** (available download resolutions and codecs)
24
- - **Download videos** with quality selection (local mode only)
25
- - **Save to library** (summaries, notes, skills) (local mode only)
26
- - **List library** with tag filtering (local mode only)
16
+ **Find and read**
17
+
18
+ - **Search** videos and channels by keyword
19
+ - **Fetch** videos from a playlist or channel
20
+ - **Video, channel and playlist metadata**, chapters, and top comments
21
+ - **Transcripts with timestamps**, sliced by time range or chapter, and capped so a
22
+ three-hour video cannot flood your context
23
+ - **Search inside a transcript** and get back `?t=` links that open the video at
24
+ the exact moment
25
+ - **Batch tools**: transcripts for many videos at once, or a whole-playlist digest
26
+
27
+ **Extract for editing**
28
+
29
+ - **Clip a time range** without downloading the whole video, cut precisely or on
30
+ keyframes, by timestamp or chapter name
31
+ - **Audio clips** in mp3, m4a, wav, flac or opus
32
+ - **Frame capture** at any timestamp, without downloading the file
33
+ - **Subtitle export** as SRT, WebVTT or plain text for Premiere, Resolve or CapCut
34
+ - **Full downloads** with quality presets
35
+
36
+ **Keep what you learn** (local mode)
37
+
38
+ - **Save** summaries and skill notes to a local library
39
+ - **Read them back**, and **search across all of them** with full-text ranking
40
+ - **Tag, retag and delete**
41
+
42
+ **Built to stay working**
43
+
44
+ - WebVTT parsed by the W3C reference implementation, not a hand-written matcher
45
+ - Typed, actionable errors — "no captions in en, try: fr, es, de" rather than a
46
+ wall of yt-dlp stderr
47
+ - Timeouts, retry with backoff, and a concurrency limit on every yt-dlp call
48
+ - `check_health` diagnoses missing or outdated yt-dlp and ffmpeg
49
+ - Structured output on every tool, plus MCP resources, prompts and completions
27
50
 
28
51
  ## Prerequisites
29
52
 
30
53
  - Node.js 20+
31
- - yt-dlp: `brew install yt-dlp` (macOS) or [see installation guide](https://github.com/yt-dlp/yt-dlp#installation)
54
+ - [yt-dlp](https://github.com/yt-dlp/yt-dlp#installation) — required by every tool.
55
+ `brew install yt-dlp` (macOS) or `pip install -U yt-dlp`
56
+ - [ffmpeg](https://ffmpeg.org/) — required for downloads, clip extraction and frame
57
+ capture. Everything else works without it.
58
+
59
+ Run the `check_health` tool to confirm both are installed and current. An
60
+ outdated yt-dlp is the most common cause of unexplained failures, since YouTube
61
+ changes frequently; `yt-dlp -U` fixes most of them.
32
62
 
33
63
  ## Installation
34
64
 
@@ -133,146 +163,115 @@ docker run -p 3000:10000 youtube-knowledge-mcp
133
163
 
134
164
  ## MCP Tools
135
165
 
136
- ### Remote + Local (10 tools)
137
-
138
- #### search_videos
139
-
140
- Search YouTube for videos by keyword or phrase.
141
-
142
- | Parameter | Type | Default | Description |
143
- | --------- | ------ | -------- | -------------------------------- |
144
- | `query` | string | required | Search query |
145
- | `limit` | number | 5 | Maximum results to return (1-20) |
146
-
147
- **Returns:** video IDs, titles, durations, channels, view counts, URLs
148
-
149
- #### fetch_videos
150
-
151
- List videos from a YouTube playlist or channel.
152
-
153
- | Parameter | Type | Default | Description |
154
- | --------- | ------ | -------- | ------------------------------------ |
155
- | `url` | string | required | Playlist URL, channel URL, or handle |
156
- | `limit` | number | 20 | Maximum videos to return (1-100) |
157
-
158
- #### get_video_info
159
-
160
- Get detailed metadata for a single YouTube video.
161
-
162
- | Parameter | Type | Description |
163
- | --------- | ------ | --------------- |
164
- | `video` | string | Video ID or URL |
165
-
166
- **Returns:** title, channel, duration, upload date, view count, like count, comment count, description, tags, thumbnail URL
167
-
168
- #### get_transcript
169
-
170
- Extract the full transcript from a YouTube video.
171
-
172
- | Parameter | Type | Default | Description |
173
- | ---------- | ------ | -------- | ------------------------------ |
174
- | `video` | string | required | Video ID or URL |
175
- | `language` | string | "en" | Preferred language (ISO 639-1) |
176
-
177
- **Returns:** plain text transcript with word count and detected language
178
-
179
- #### get_chapters
180
-
181
- Extract chapter markers and timestamps from a YouTube video.
182
-
183
- | Parameter | Type | Description |
184
- | --------- | ------ | --------------- |
185
- | `video` | string | Video ID or URL |
186
-
187
- **Returns:** chapter titles with start and end times. Empty list if no chapters found.
188
-
189
- #### get_comments
190
-
191
- Get top comments from a YouTube video sorted by popularity.
192
-
193
- | Parameter | Type | Default | Description |
194
- | --------- | ------ | -------- | ------------------------------------------- |
195
- | `video` | string | required | Video ID or URL |
196
- | `limit` | number | 20 | Maximum top-level comments to return (1-50) |
197
-
198
- **Returns:** author, text, like count, pinned status
199
-
200
- #### get_channel_info
201
-
202
- Get metadata for a YouTube channel.
203
-
204
- | Parameter | Type | Description |
205
- | --------- | ------ | ---------------------------------------------- |
206
- | `channel` | string | Channel URL, handle (e.g., @Fireship), or name |
207
-
208
- **Returns:** channel name, handle, subscriber count, description, channel URL
209
-
210
- #### search_channels
211
-
212
- Search YouTube for channels by keyword or phrase.
213
-
214
- | Parameter | Type | Default | Description |
215
- | --------- | ------ | -------- | --------------------------------- |
216
- | `query` | string | required | Search query for channels |
217
- | `limit` | number | 5 | Maximum channels to return (1-20) |
218
-
219
- **Returns:** channel names, handles, subscriber counts, descriptions, URLs
220
-
221
- #### get_playlist_info
222
-
223
- Get metadata for a YouTube playlist.
224
-
225
- | Parameter | Type | Description |
226
- | --------- | ------ | -------------------- |
227
- | `url` | string | YouTube playlist URL |
228
-
229
- **Returns:** title, channel, video count, last updated date, description
230
-
231
- #### list_formats
232
-
233
- List all available download formats for a YouTube video.
234
-
235
- | Parameter | Type | Description |
236
- | --------- | ------ | --------------- |
237
- | `video` | string | Video ID or URL |
238
-
239
- **Returns:** format IDs, extensions, resolutions, FPS, codecs, file sizes
240
-
241
- ### Local Only (3 tools, stdio mode)
242
-
243
- These tools operate on the local filesystem and are only available in stdio mode.
244
-
245
- #### download_video
246
-
247
- Download a YouTube video to local disk.
248
-
249
- | Parameter | Type | Default | Description |
250
- | ----------- | ------ | -------- | ------------------------------------------------------------------- |
251
- | `video` | string | required | Video ID or URL |
252
- | `quality` | string | "best" | Quality preset (best, 2160p, 1440p, 1080p, 720p, 480p, 360p, audio) |
253
- | `formatId` | string | - | Specific format code from list_formats |
254
- | `outputDir` | string | - | Custom output directory |
255
-
256
- #### save_to_library
257
-
258
- Save a summary or skill note to your local YouTube knowledge library.
259
-
260
- | Parameter | Type | Description |
261
- | ------------- | -------- | ---------------------------------- |
262
- | `videoId` | string | YouTube video ID |
263
- | `title` | string | Video title |
264
- | `content` | string | Content to save (markdown format) |
265
- | `contentType` | string | "summary" or "skill" |
266
- | `tags` | string[] | Tags for categorization (optional) |
267
- | `channel` | string | Channel name (optional) |
268
-
269
- #### list_library
270
-
271
- List all saved items in your local knowledge library.
272
-
273
- | Parameter | Type | Description |
274
- | --------- | ------ | --------------------------------------- |
275
- | `tag` | string | Filter by tag (optional, partial match) |
166
+ 27 tools. The 14 read-only ones work over both transports; the 13 that touch
167
+ your filesystem are registered only in local (stdio) mode, so a remote
168
+ deployment cannot reach the host's disk.
169
+
170
+ Every tool returns human-readable text **and** typed structured output, and
171
+ reports failures as an actionable message — `[NO_CAPTIONS] No "en" captions are
172
+ available for this video. Call get_transcript again with one of: fr, es, de.`
173
+
174
+ ### Discovery — remote + local
175
+
176
+ | Tool | Key parameters | Returns |
177
+ | ------------------- | ---------------- | ---------------------------------------------------------------- |
178
+ | `search_videos` | `query`, `limit` | Matching videos with durations, channels and view counts |
179
+ | `search_channels` | `query`, `limit` | Matching channels with subscriber counts |
180
+ | `fetch_videos` | `url`, `limit` | Videos in a playlist or channel |
181
+ | `get_video_info` | `video` | Title, channel, duration, views, likes, description, tags |
182
+ | `get_channel_info` | `channel` | Name, handle, subscriber count, description |
183
+ | `get_playlist_info` | `url` | Title, channel, video count, last updated |
184
+ | `get_chapters` | `video` | Chapter titles with start/end times and deep links |
185
+ | `get_comments` | `video`, `limit` | Top-level comments by popularity |
186
+ | `list_formats` | `video` | Available formats grouped by video+audio, video-only, audio-only |
187
+ | `check_health` | — | yt-dlp and ffmpeg status, versions, and staleness warnings |
188
+
189
+ ### Transcripts — remote + local
190
+
191
+ | Tool | Key parameters | Returns |
192
+ | ------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------- |
193
+ | `get_transcript` | `video`, `language`, `format`, `startTime`/`endTime`, `chapter`, `maxChars`, `offset`, `refresh` | Transcript as plain text, timestamped lines, or cues |
194
+ | `search_transcript` | `video`, `query`, `regex`, `caseSensitive`, `limit`, `contextSeconds` | Matches with timestamps and `?t=` deep links |
195
+ | `get_transcripts` | `videos` (up to 25), `language`, `maxCharsPerVideo` | Transcripts for many videos; failures reported per video |
196
+ | `digest_playlist` | `url`, `limit`, `includeChapters`, `includeTranscriptStats` | Per-video metadata, chapters and transcript stats |
197
+
198
+ `format: "timestamped"` prefixes each line with `[MM:SS]` — use it when you need
199
+ to cite or link to a moment. `maxChars` with `offset` reads a long transcript in
200
+ pieces instead of returning 100,000+ tokens at once.
201
+
202
+ ### Extraction for editing — local only
203
+
204
+ | Tool | Key parameters | Returns |
205
+ | -------------------- | -------------------------------------------------------------------------- | ---------------------------- |
206
+ | `extract_clip` | `video`, `start`+`end` or `chapter`, `quality`, `preciseCuts`, `outputDir` | Path to the cut video |
207
+ | `extract_audio_clip` | `video`, `start`+`end` or `chapter`, `audioFormat`, `outputDir` | Path to the audio file |
208
+ | `extract_clips` | `video`, `ranges` (up to 20), `quality`, `preciseCuts`, `outputDir` | One file per range |
209
+ | `extract_frame` | `video`, `timestamp`, `format`, `outputDir` | Path to a PNG or JPG still |
210
+ | `export_subtitles` | `video`, `format` (srt/vtt/txt), `language`, `outputDir` | Path to the subtitle file |
211
+ | `download_video` | `video`, `quality`, `formatId`, `outputDir` | Path to the downloaded video |
212
+
213
+ Clips are cut with `--download-sections`, so only the byte range covering the
214
+ window is fetched rather than the whole file. `preciseCuts` (default `true`)
215
+ cuts exactly at the requested times; set it to `false` for a faster
216
+ keyframe-aligned cut. All of these require ffmpeg.
217
+
218
+ ### Knowledge library — local only
219
+
220
+ | Tool | Key parameters | Returns |
221
+ | ----------------------- | --------------------------------------------------------------- | ----------------------------------- |
222
+ | `save_to_library` | `videoId`, `title`, `content`, `contentType`, `channel`, `tags` | Path to the saved note |
223
+ | `list_library` | `tag` | Saved items, newest first |
224
+ | `get_library_item` | `videoId`, `contentType` | The saved markdown and its metadata |
225
+ | `search_library` | `query`, `limit` | Ranked matches with excerpts |
226
+ | `update_library_tags` | `videoId`, `add`, `remove`, `replace` | The updated tags |
227
+ | `delete_library_item` | `videoId`, `contentType` | What was deleted |
228
+ | `rebuild_library_index` | — | Number of notes reindexed |
229
+
230
+ ## Prompts
231
+
232
+ Reusable workflows your client can invoke directly: `summarize_video`,
233
+ `extract_skill`, `compare_videos`, `research_topic`, `channel_deep_dive`,
234
+ `clip_from_quote` (find a phrase, then cut the clip around it), and
235
+ `review_library` (local only).
236
+
237
+ ## Resources
238
+
239
+ - `youtube://transcript/{videoId}` — a timestamped transcript, fetched and cached on first read
240
+ - `youtube://library/{videoId}/{summary|skill}` — a saved note (local only, and enumerable)
241
+
242
+ ## Error codes
243
+
244
+ Failures are reported inside the result so the model can read and recover from
245
+ them, each prefixed with a code and followed by a next step.
246
+
247
+ | Code | Meaning |
248
+ | ------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
249
+ | `PRIVATE`, `AGE_GATED`, `MEMBERS_ONLY`, `PREMIUM_ONLY`, `NOT_FOUND` | The video cannot be accessed |
250
+ | `LOGIN_REQUIRED` | yt-dlp reports the video needs a signed-in account |
251
+ | `NO_CAPTIONS` | No captions in the requested language; the message lists the ones that exist |
252
+ | `LIVE_NOT_ENDED` | An upcoming stream, or one whose recording is still processing |
253
+ | `RATE_LIMITED`, `TIMEOUT` | Transient; retried automatically with backoff before surfacing |
254
+ | `YTDLP_MISSING`, `FFMPEG_MISSING`, `YTDLP_FAILED` | A tooling problem; the message says how to fix it |
255
+ | `INVALID_INPUT` | A bad argument, caught before any network call |
256
+ | `CANCELLED` | The client cancelled the request |
257
+
258
+ ## Environment variables
259
+
260
+ All optional.
261
+
262
+ | Variable | Default | Purpose |
263
+ | ------------------------------- | --------- | -------------------------------------------------------------------------------------------------------- |
264
+ | `MCP_AUTH_TOKEN` | unset | Require this bearer token on the HTTP transport. **Set this if you expose the server beyond localhost.** |
265
+ | `MCP_ALLOWED_HOSTS` | unset | Comma-separated Host allowlist; enables DNS-rebinding protection |
266
+ | `MCP_ALLOWED_ORIGINS` | unset | Comma-separated Origin allowlist |
267
+ | `MCP_BIND_HOST` | `0.0.0.0` | Interface to bind |
268
+ | `PORT` | `3000` | HTTP port |
269
+ | `MCP_RATE_LIMIT` | `60` | Requests per window, per client |
270
+ | `MCP_RATE_WINDOW_MS` | `60000` | Rate-limit window |
271
+ | `MCP_SESSION_IDLE_MS` | `1800000` | Close HTTP sessions idle this long |
272
+ | `MCP_MAX_SESSIONS` | `1000` | Reject new sessions past this many |
273
+ | `YOUTUBE_MCP_MAX_CONCURRENCY` | `3` | Concurrent yt-dlp processes |
274
+ | `YOUTUBE_MCP_TRANSCRIPT_TTL_MS` | 30 days | Transcript cache lifetime |
276
275
 
277
276
  ## Library Storage
278
277
 
@@ -280,58 +279,98 @@ Content is stored in `~/.youtube-knowledge/`:
280
279
 
281
280
  ```
282
281
  ~/.youtube-knowledge/
283
- ├── transcripts/ # Cached transcripts
284
- │ └── {video_id}.txt
285
- ├── library/ # Saved content
282
+ ├── transcripts/ # Cached timestamped transcripts
283
+ │ └── {video_id}.{lang}.json
284
+ ├── library/ # Saved notes
286
285
  │ └── {video_id}/
287
286
  │ ├── metadata.json
288
287
  │ ├── summary.md
289
288
  │ └── skill.md
290
- ├── downloads/ # Downloaded videos
291
- │ └── {video_title}.{ext}
292
- └── index.json # Searchable index
289
+ ├── downloads/ # Full downloads
290
+ ├── clips/ # Extracted clips
291
+ ├── frames/ # Captured stills
292
+ ├── subtitles/ # Exported SRT / VTT / TXT
293
+ ├── index.json # Library index
294
+ └── search-index.json # Full-text search index
293
295
  ```
294
296
 
297
+ Transcripts are cached for 30 days by default; pass `refresh: true` to any
298
+ transcript tool to bypass the cache, or set `YOUTUBE_MCP_TRANSCRIPT_TTL_MS`.
299
+
300
+ Every tool that writes files confines its output to your home directory, and
301
+ `outputDir` is rejected if it points anywhere else.
302
+
295
303
  ## Usage Examples
296
304
 
297
- ### Search and analyze
305
+ ### Find a moment and cite it
306
+
307
+ ```
308
+ "Find where this video talks about rate limiting and give me the timestamp:
309
+ https://youtube.com/watch?v=..."
310
+ ```
311
+
312
+ `search_transcript` returns each match with a link that opens the video at that
313
+ second, so the claim can be checked rather than taken on trust.
314
+
315
+ ### Find a moment and clip it
298
316
 
299
317
  ```
300
- "Search YouTube for 'transformer architecture explained' and summarize the top result"
318
+ "Find where she says 'the real bottleneck was the database' and cut me a
319
+ 30-second clip around it"
301
320
  ```
302
321
 
303
- ### Explore a channel
322
+ `search_transcript` locates the moment, `extract_clip` cuts it. Only the byte
323
+ range covering the clip is downloaded.
324
+
325
+ ### Read one section of a long video
304
326
 
305
327
  ```
306
- "Show me the latest videos from @ThePrimeagen and get the chapters for the most recent one"
328
+ "Summarize just the 'Benchmarks' chapter of this 3-hour podcast"
307
329
  ```
308
330
 
309
- ### Deep content analysis
331
+ `get_chapters` finds the section, then `get_transcript` with `chapter:
332
+ "Benchmarks"` reads only that part instead of the whole thing.
333
+
334
+ ### Survey a playlist cheaply
310
335
 
311
336
  ```
312
- "Get the transcript and chapters for this video, then create a structured summary"
337
+ "What does this 40-video course cover, and which three videos should I watch?"
313
338
  ```
314
339
 
315
- ### Audience sentiment
340
+ `digest_playlist` returns metadata and chapters for every video in one call.
341
+
342
+ ### Prepare footage for an edit
316
343
 
317
344
  ```
318
- "What are the top comments on this video? What does the audience think?"
345
+ "Pull these four moments as separate clips and export the subtitles as SRT"
319
346
  ```
320
347
 
321
- ### Download a video (local only)
348
+ `extract_clips` cuts all four in one call; `export_subtitles` writes a file your
349
+ editor can import.
350
+
351
+ ### Build and query a knowledge base
322
352
 
323
353
  ```
324
- "Download this video in 1080p: https://youtube.com/watch?v=ABC123"
354
+ "Summarize this video and save it to my library tagged 'databases'"
355
+ "What have I saved about connection pooling?"
325
356
  ```
326
357
 
358
+ `save_to_library` stores it; `search_library` searches across everything saved
359
+ with full-text ranking.
360
+
327
361
  ## Testing
328
362
 
329
363
  ```bash
330
364
  npm test # Run all tests
331
365
  npm run test:watch # Watch mode
332
- npm run test:coverage # Coverage report
366
+ npm run test:coverage # Coverage report, with thresholds enforced
333
367
  ```
334
368
 
369
+ The suite covers the pure logic directly, drives the real server through an MCP
370
+ client over an in-memory transport, exercises the library against a real
371
+ temporary filesystem, and snapshots the tool manifest so any change to the
372
+ public surface shows up as a reviewable diff.
373
+
335
374
  ## Development
336
375
 
337
376
  ```bash
@@ -340,17 +379,22 @@ npm run build # Build for production
340
379
  npm run rebuild # Clean and rebuild
341
380
  npm start # Run server (stdio)
342
381
  npm run start:http # Run server (HTTP)
343
- npm run validate # Typecheck + lint + test
382
+ npm run validate # Typecheck + lint + format check + test
344
383
  ```
345
384
 
385
+ CI runs the same gate on Node 20, 22 and 24 for every push and pull request,
386
+ then boots the built server as a real MCP client to verify the manifest.
387
+
346
388
  ## Contributing
347
389
 
348
- Contributions are welcome! Please:
390
+ Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for the
391
+ project layout, coding standards, and how to add a tool.
392
+
393
+ ## Security
349
394
 
350
- 1. Fork the repository
351
- 2. Create a feature branch
352
- 3. Run tests (`npm run validate`)
353
- 4. Submit a pull request
395
+ The HTTP transport is unauthenticated unless you set `MCP_AUTH_TOKEN`. See
396
+ [SECURITY.md](SECURITY.md) before exposing it beyond localhost, and to report a
397
+ vulnerability.
354
398
 
355
399
  ## License
356
400
 
package/dist/http.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ import type { Request } from 'express';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ /**
4
+ * Streamable HTTP transport.
5
+ *
6
+ * Split out of index.ts because it carries all of the deployment-facing
7
+ * concerns — authentication, origin validation, session lifetime, rate limits —
8
+ * none of which apply to the stdio path.
9
+ */
10
+ export interface HttpConfig {
11
+ port: number;
12
+ /** Interface to bind. Containers need 0.0.0.0; local runs should use 127.0.0.1. */
13
+ bindHost: string;
14
+ /** When set, every request must present this as a bearer token. */
15
+ authToken?: string;
16
+ /** Host header allowlist. Empty means "accept any", which is only safe behind a proxy. */
17
+ allowedHosts: string[];
18
+ /** Origin header allowlist for browser callers. */
19
+ allowedOrigins: string[];
20
+ rateLimit: number;
21
+ rateWindowMs: number;
22
+ /** Sessions idle longer than this are closed, so the map cannot grow forever. */
23
+ sessionIdleMs: number;
24
+ maxSessions: number;
25
+ }
26
+ export declare function readHttpConfig(): HttpConfig;
27
+ /** Fixed-window counter, per client. */
28
+ export declare class RateLimiter {
29
+ private readonly limit;
30
+ private readonly windowMs;
31
+ private readonly hits;
32
+ constructor(limit: number, windowMs: number);
33
+ check(key: string, now?: number): {
34
+ allowed: boolean;
35
+ remaining: number;
36
+ retryAfter: number;
37
+ };
38
+ sweep(now?: number): void;
39
+ get size(): number;
40
+ }
41
+ /**
42
+ * Compare a presented token against the configured one in constant time, so the
43
+ * endpoint does not leak the token's prefix through response timing.
44
+ */
45
+ export declare function tokenMatches(presented: string | undefined, expected: string): boolean;
46
+ export declare function bearerToken(header: string | undefined): string | undefined;
47
+ export declare function clientKey(req: Request): string;
48
+ /**
49
+ * Node parses a repeated header into an array, so this field is genuinely
50
+ * `string | string[] | undefined`. Asserting it was always a string meant a
51
+ * request that sent `Mcp-Session-Id` twice reached `sessions.get()` holding an
52
+ * array, missed every session, and was reported as an invalid session ID.
53
+ * Ambiguous is treated as absent: two session IDs identify no session.
54
+ */
55
+ export declare function sessionIdOf(req: Request): string | undefined;
56
+ export interface HttpServerHandle {
57
+ close: () => Promise<void>;
58
+ /** Resolves once bound. Port 0 picks a free port, which tests rely on. */
59
+ ready: Promise<{
60
+ port: number;
61
+ }>;
62
+ }
63
+ export declare function startHttp(createServer: (mode: 'http') => McpServer, config?: HttpConfig): HttpServerHandle;
64
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAW,OAAO,EAAY,MAAM,SAAS,CAAC;AAI1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGzE;;;;;;GAMG;AAEH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,mFAAmF;IACnF,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0FAA0F;IAC1F,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,mDAAmD;IACnD,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAcD,wBAAgB,cAAc,IAAI,UAAU,CAa3C;AAED,wCAAwC;AACxC,qBAAa,WAAW;IAIpB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAJ3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyD;gBAG3D,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM;IAGnC,KAAK,CACH,GAAG,EAAE,MAAM,EACX,GAAG,SAAa,GACf;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;IAiB9D,KAAK,CAAC,GAAG,SAAa,GAAG,IAAI;IAM7B,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAarF;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAI1E;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAI9C;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAG5D;AAaD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,0EAA0E;IAC1E,KAAK,EAAE,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClC;AAED,wBAAgB,SAAS,CACvB,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,EACzC,MAAM,GAAE,UAA6B,GACpC,gBAAgB,CA6MlB"}