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.
- package/README.md +219 -175
- package/dist/http.d.ts +64 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +283 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +19 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +342 -164
- package/dist/index.js.map +1 -1
- package/dist/prompts.d.ts +3 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +167 -0
- package/dist/prompts.js.map +1 -0
- package/dist/resources.d.ts +10 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +72 -0
- package/dist/resources.js.map +1 -0
- package/dist/schemas.d.ts +326 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +128 -0
- package/dist/schemas.js.map +1 -0
- package/dist/tools/batch.d.ts +111 -0
- package/dist/tools/batch.d.ts.map +1 -0
- package/dist/tools/batch.js +187 -0
- package/dist/tools/batch.js.map +1 -0
- package/dist/tools/check-health.d.ts +59 -0
- package/dist/tools/check-health.d.ts.map +1 -0
- package/dist/tools/check-health.js +38 -0
- package/dist/tools/check-health.js.map +1 -0
- package/dist/tools/clips.d.ts +158 -0
- package/dist/tools/clips.d.ts.map +1 -0
- package/dist/tools/clips.js +279 -0
- package/dist/tools/clips.js.map +1 -0
- package/dist/tools/download-video.d.ts +43 -12
- package/dist/tools/download-video.d.ts.map +1 -1
- package/dist/tools/download-video.js +32 -3
- package/dist/tools/download-video.js.map +1 -1
- package/dist/tools/fetch-videos.d.ts +39 -7
- package/dist/tools/fetch-videos.d.ts.map +1 -1
- package/dist/tools/fetch-videos.js +20 -3
- package/dist/tools/fetch-videos.js.map +1 -1
- package/dist/tools/get-channel-info.d.ts +11 -7
- package/dist/tools/get-channel-info.d.ts.map +1 -1
- package/dist/tools/get-channel-info.js +5 -3
- package/dist/tools/get-channel-info.js.map +1 -1
- package/dist/tools/get-chapters.d.ts +28 -6
- package/dist/tools/get-chapters.d.ts.map +1 -1
- package/dist/tools/get-chapters.js +24 -4
- package/dist/tools/get-chapters.js.map +1 -1
- package/dist/tools/get-comments.d.ts +26 -7
- package/dist/tools/get-comments.d.ts.map +1 -1
- package/dist/tools/get-comments.js +10 -4
- package/dist/tools/get-comments.js.map +1 -1
- package/dist/tools/get-playlist-info.d.ts +13 -6
- package/dist/tools/get-playlist-info.d.ts.map +1 -1
- package/dist/tools/get-playlist-info.js +4 -2
- package/dist/tools/get-playlist-info.js.map +1 -1
- package/dist/tools/get-transcript.d.ts +42 -7
- package/dist/tools/get-transcript.d.ts.map +1 -1
- package/dist/tools/get-transcript.js +139 -10
- package/dist/tools/get-transcript.js.map +1 -1
- package/dist/tools/get-video-info.d.ts +17 -6
- package/dist/tools/get-video-info.d.ts.map +1 -1
- package/dist/tools/get-video-info.js +5 -2
- package/dist/tools/get-video-info.js.map +1 -1
- package/dist/tools/library.d.ts +127 -0
- package/dist/tools/library.d.ts.map +1 -0
- package/dist/tools/library.js +118 -0
- package/dist/tools/library.js.map +1 -0
- package/dist/tools/list-library.d.ts +40 -6
- package/dist/tools/list-library.d.ts.map +1 -1
- package/dist/tools/list-library.js +8 -2
- package/dist/tools/list-library.js.map +1 -1
- package/dist/tools/save-to-library.d.ts +9 -6
- package/dist/tools/save-to-library.d.ts.map +1 -1
- package/dist/tools/save-to-library.js +9 -2
- package/dist/tools/save-to-library.js.map +1 -1
- package/dist/tools/search-channels.d.ts +33 -7
- package/dist/tools/search-channels.d.ts.map +1 -1
- package/dist/tools/search-channels.js +11 -4
- package/dist/tools/search-channels.js.map +1 -1
- package/dist/tools/search-transcript.d.ts +52 -0
- package/dist/tools/search-transcript.d.ts.map +1 -0
- package/dist/tools/search-transcript.js +103 -0
- package/dist/tools/search-transcript.js.map +1 -0
- package/dist/tools/search-videos.d.ts +39 -7
- package/dist/tools/search-videos.d.ts.map +1 -1
- package/dist/tools/search-videos.js +21 -3
- package/dist/tools/search-videos.js.map +1 -1
- package/dist/utils/clips.d.ts +69 -0
- package/dist/utils/clips.d.ts.map +1 -0
- package/dist/utils/clips.js +202 -0
- package/dist/utils/clips.js.map +1 -0
- package/dist/utils/context.d.ts +24 -0
- package/dist/utils/context.d.ts.map +1 -0
- package/dist/utils/context.js +19 -0
- package/dist/utils/context.js.map +1 -0
- package/dist/utils/errors.d.ts +58 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +152 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/format.d.ts +34 -5
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +45 -0
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/preflight.d.ts +30 -0
- package/dist/utils/preflight.d.ts.map +1 -0
- package/dist/utils/preflight.js +79 -0
- package/dist/utils/preflight.js.map +1 -0
- package/dist/utils/search-index.d.ts +58 -0
- package/dist/utils/search-index.d.ts.map +1 -0
- package/dist/utils/search-index.js +192 -0
- package/dist/utils/search-index.js.map +1 -0
- package/dist/utils/storage.d.ts +83 -6
- package/dist/utils/storage.d.ts.map +1 -1
- package/dist/utils/storage.js +204 -21
- package/dist/utils/storage.js.map +1 -1
- package/dist/utils/transcript.d.ts +142 -0
- package/dist/utils/transcript.d.ts.map +1 -0
- package/dist/utils/transcript.js +216 -0
- package/dist/utils/transcript.js.map +1 -0
- package/dist/utils/validate.d.ts +16 -0
- package/dist/utils/validate.d.ts.map +1 -0
- package/dist/utils/validate.js +64 -0
- package/dist/utils/validate.js.map +1 -0
- package/dist/utils/youtube.d.ts +15 -1
- package/dist/utils/youtube.d.ts.map +1 -1
- package/dist/utils/youtube.js +231 -174
- package/dist/utils/youtube.js.map +1 -1
- package/dist/utils/ytdlp.d.ts +51 -0
- package/dist/utils/ytdlp.d.ts.map +1 -0
- package/dist/utils/ytdlp.js +191 -0
- package/dist/utils/ytdlp.js.map +1 -0
- 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
|
-
|
|
17
|
-
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
22
|
-
-
|
|
23
|
-
- **
|
|
24
|
-
|
|
25
|
-
- **
|
|
26
|
-
|
|
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
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
|
154
|
-
|
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
|
163
|
-
|
|
|
164
|
-
| `
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
|
175
|
-
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
|
194
|
-
|
|
|
195
|
-
| `
|
|
196
|
-
| `
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
|
226
|
-
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
|
236
|
-
|
|
|
237
|
-
| `
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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}.
|
|
285
|
-
├── library/ # Saved
|
|
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/ #
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
###
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
"
|
|
328
|
+
"Summarize just the 'Benchmarks' chapter of this 3-hour podcast"
|
|
307
329
|
```
|
|
308
330
|
|
|
309
|
-
|
|
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
|
-
"
|
|
337
|
+
"What does this 40-video course cover, and which three videos should I watch?"
|
|
313
338
|
```
|
|
314
339
|
|
|
315
|
-
|
|
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
|
-
"
|
|
345
|
+
"Pull these four moments as separate clips and export the subtitles as SRT"
|
|
319
346
|
```
|
|
320
347
|
|
|
321
|
-
|
|
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
|
-
"
|
|
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
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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"}
|