youtube-knowledge-mcp 2.1.0 → 2.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 +105 -34
- package/dist/brain-schemas.d.ts +10 -10
- package/dist/brain-schemas.d.ts.map +1 -1
- package/dist/brain-schemas.js +0 -4
- package/dist/brain-schemas.js.map +1 -1
- package/dist/http/app.d.ts +16 -0
- package/dist/http/app.d.ts.map +1 -0
- package/dist/http/app.js +176 -0
- package/dist/http/app.js.map +1 -0
- package/dist/http/auth.d.ts +38 -0
- package/dist/http/auth.d.ts.map +1 -0
- package/dist/http/auth.js +79 -0
- package/dist/http/auth.js.map +1 -0
- package/dist/http/config.d.ts +41 -0
- package/dist/http/config.d.ts.map +1 -0
- package/dist/http/config.js +65 -0
- package/dist/http/config.js.map +1 -0
- package/dist/http/rate-limiter.d.ts +24 -0
- package/dist/http/rate-limiter.d.ts.map +1 -0
- package/dist/http/rate-limiter.js +55 -0
- package/dist/http/rate-limiter.js.map +1 -0
- package/dist/http/sessions.d.ts +34 -0
- package/dist/http/sessions.d.ts.map +1 -0
- package/dist/http/sessions.js +62 -0
- package/dist/http/sessions.js.map +1 -0
- package/dist/http.d.ts +6 -48
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +35 -252
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -517
- package/dist/index.js.map +1 -1
- package/dist/prompts-brain.d.ts +4 -0
- package/dist/prompts-brain.d.ts.map +1 -0
- package/dist/prompts-brain.js +48 -0
- package/dist/prompts-brain.js.map +1 -0
- package/dist/prompts-research.d.ts +4 -0
- package/dist/prompts-research.d.ts.map +1 -0
- package/dist/prompts-research.js +74 -0
- package/dist/prompts-research.js.map +1 -0
- package/dist/prompts-shared.d.ts +13 -0
- package/dist/prompts-shared.d.ts.map +1 -0
- package/dist/prompts-shared.js +7 -0
- package/dist/prompts-shared.js.map +1 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +5 -100
- package/dist/prompts.js.map +1 -1
- package/dist/registry/brain-tools.d.ts +4 -0
- package/dist/registry/brain-tools.d.ts.map +1 -0
- package/dist/registry/brain-tools.js +104 -0
- package/dist/registry/brain-tools.js.map +1 -0
- package/dist/registry/channel-tools.d.ts +4 -0
- package/dist/registry/channel-tools.d.ts.map +1 -0
- package/dist/registry/channel-tools.js +53 -0
- package/dist/registry/channel-tools.js.map +1 -0
- package/dist/registry/health-tools.d.ts +4 -0
- package/dist/registry/health-tools.d.ts.map +1 -0
- package/dist/registry/health-tools.js +21 -0
- package/dist/registry/health-tools.js.map +1 -0
- package/dist/registry/index.d.ts +8 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +27 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/library-tools.d.ts +4 -0
- package/dist/registry/library-tools.d.ts.map +1 -0
- package/dist/registry/library-tools.js +116 -0
- package/dist/registry/library-tools.js.map +1 -0
- package/dist/registry/media-tools.d.ts +4 -0
- package/dist/registry/media-tools.d.ts.map +1 -0
- package/dist/registry/media-tools.js +100 -0
- package/dist/registry/media-tools.js.map +1 -0
- package/dist/registry/thumbnail-tools.d.ts +7 -0
- package/dist/registry/thumbnail-tools.d.ts.map +1 -0
- package/dist/registry/thumbnail-tools.js +72 -0
- package/dist/registry/thumbnail-tools.js.map +1 -0
- package/dist/registry/transcript-tools.d.ts +4 -0
- package/dist/registry/transcript-tools.d.ts.map +1 -0
- package/dist/registry/transcript-tools.js +68 -0
- package/dist/registry/transcript-tools.js.map +1 -0
- package/dist/registry/types.d.ts +41 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/registry/types.js +17 -0
- package/dist/registry/types.js.map +1 -0
- package/dist/registry/video-tools.d.ts +4 -0
- package/dist/registry/video-tools.d.ts.map +1 -0
- package/dist/registry/video-tools.js +101 -0
- package/dist/registry/video-tools.js.map +1 -0
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +13 -10
- package/dist/resources.js.map +1 -1
- package/dist/schemas.d.ts +11 -2
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +5 -0
- package/dist/schemas.js.map +1 -1
- package/dist/thumbnail-resource.d.ts +8 -0
- package/dist/thumbnail-resource.d.ts.map +1 -0
- package/dist/thumbnail-resource.js +38 -0
- package/dist/thumbnail-resource.js.map +1 -0
- package/dist/thumbnail-schemas.d.ts +512 -0
- package/dist/thumbnail-schemas.d.ts.map +1 -0
- package/dist/thumbnail-schemas.js +95 -0
- package/dist/thumbnail-schemas.js.map +1 -0
- package/dist/tools/batch.js +1 -1
- package/dist/tools/batch.js.map +1 -1
- package/dist/tools/build-brain.js +1 -1
- package/dist/tools/build-brain.js.map +1 -1
- package/dist/tools/check-health.d.ts +2 -0
- package/dist/tools/check-health.d.ts.map +1 -1
- package/dist/tools/check-health.js +9 -0
- package/dist/tools/check-health.js.map +1 -1
- package/dist/tools/clips.d.ts +9 -41
- package/dist/tools/clips.d.ts.map +1 -1
- package/dist/tools/clips.js +52 -128
- package/dist/tools/clips.js.map +1 -1
- package/dist/tools/fetch-channel-thumbnails.d.ts +179 -0
- package/dist/tools/fetch-channel-thumbnails.d.ts.map +1 -0
- package/dist/tools/fetch-channel-thumbnails.js +123 -0
- package/dist/tools/fetch-channel-thumbnails.js.map +1 -0
- package/dist/tools/fetch-videos.d.ts +3 -0
- package/dist/tools/fetch-videos.d.ts.map +1 -1
- package/dist/tools/fetch-videos.js +3 -0
- package/dist/tools/fetch-videos.js.map +1 -1
- package/dist/tools/frames-subtitles.d.ts +44 -0
- package/dist/tools/frames-subtitles.d.ts.map +1 -0
- package/dist/tools/frames-subtitles.js +104 -0
- package/dist/tools/frames-subtitles.js.map +1 -0
- package/dist/tools/get-channel-info.d.ts +2 -0
- package/dist/tools/get-channel-info.d.ts.map +1 -1
- package/dist/tools/get-channel-info.js +8 -1
- package/dist/tools/get-channel-info.js.map +1 -1
- package/dist/tools/get-playlist-info.d.ts.map +1 -1
- package/dist/tools/get-playlist-info.js +1 -0
- package/dist/tools/get-playlist-info.js.map +1 -1
- package/dist/tools/get-thumbnail.d.ts +38 -0
- package/dist/tools/get-thumbnail.d.ts.map +1 -0
- package/dist/tools/get-thumbnail.js +152 -0
- package/dist/tools/get-thumbnail.js.map +1 -0
- package/dist/tools/get-transcript.d.ts.map +1 -1
- package/dist/tools/get-transcript.js +2 -14
- package/dist/tools/get-transcript.js.map +1 -1
- package/dist/tools/library.d.ts.map +1 -1
- package/dist/tools/library.js +13 -3
- package/dist/tools/library.js.map +1 -1
- package/dist/tools/list-channel-thumbnails.d.ts +235 -0
- package/dist/tools/list-channel-thumbnails.d.ts.map +1 -0
- package/dist/tools/list-channel-thumbnails.js +79 -0
- package/dist/tools/list-channel-thumbnails.js.map +1 -0
- package/dist/tools/save-to-library.d.ts.map +1 -1
- package/dist/tools/save-to-library.js +5 -1
- package/dist/tools/save-to-library.js.map +1 -1
- package/dist/tools/search-channels.d.ts +6 -0
- package/dist/tools/search-channels.d.ts.map +1 -1
- package/dist/tools/search-transcript.d.ts.map +1 -1
- package/dist/tools/search-transcript.js +6 -13
- package/dist/tools/search-transcript.js.map +1 -1
- package/dist/tools/search-videos.d.ts +3 -0
- package/dist/tools/search-videos.d.ts.map +1 -1
- package/dist/utils/batches.d.ts +15 -0
- package/dist/utils/batches.d.ts.map +1 -0
- package/dist/utils/batches.js +31 -0
- package/dist/utils/batches.js.map +1 -0
- package/dist/utils/brain-build.d.ts.map +1 -1
- package/dist/utils/brain-build.js +1 -12
- package/dist/utils/brain-build.js.map +1 -1
- package/dist/utils/brain-index.d.ts.map +1 -1
- package/dist/utils/brain-index.js +3 -2
- package/dist/utils/brain-index.js.map +1 -1
- package/dist/utils/brain-lock.d.ts +2 -18
- package/dist/utils/brain-lock.d.ts.map +1 -1
- package/dist/utils/brain-lock.js +4 -80
- package/dist/utils/brain-lock.js.map +1 -1
- package/dist/utils/brain-lookup.d.ts +8 -0
- package/dist/utils/brain-lookup.d.ts.map +1 -1
- package/dist/utils/brain-lookup.js +5 -31
- package/dist/utils/brain-lookup.js.map +1 -1
- package/dist/utils/brain-storage.d.ts.map +1 -1
- package/dist/utils/brain-storage.js +5 -5
- package/dist/utils/brain-storage.js.map +1 -1
- package/dist/utils/caption-probe.d.ts +7 -0
- package/dist/utils/caption-probe.d.ts.map +1 -0
- package/dist/utils/caption-probe.js +36 -0
- package/dist/utils/caption-probe.js.map +1 -0
- package/dist/utils/channel-images.d.ts +16 -0
- package/dist/utils/channel-images.d.ts.map +1 -0
- package/dist/utils/channel-images.js +35 -0
- package/dist/utils/channel-images.js.map +1 -0
- package/dist/utils/channel-lookup.d.ts +5 -0
- package/dist/utils/channel-lookup.d.ts.map +1 -0
- package/dist/utils/channel-lookup.js +37 -0
- package/dist/utils/channel-lookup.js.map +1 -0
- package/dist/utils/chapters.d.ts +11 -0
- package/dist/utils/chapters.d.ts.map +1 -0
- package/dist/utils/chapters.js +24 -0
- package/dist/utils/chapters.js.map +1 -0
- package/dist/utils/clips.d.ts.map +1 -1
- package/dist/utils/clips.js +18 -26
- package/dist/utils/clips.js.map +1 -1
- package/dist/utils/env.d.ts +26 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js +72 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/errors.d.ts +1 -1
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +20 -2
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/file-lock.d.ts +34 -0
- package/dist/utils/file-lock.d.ts.map +1 -0
- package/dist/utils/file-lock.js +91 -0
- package/dist/utils/file-lock.js.map +1 -0
- package/dist/utils/flat-listing.d.ts +89 -0
- package/dist/utils/flat-listing.d.ts.map +1 -0
- package/dist/utils/flat-listing.js +68 -0
- package/dist/utils/flat-listing.js.map +1 -0
- package/dist/utils/format.d.ts +1 -3
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +1 -5
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/guard.d.ts +21 -0
- package/dist/utils/guard.d.ts.map +1 -0
- package/dist/utils/guard.js +105 -0
- package/dist/utils/guard.js.map +1 -0
- package/dist/utils/image-dimensions.d.ts +19 -0
- package/dist/utils/image-dimensions.d.ts.map +1 -0
- package/dist/utils/image-dimensions.js +93 -0
- package/dist/utils/image-dimensions.js.map +1 -0
- package/dist/utils/image-fetch.d.ts +31 -0
- package/dist/utils/image-fetch.d.ts.map +1 -0
- package/dist/utils/image-fetch.js +141 -0
- package/dist/utils/image-fetch.js.map +1 -0
- package/dist/utils/json-file.d.ts +14 -5
- package/dist/utils/json-file.d.ts.map +1 -1
- package/dist/utils/json-file.js +24 -9
- package/dist/utils/json-file.js.map +1 -1
- package/dist/utils/paths.d.ts +10 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +14 -0
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/pattern.d.ts +30 -0
- package/dist/utils/pattern.d.ts.map +1 -0
- package/dist/utils/pattern.js +130 -0
- package/dist/utils/pattern.js.map +1 -0
- package/dist/utils/search-index.d.ts.map +1 -1
- package/dist/utils/search-index.js +9 -4
- package/dist/utils/search-index.js.map +1 -1
- package/dist/utils/storage.d.ts.map +1 -1
- package/dist/utils/storage.js +14 -8
- package/dist/utils/storage.js.map +1 -1
- package/dist/utils/subtitles.d.ts +7 -0
- package/dist/utils/subtitles.d.ts.map +1 -0
- package/dist/utils/subtitles.js +30 -0
- package/dist/utils/subtitles.js.map +1 -0
- package/dist/utils/thumbnail-entry.d.ts +19 -0
- package/dist/utils/thumbnail-entry.d.ts.map +1 -0
- package/dist/utils/thumbnail-entry.js +119 -0
- package/dist/utils/thumbnail-entry.js.map +1 -0
- package/dist/utils/thumbnail-fetch.d.ts +37 -0
- package/dist/utils/thumbnail-fetch.d.ts.map +1 -0
- package/dist/utils/thumbnail-fetch.js +89 -0
- package/dist/utils/thumbnail-fetch.js.map +1 -0
- package/dist/utils/thumbnail-ladder.d.ts +44 -0
- package/dist/utils/thumbnail-ladder.d.ts.map +1 -0
- package/dist/utils/thumbnail-ladder.js +68 -0
- package/dist/utils/thumbnail-ladder.js.map +1 -0
- package/dist/utils/thumbnail-listing.d.ts +16 -0
- package/dist/utils/thumbnail-listing.d.ts.map +1 -0
- package/dist/utils/thumbnail-listing.js +26 -0
- package/dist/utils/thumbnail-listing.js.map +1 -0
- package/dist/utils/thumbnail-paths.d.ts +20 -0
- package/dist/utils/thumbnail-paths.d.ts.map +1 -0
- package/dist/utils/thumbnail-paths.js +35 -0
- package/dist/utils/thumbnail-paths.js.map +1 -0
- package/dist/utils/thumbnail-store.d.ts +19 -0
- package/dist/utils/thumbnail-store.d.ts.map +1 -0
- package/dist/utils/thumbnail-store.js +125 -0
- package/dist/utils/thumbnail-store.js.map +1 -0
- package/dist/utils/transcript-cache.d.ts +20 -0
- package/dist/utils/transcript-cache.d.ts.map +1 -0
- package/dist/utils/transcript-cache.js +127 -0
- package/dist/utils/transcript-cache.js.map +1 -0
- package/dist/utils/transcript-search.d.ts +20 -0
- package/dist/utils/transcript-search.d.ts.map +1 -0
- package/dist/utils/transcript-search.js +35 -0
- package/dist/utils/transcript-search.js.map +1 -0
- package/dist/utils/transcript.d.ts +10 -31
- package/dist/utils/transcript.d.ts.map +1 -1
- package/dist/utils/transcript.js +2 -64
- package/dist/utils/transcript.js.map +1 -1
- package/dist/utils/validate-youtube.d.ts +35 -0
- package/dist/utils/validate-youtube.d.ts.map +1 -0
- package/dist/utils/validate-youtube.js +87 -0
- package/dist/utils/validate-youtube.js.map +1 -0
- package/dist/utils/validate.d.ts +11 -4
- package/dist/utils/validate.d.ts.map +1 -1
- package/dist/utils/validate.js +43 -36
- package/dist/utils/validate.js.map +1 -1
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +19 -0
- package/dist/utils/version.js.map +1 -0
- package/dist/utils/youtube-channel.d.ts +44 -0
- package/dist/utils/youtube-channel.d.ts.map +1 -0
- package/dist/utils/youtube-channel.js +61 -0
- package/dist/utils/youtube-channel.js.map +1 -0
- package/dist/utils/youtube-download.d.ts +27 -0
- package/dist/utils/youtube-download.d.ts.map +1 -0
- package/dist/utils/youtube-download.js +118 -0
- package/dist/utils/youtube-download.js.map +1 -0
- package/dist/utils/youtube-schemas.d.ts +394 -0
- package/dist/utils/youtube-schemas.d.ts.map +1 -0
- package/dist/utils/youtube-schemas.js +118 -0
- package/dist/utils/youtube-schemas.js.map +1 -0
- package/dist/utils/youtube-search.d.ts +14 -0
- package/dist/utils/youtube-search.d.ts.map +1 -0
- package/dist/utils/youtube-search.js +39 -0
- package/dist/utils/youtube-search.js.map +1 -0
- package/dist/utils/youtube-url.d.ts +25 -0
- package/dist/utils/youtube-url.d.ts.map +1 -0
- package/dist/utils/youtube-url.js +75 -0
- package/dist/utils/youtube-url.js.map +1 -0
- package/dist/utils/youtube-video.d.ts +47 -0
- package/dist/utils/youtube-video.d.ts.map +1 -0
- package/dist/utils/youtube-video.js +98 -0
- package/dist/utils/youtube-video.js.map +1 -0
- package/dist/utils/youtube.d.ts +10 -122
- package/dist/utils/youtube.d.ts.map +1 -1
- package/dist/utils/youtube.js +11 -486
- package/dist/utils/youtube.js.map +1 -1
- package/dist/utils/ytdlp-env.d.ts +33 -0
- package/dist/utils/ytdlp-env.d.ts.map +1 -0
- package/dist/utils/ytdlp-env.js +121 -0
- package/dist/utils/ytdlp-env.js.map +1 -0
- package/dist/utils/ytdlp-limiter.d.ts +24 -0
- package/dist/utils/ytdlp-limiter.d.ts.map +1 -0
- package/dist/utils/ytdlp-limiter.js +69 -0
- package/dist/utils/ytdlp-limiter.js.map +1 -0
- package/dist/utils/ytdlp-parse.d.ts +13 -0
- package/dist/utils/ytdlp-parse.d.ts.map +1 -0
- package/dist/utils/ytdlp-parse.js +48 -0
- package/dist/utils/ytdlp-parse.js.map +1 -0
- package/dist/utils/ytdlp.d.ts +13 -18
- package/dist/utils/ytdlp.d.ts.map +1 -1
- package/dist/utils/ytdlp.js +21 -110
- package/dist/utils/ytdlp.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { accessSync, constants, statSync } from 'node:fs';
|
|
2
|
+
import { envEnum, envString } from './env.js';
|
|
3
|
+
import { assertInsideHome } from './validate.js';
|
|
4
|
+
/**
|
|
5
|
+
* The session options yt-dlp is started with, read from the environment.
|
|
6
|
+
*
|
|
7
|
+
* Cookies are how yt-dlp reads age-restricted, members-only and sign-in-gated
|
|
8
|
+
* videos, and how it gets past the "confirm you're not a bot" check YouTube
|
|
9
|
+
* sometimes applies to an address. They are also the user's own account, so
|
|
10
|
+
* everything here is careful about two things: the values are validated at
|
|
11
|
+
* boot and never at request time, and neither the cookie path nor its contents
|
|
12
|
+
* ever appear in a log line, a tool result or an error message.
|
|
13
|
+
*/
|
|
14
|
+
export const COOKIE_BROWSERS = [
|
|
15
|
+
'brave',
|
|
16
|
+
'chrome',
|
|
17
|
+
'chromium',
|
|
18
|
+
'edge',
|
|
19
|
+
'firefox',
|
|
20
|
+
'opera',
|
|
21
|
+
'safari',
|
|
22
|
+
'vivaldi',
|
|
23
|
+
'whale',
|
|
24
|
+
];
|
|
25
|
+
const PROXY_SCHEMES = new Set(['http:', 'https:', 'socks4:', 'socks4a:', 'socks5:', 'socks5h:']);
|
|
26
|
+
function readCookiesFile(env) {
|
|
27
|
+
const raw = envString('YOUTUBE_MCP_COOKIES_FILE', env);
|
|
28
|
+
if (raw === undefined)
|
|
29
|
+
return undefined;
|
|
30
|
+
// The message names the variable and never the path: the path is the one
|
|
31
|
+
// thing about a cookies file that should not travel.
|
|
32
|
+
const rejected = (reason) => new Error(`YOUTUBE_MCP_COOKIES_FILE ${reason}. Point it at a readable cookies file in your home directory.`);
|
|
33
|
+
let path;
|
|
34
|
+
try {
|
|
35
|
+
path = assertInsideHome(raw, 'cookies file');
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw rejected('must be inside your home directory');
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
if (!statSync(path).isFile())
|
|
42
|
+
throw rejected('is not a regular file');
|
|
43
|
+
accessSync(path, constants.R_OK);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (error instanceof Error && error.message.startsWith('YOUTUBE_MCP_COOKIES_FILE'))
|
|
47
|
+
throw error;
|
|
48
|
+
throw rejected('could not be read');
|
|
49
|
+
}
|
|
50
|
+
return path;
|
|
51
|
+
}
|
|
52
|
+
function readProxy(env) {
|
|
53
|
+
const raw = envString('YOUTUBE_MCP_PROXY', env);
|
|
54
|
+
if (raw === undefined)
|
|
55
|
+
return undefined;
|
|
56
|
+
let url;
|
|
57
|
+
try {
|
|
58
|
+
url = new URL(raw);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
throw new Error('YOUTUBE_MCP_PROXY must be a URL such as socks5h://127.0.0.1:1080.');
|
|
62
|
+
}
|
|
63
|
+
if (!PROXY_SCHEMES.has(url.protocol)) {
|
|
64
|
+
throw new Error(`YOUTUBE_MCP_PROXY must use one of ${[...PROXY_SCHEMES].map((s) => s.slice(0, -1)).join(', ')}.`);
|
|
65
|
+
}
|
|
66
|
+
return url.toString();
|
|
67
|
+
}
|
|
68
|
+
function readSleep(env) {
|
|
69
|
+
const raw = envString('YOUTUBE_MCP_SLEEP_REQUESTS_S', env);
|
|
70
|
+
if (raw === undefined)
|
|
71
|
+
return undefined;
|
|
72
|
+
const seconds = Number(raw);
|
|
73
|
+
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
74
|
+
throw new Error('YOUTUBE_MCP_SLEEP_REQUESTS_S must be a non-negative number of seconds.');
|
|
75
|
+
}
|
|
76
|
+
return seconds;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Validate the session settings and turn them into yt-dlp flags.
|
|
80
|
+
*
|
|
81
|
+
* Throws a plain `Error` naming the variable: this runs at boot, where a bad
|
|
82
|
+
* setting should stop the server before a transport starts, not surface as a
|
|
83
|
+
* puzzling failure on the first tool call.
|
|
84
|
+
*/
|
|
85
|
+
export function readYtDlpEnv(env = process.env) {
|
|
86
|
+
const cookiesFile = readCookiesFile(env);
|
|
87
|
+
const browser = envEnum('YOUTUBE_MCP_COOKIES_FROM_BROWSER', COOKIE_BROWSERS, env);
|
|
88
|
+
const proxy = readProxy(env);
|
|
89
|
+
const sleep = readSleep(env);
|
|
90
|
+
if (cookiesFile !== undefined && browser !== undefined) {
|
|
91
|
+
throw new Error('Set either YOUTUBE_MCP_COOKIES_FILE or YOUTUBE_MCP_COOKIES_FROM_BROWSER, not both.');
|
|
92
|
+
}
|
|
93
|
+
const args = [];
|
|
94
|
+
if (cookiesFile !== undefined)
|
|
95
|
+
args.push('--cookies', cookiesFile);
|
|
96
|
+
if (browser !== undefined)
|
|
97
|
+
args.push('--cookies-from-browser', browser);
|
|
98
|
+
if (proxy !== undefined)
|
|
99
|
+
args.push('--proxy', proxy);
|
|
100
|
+
if (sleep !== undefined)
|
|
101
|
+
args.push('--sleep-requests', String(sleep));
|
|
102
|
+
return {
|
|
103
|
+
args,
|
|
104
|
+
cookies: cookiesFile !== undefined ? 'file' : browser !== undefined ? 'browser' : 'none',
|
|
105
|
+
...(browser === undefined ? {} : { browser }),
|
|
106
|
+
proxy: proxy !== undefined,
|
|
107
|
+
...(sleep === undefined ? {} : { sleepRequestsSeconds: sleep }),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** One line for the boot log: what is configured, never what it is. */
|
|
111
|
+
export function describeYtDlpEnv(settings) {
|
|
112
|
+
const cookies = settings.cookies === 'browser'
|
|
113
|
+
? `browser (${settings.browser ?? 'unknown'})`
|
|
114
|
+
: settings.cookies;
|
|
115
|
+
const parts = [`yt-dlp cookies: ${cookies}`, `proxy: ${settings.proxy ? 'configured' : 'none'}`];
|
|
116
|
+
if (settings.sleepRequestsSeconds !== undefined) {
|
|
117
|
+
parts.push(`sleep between requests: ${settings.sleepRequestsSeconds}s`);
|
|
118
|
+
}
|
|
119
|
+
return parts.join(' · ');
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=ytdlp-env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ytdlp-env.js","sourceRoot":"","sources":["../../src/utils/ytdlp-env.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Takes a concurrency slot, waiting for one when the limiter is full.
|
|
3
|
+
*
|
|
4
|
+
* The wait honours the request's abort signal. It used to be a bare promise
|
|
5
|
+
* with no way out, so a client that gave up stayed queued forever — and since a
|
|
6
|
+
* media transfer runs without a wall-clock timeout, three stalled downloads
|
|
7
|
+
* held every slot and every later call queued behind them, uncancellable. The
|
|
8
|
+
* server was not slow, it was wedged, and no tool could run again.
|
|
9
|
+
*/
|
|
10
|
+
export declare function acquireSlot(signal: AbortSignal | undefined): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Hands the slot to the next waiter rather than freeing it and letting them
|
|
13
|
+
* race for it: decrementing first left `active` below the limit for a tick, so
|
|
14
|
+
* a fresh caller could take the slot the queue was already promised and the
|
|
15
|
+
* limiter would run over its own ceiling.
|
|
16
|
+
*/
|
|
17
|
+
export declare function releaseSlot(): void;
|
|
18
|
+
/** Test seam: lets the suite assert the limiter without spawning processes. */
|
|
19
|
+
export declare function concurrencyState(): {
|
|
20
|
+
active: number;
|
|
21
|
+
queued: number;
|
|
22
|
+
limit: number;
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=ytdlp-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ytdlp-limiter.d.ts","sourceRoot":"","sources":["../../src/utils/ytdlp-limiter.ts"],"names":[],"mappings":"AAqBA;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BhF;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAOlC;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,IAAI;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAEpF"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { envInt } from './env.js';
|
|
2
|
+
import { YouTubeError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* The concurrency limiter every yt-dlp spawn passes through.
|
|
5
|
+
*
|
|
6
|
+
* A cap so batch operations do not look like a scraper to YouTube, with a queue
|
|
7
|
+
* that honours cancellation so an abandoned request never holds a place in it.
|
|
8
|
+
*/
|
|
9
|
+
/** A limit of 0 (or NaN) would queue every call forever, so the floor is 1. */
|
|
10
|
+
const MAX_CONCURRENT = envInt('YOUTUBE_MCP_MAX_CONCURRENCY', 3, { min: 1, max: 32 });
|
|
11
|
+
let active = 0;
|
|
12
|
+
const waiting = [];
|
|
13
|
+
/**
|
|
14
|
+
* Takes a concurrency slot, waiting for one when the limiter is full.
|
|
15
|
+
*
|
|
16
|
+
* The wait honours the request's abort signal. It used to be a bare promise
|
|
17
|
+
* with no way out, so a client that gave up stayed queued forever — and since a
|
|
18
|
+
* media transfer runs without a wall-clock timeout, three stalled downloads
|
|
19
|
+
* held every slot and every later call queued behind them, uncancellable. The
|
|
20
|
+
* server was not slow, it was wedged, and no tool could run again.
|
|
21
|
+
*/
|
|
22
|
+
export async function acquireSlot(signal) {
|
|
23
|
+
// No already-aborted check here: `runYtDlp` makes it before every attempt and
|
|
24
|
+
// nothing awaits in between, so a second one could never fire — an
|
|
25
|
+
// unreachable guard that reads like a reachable one.
|
|
26
|
+
if (active < MAX_CONCURRENT) {
|
|
27
|
+
active++;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
await new Promise((resolve, reject) => {
|
|
31
|
+
const waiter = {
|
|
32
|
+
grant: () => {
|
|
33
|
+
signal?.removeEventListener('abort', onAbort);
|
|
34
|
+
resolve();
|
|
35
|
+
},
|
|
36
|
+
cancel: (error) => {
|
|
37
|
+
signal?.removeEventListener('abort', onAbort);
|
|
38
|
+
reject(error);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
function onAbort() {
|
|
42
|
+
const index = waiting.indexOf(waiter);
|
|
43
|
+
if (index !== -1)
|
|
44
|
+
waiting.splice(index, 1);
|
|
45
|
+
waiter.cancel(new YouTubeError('CANCELLED', 'The request was cancelled.'));
|
|
46
|
+
}
|
|
47
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
48
|
+
waiting.push(waiter);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Hands the slot to the next waiter rather than freeing it and letting them
|
|
53
|
+
* race for it: decrementing first left `active` below the limit for a tick, so
|
|
54
|
+
* a fresh caller could take the slot the queue was already promised and the
|
|
55
|
+
* limiter would run over its own ceiling.
|
|
56
|
+
*/
|
|
57
|
+
export function releaseSlot() {
|
|
58
|
+
const next = waiting.shift();
|
|
59
|
+
if (next) {
|
|
60
|
+
next.grant();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
active--;
|
|
64
|
+
}
|
|
65
|
+
/** Test seam: lets the suite assert the limiter without spawning processes. */
|
|
66
|
+
export function concurrencyState() {
|
|
67
|
+
return { active, queued: waiting.length, limit: MAX_CONCURRENT };
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=ytdlp-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ytdlp-limiter.js","sourceRoot":"","sources":["../../src/utils/ytdlp-limiter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;GAKG;AAEH,+EAA+E;AAC/E,MAAM,cAAc,GAAG,MAAM,CAAC,6BAA6B,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;AAOrF,IAAI,MAAM,GAAG,CAAC,CAAC;AACf,MAAM,OAAO,GAAa,EAAE,CAAC;AAE7B;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAA+B;IAC/D,8EAA8E;IAC9E,mEAAmE;IACnE,qDAAqD;IACrD,IAAI,MAAM,GAAG,cAAc,EAAE,CAAC;QAC5B,MAAM,EAAE,CAAC;QACT,OAAO;IACT,CAAC;IAED,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,MAAM,GAAW;YACrB,KAAK,EAAE,GAAG,EAAE;gBACV,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAC9C,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;gBAChB,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAC9C,MAAM,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC;SACF,CAAC;QAEF,SAAS,OAAO;YACd,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YACtC,IAAI,KAAK,KAAK,CAAC,CAAC;gBAAE,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YAC3C,MAAM,CAAC,MAAM,CAAC,IAAI,YAAY,CAAC,WAAW,EAAE,4BAA4B,CAAC,CAAC,CAAC;QAC7E,CAAC;QAED,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW;IACzB,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAC7B,IAAI,IAAI,EAAE,CAAC;QACT,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,OAAO;IACT,CAAC;IACD,MAAM,EAAE,CAAC;AACX,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,gBAAgB;IAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Parse yt-dlp JSON output against a schema.
|
|
4
|
+
*
|
|
5
|
+
* Every JSON.parse in this codebase used to be unguarded, so a truncated or
|
|
6
|
+
* non-JSON response surfaced as a raw SyntaxError; and the guard that replaced
|
|
7
|
+
* it only checked for an object, so a field of the wrong type still reached
|
|
8
|
+
* the code that read it.
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseYtDlpJson<T>(stdout: string, schema: z.ZodType<T, z.ZodTypeDef, unknown>, what: string): T;
|
|
11
|
+
/** Parse newline-delimited JSON, skipping rows that do not parse or do not match. */
|
|
12
|
+
export declare function parseYtDlpJsonLines<T>(stdout: string, schema: z.ZodType<T, z.ZodTypeDef, unknown>): T[];
|
|
13
|
+
//# sourceMappingURL=ytdlp-parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ytdlp-parse.d.ts","sourceRoot":"","sources":["../../src/utils/ytdlp-parse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAa7B;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAC9B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,EAAE,OAAO,CAAC,EAC3C,IAAI,EAAE,MAAM,GACX,CAAC,CAWH;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,EAAE,OAAO,CAAC,GAC1C,CAAC,EAAE,CAcL"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { YouTubeError } from './errors.js';
|
|
2
|
+
/** Reading yt-dlp's JSON output without trusting its shape. */
|
|
3
|
+
function outOfDate(what, detail, cause) {
|
|
4
|
+
return new YouTubeError('MALFORMED_RESPONSE', `yt-dlp returned ${detail} for ${what}.`, {
|
|
5
|
+
nextStep: 'This usually means yt-dlp is out of date. Run `yt-dlp -U` and try again.',
|
|
6
|
+
retryable: cause !== undefined,
|
|
7
|
+
cause,
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Parse yt-dlp JSON output against a schema.
|
|
12
|
+
*
|
|
13
|
+
* Every JSON.parse in this codebase used to be unguarded, so a truncated or
|
|
14
|
+
* non-JSON response surfaced as a raw SyntaxError; and the guard that replaced
|
|
15
|
+
* it only checked for an object, so a field of the wrong type still reached
|
|
16
|
+
* the code that read it.
|
|
17
|
+
*/
|
|
18
|
+
export function parseYtDlpJson(stdout, schema, what) {
|
|
19
|
+
let parsed;
|
|
20
|
+
try {
|
|
21
|
+
parsed = JSON.parse(stdout);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw outOfDate(what, 'unreadable output', error);
|
|
25
|
+
}
|
|
26
|
+
const result = schema.safeParse(parsed);
|
|
27
|
+
if (!result.success)
|
|
28
|
+
throw outOfDate(what, 'an unexpected shape');
|
|
29
|
+
return result.data;
|
|
30
|
+
}
|
|
31
|
+
/** Parse newline-delimited JSON, skipping rows that do not parse or do not match. */
|
|
32
|
+
export function parseYtDlpJsonLines(stdout, schema) {
|
|
33
|
+
const results = [];
|
|
34
|
+
for (const line of stdout.trim().split('\n')) {
|
|
35
|
+
if (!line.trim())
|
|
36
|
+
continue;
|
|
37
|
+
try {
|
|
38
|
+
const result = schema.safeParse(JSON.parse(line));
|
|
39
|
+
if (result.success)
|
|
40
|
+
results.push(result.data);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// A single malformed row should not lose the whole result set.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return results;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=ytdlp-parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ytdlp-parse.js","sourceRoot":"","sources":["../../src/utils/ytdlp-parse.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,+DAA+D;AAE/D,SAAS,SAAS,CAAC,IAAY,EAAE,MAAc,EAAE,KAAe;IAC9D,OAAO,IAAI,YAAY,CAAC,oBAAoB,EAAE,mBAAmB,MAAM,QAAQ,IAAI,GAAG,EAAE;QACtF,QAAQ,EAAE,0EAA0E;QACpF,SAAS,EAAE,KAAK,KAAK,SAAS;QAC9B,KAAK;KACN,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,MAAc,EACd,MAA2C,EAC3C,IAAY;IAEZ,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,SAAS,CAAC,IAAI,EAAE,mBAAmB,EAAE,KAAK,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,MAAM,SAAS,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC;IAClE,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,mBAAmB,CACjC,MAAc,EACd,MAA2C;IAE3C,MAAM,OAAO,GAAQ,EAAE,CAAC;IAExB,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,SAAS;QAC3B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YAClD,IAAI,MAAM,CAAC,OAAO;gBAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChD,CAAC;QAAC,MAAM,CAAC;YACP,+DAA+D;QACjE,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/dist/utils/ytdlp.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { concurrencyState } from './ytdlp-limiter.js';
|
|
2
|
+
export { parseYtDlpJson, parseYtDlpJsonLines } from './ytdlp-parse.js';
|
|
1
3
|
/**
|
|
2
4
|
* The single place this server spawns yt-dlp.
|
|
3
5
|
*
|
|
@@ -17,12 +19,6 @@ export declare const TIMEOUTS: {
|
|
|
17
19
|
/** Media transfers: unbounded here; cancellation is the client's lever. */
|
|
18
20
|
readonly download: 0;
|
|
19
21
|
};
|
|
20
|
-
/** Test seam: lets the suite assert the limiter without spawning processes. */
|
|
21
|
-
export declare function concurrencyState(): {
|
|
22
|
-
active: number;
|
|
23
|
-
queued: number;
|
|
24
|
-
limit: number;
|
|
25
|
-
};
|
|
26
22
|
export interface RunOptions {
|
|
27
23
|
/** Milliseconds before the child is killed. 0 disables the timeout. */
|
|
28
24
|
timeoutMs?: number;
|
|
@@ -30,22 +26,21 @@ export interface RunOptions {
|
|
|
30
26
|
retry?: boolean;
|
|
31
27
|
/** Human-readable operation name, used in log lines. */
|
|
32
28
|
label?: string;
|
|
29
|
+
/**
|
|
30
|
+
* The URL, search expression or id yt-dlp operates on.
|
|
31
|
+
*
|
|
32
|
+
* Always the last argument and always preceded by a literal `--`, so a value
|
|
33
|
+
* that begins with a dash is a target yt-dlp fails to fetch, never an option
|
|
34
|
+
* it obeys. Required rather than defaulted: a call site cannot forget it.
|
|
35
|
+
*/
|
|
36
|
+
target: string;
|
|
33
37
|
}
|
|
34
38
|
/**
|
|
35
39
|
* Run yt-dlp and return its stdout.
|
|
36
40
|
*
|
|
37
41
|
* Arguments are always passed as an array — never a shell string — so video
|
|
38
|
-
* titles and user-supplied queries cannot become shell metacharacters.
|
|
39
|
-
|
|
40
|
-
export declare function runYtDlp(args: string[], options?: RunOptions): Promise<string>;
|
|
41
|
-
/**
|
|
42
|
-
* Parse yt-dlp JSON output behind a type guard.
|
|
43
|
-
*
|
|
44
|
-
* Every JSON.parse in this codebase used to be unguarded, so a truncated or
|
|
45
|
-
* non-JSON response surfaced as a raw SyntaxError.
|
|
42
|
+
* titles and user-supplied queries cannot become shell metacharacters. The
|
|
43
|
+
* target follows a `--` terminator, so it cannot become a yt-dlp option either.
|
|
46
44
|
*/
|
|
47
|
-
export declare function
|
|
48
|
-
/** Parse newline-delimited JSON, skipping unparseable lines rather than failing the call. */
|
|
49
|
-
export declare function parseYtDlpJsonLines<T>(stdout: string, guard: (value: unknown) => value is T): T[];
|
|
50
|
-
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
45
|
+
export declare function runYtDlp(args: string[], options: RunOptions): Promise<string>;
|
|
51
46
|
//# sourceMappingURL=ytdlp.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ytdlp.d.ts","sourceRoot":"","sources":["../../src/utils/ytdlp.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ytdlp.d.ts","sourceRoot":"","sources":["../../src/utils/ytdlp.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEvE;;;;;;;GAOG;AAEH,kFAAkF;AAClF,eAAO,MAAM,QAAQ;IACnB,wDAAwD;;IAExD,gDAAgD;;IAEhD,iEAAiE;;IAEjE,2EAA2E;;CAEnE,CAAC;AAkBX,MAAM,WAAW,UAAU;IACzB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;OAMG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB;AAsBD;;;;;;GAMG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBnF"}
|
package/dist/utils/ytdlp.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { execa, ExecaError } from 'execa';
|
|
2
2
|
import { currentContext, log } from './context.js';
|
|
3
3
|
import { YouTubeError, classifyYtDlpFailure } from './errors.js';
|
|
4
|
+
import { acquireSlot, releaseSlot } from './ytdlp-limiter.js';
|
|
5
|
+
import { readYtDlpEnv } from './ytdlp-env.js';
|
|
6
|
+
export { concurrencyState } from './ytdlp-limiter.js';
|
|
7
|
+
export { parseYtDlpJson, parseYtDlpJsonLines } from './ytdlp-parse.js';
|
|
4
8
|
/**
|
|
5
9
|
* The single place this server spawns yt-dlp.
|
|
6
10
|
*
|
|
@@ -20,7 +24,6 @@ export const TIMEOUTS = {
|
|
|
20
24
|
/** Media transfers: unbounded here; cancellation is the client's lever. */
|
|
21
25
|
download: 0,
|
|
22
26
|
};
|
|
23
|
-
const MAX_CONCURRENT = Number(process.env.YOUTUBE_MCP_MAX_CONCURRENCY ?? '3');
|
|
24
27
|
const MAX_ATTEMPTS = 3;
|
|
25
28
|
const BASE_BACKOFF_MS = 750;
|
|
26
29
|
/** Guards against a pathological video description blowing up memory. */
|
|
@@ -35,64 +38,6 @@ const MAX_OUTPUT_BYTES = 64 * 1024 * 1024;
|
|
|
35
38
|
* progress is never interrupted.
|
|
36
39
|
*/
|
|
37
40
|
const SOCKET_TIMEOUT_S = '30';
|
|
38
|
-
let active = 0;
|
|
39
|
-
const waiting = [];
|
|
40
|
-
/**
|
|
41
|
-
* Takes a concurrency slot, waiting for one when the limiter is full.
|
|
42
|
-
*
|
|
43
|
-
* The wait honours the request's abort signal. It used to be a bare promise
|
|
44
|
-
* with no way out, so a client that gave up stayed queued forever — and since a
|
|
45
|
-
* media transfer runs without a wall-clock timeout, three stalled downloads
|
|
46
|
-
* held every slot and every later call queued behind them, uncancellable. The
|
|
47
|
-
* server was not slow, it was wedged, and no tool could run again.
|
|
48
|
-
*/
|
|
49
|
-
async function acquireSlot(signal) {
|
|
50
|
-
// No already-aborted check here: `runYtDlp` makes it before every attempt and
|
|
51
|
-
// nothing awaits in between, so a second one could never fire — an
|
|
52
|
-
// unreachable guard that reads like a reachable one.
|
|
53
|
-
if (active < MAX_CONCURRENT) {
|
|
54
|
-
active++;
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
await new Promise((resolve, reject) => {
|
|
58
|
-
const waiter = {
|
|
59
|
-
grant: () => {
|
|
60
|
-
signal?.removeEventListener('abort', onAbort);
|
|
61
|
-
resolve();
|
|
62
|
-
},
|
|
63
|
-
cancel: (error) => {
|
|
64
|
-
signal?.removeEventListener('abort', onAbort);
|
|
65
|
-
reject(error);
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
function onAbort() {
|
|
69
|
-
const index = waiting.indexOf(waiter);
|
|
70
|
-
if (index !== -1)
|
|
71
|
-
waiting.splice(index, 1);
|
|
72
|
-
waiter.cancel(new YouTubeError('CANCELLED', 'The request was cancelled.'));
|
|
73
|
-
}
|
|
74
|
-
signal?.addEventListener('abort', onAbort, { once: true });
|
|
75
|
-
waiting.push(waiter);
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Hands the slot to the next waiter rather than freeing it and letting them
|
|
80
|
-
* race for it: decrementing first left `active` below the limit for a tick, so
|
|
81
|
-
* a fresh caller could take the slot the queue was already promised and the
|
|
82
|
-
* limiter would run over its own ceiling.
|
|
83
|
-
*/
|
|
84
|
-
function releaseSlot() {
|
|
85
|
-
const next = waiting.shift();
|
|
86
|
-
if (next) {
|
|
87
|
-
next.grant();
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
active--;
|
|
91
|
-
}
|
|
92
|
-
/** Test seam: lets the suite assert the limiter without spawning processes. */
|
|
93
|
-
export function concurrencyState() {
|
|
94
|
-
return { active, queued: waiting.length, limit: MAX_CONCURRENT };
|
|
95
|
-
}
|
|
96
41
|
function backoffDelay(attempt) {
|
|
97
42
|
// Exponential with full jitter, so parallel callers do not retry in lockstep.
|
|
98
43
|
const ceiling = BASE_BACKOFF_MS * 2 ** (attempt - 1);
|
|
@@ -115,10 +60,11 @@ async function sleep(ms, signal) {
|
|
|
115
60
|
* Run yt-dlp and return its stdout.
|
|
116
61
|
*
|
|
117
62
|
* Arguments are always passed as an array — never a shell string — so video
|
|
118
|
-
* titles and user-supplied queries cannot become shell metacharacters.
|
|
63
|
+
* titles and user-supplied queries cannot become shell metacharacters. The
|
|
64
|
+
* target follows a `--` terminator, so it cannot become a yt-dlp option either.
|
|
119
65
|
*/
|
|
120
|
-
export async function runYtDlp(args, options
|
|
121
|
-
const { timeoutMs = TIMEOUTS.metadata, retry = true, label = 'yt-dlp' } = options;
|
|
66
|
+
export async function runYtDlp(args, options) {
|
|
67
|
+
const { timeoutMs = TIMEOUTS.metadata, retry = true, label = 'yt-dlp', target } = options;
|
|
122
68
|
const { signal } = currentContext();
|
|
123
69
|
const maxAttempts = retry ? MAX_ATTEMPTS : 1;
|
|
124
70
|
for (let attempt = 1;; attempt++) {
|
|
@@ -129,7 +75,7 @@ export async function runYtDlp(args, options = {}) {
|
|
|
129
75
|
throw new YouTubeError('CANCELLED', 'The request was cancelled.');
|
|
130
76
|
}
|
|
131
77
|
try {
|
|
132
|
-
return await spawnOnce(args, timeoutMs, signal);
|
|
78
|
+
return await spawnOnce(args, target, timeoutMs, signal);
|
|
133
79
|
}
|
|
134
80
|
catch (error) {
|
|
135
81
|
const failure = translateExecaFailure(error, timeoutMs);
|
|
@@ -143,10 +89,20 @@ export async function runYtDlp(args, options = {}) {
|
|
|
143
89
|
}
|
|
144
90
|
}
|
|
145
91
|
/** One attempt, holding a concurrency slot for exactly its duration. */
|
|
146
|
-
async function spawnOnce(args, timeoutMs, signal) {
|
|
92
|
+
async function spawnOnce(args, target, timeoutMs, signal) {
|
|
147
93
|
await acquireSlot(signal);
|
|
148
94
|
try {
|
|
149
|
-
|
|
95
|
+
// Session flags (cookies, proxy, pacing) go first so a caller's argument can
|
|
96
|
+
// never be read as their value; the target goes last, behind `--`.
|
|
97
|
+
const argv = [
|
|
98
|
+
'--socket-timeout',
|
|
99
|
+
SOCKET_TIMEOUT_S,
|
|
100
|
+
...readYtDlpEnv().args,
|
|
101
|
+
...args,
|
|
102
|
+
'--',
|
|
103
|
+
target,
|
|
104
|
+
];
|
|
105
|
+
const { stdout } = await execa('yt-dlp', argv, {
|
|
150
106
|
timeout: timeoutMs > 0 ? timeoutMs : undefined,
|
|
151
107
|
cancelSignal: signal,
|
|
152
108
|
maxBuffer: MAX_OUTPUT_BYTES,
|
|
@@ -193,49 +149,4 @@ function asText(value) {
|
|
|
193
149
|
}
|
|
194
150
|
return '';
|
|
195
151
|
}
|
|
196
|
-
/**
|
|
197
|
-
* Parse yt-dlp JSON output behind a type guard.
|
|
198
|
-
*
|
|
199
|
-
* Every JSON.parse in this codebase used to be unguarded, so a truncated or
|
|
200
|
-
* non-JSON response surfaced as a raw SyntaxError.
|
|
201
|
-
*/
|
|
202
|
-
export function parseYtDlpJson(stdout, guard, what) {
|
|
203
|
-
let parsed;
|
|
204
|
-
try {
|
|
205
|
-
parsed = JSON.parse(stdout);
|
|
206
|
-
}
|
|
207
|
-
catch (error) {
|
|
208
|
-
throw new YouTubeError('MALFORMED_RESPONSE', `yt-dlp returned unreadable output for ${what}.`, {
|
|
209
|
-
nextStep: 'This usually means yt-dlp is out of date. Run `yt-dlp -U` and try again.',
|
|
210
|
-
retryable: true,
|
|
211
|
-
cause: error,
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
if (!guard(parsed)) {
|
|
215
|
-
throw new YouTubeError('MALFORMED_RESPONSE', `yt-dlp returned an unexpected shape for ${what}.`, {
|
|
216
|
-
nextStep: 'This usually means yt-dlp is out of date. Run `yt-dlp -U` and try again.',
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
return parsed;
|
|
220
|
-
}
|
|
221
|
-
/** Parse newline-delimited JSON, skipping unparseable lines rather than failing the call. */
|
|
222
|
-
export function parseYtDlpJsonLines(stdout, guard) {
|
|
223
|
-
const results = [];
|
|
224
|
-
for (const line of stdout.trim().split('\n')) {
|
|
225
|
-
if (!line.trim())
|
|
226
|
-
continue;
|
|
227
|
-
try {
|
|
228
|
-
const parsed = JSON.parse(line);
|
|
229
|
-
if (guard(parsed))
|
|
230
|
-
results.push(parsed);
|
|
231
|
-
}
|
|
232
|
-
catch {
|
|
233
|
-
// A single malformed row should not lose the whole result set.
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return results;
|
|
237
|
-
}
|
|
238
|
-
export function isRecord(value) {
|
|
239
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
240
|
-
}
|
|
241
152
|
//# sourceMappingURL=ytdlp.js.map
|
package/dist/utils/ytdlp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ytdlp.js","sourceRoot":"","sources":["../../src/utils/ytdlp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"ytdlp.js","sourceRoot":"","sources":["../../src/utils/ytdlp.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "youtube-knowledge-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "MCP Server for extracting and managing YouTube video knowledge",
|
|
5
5
|
"author": "teobouancheau",
|
|
6
6
|
"repository": {
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"test": "vitest run",
|
|
41
41
|
"test:watch": "vitest",
|
|
42
42
|
"test:coverage": "vitest run --coverage",
|
|
43
|
+
"test:e2e": "npm run build && vitest run -c vitest.e2e.config.ts",
|
|
43
44
|
"typecheck": "tsc --noEmit",
|
|
44
45
|
"validate": "npm run typecheck && npm run lint && npm run format:check && npm run test:coverage",
|
|
45
46
|
"prepare": "husky",
|