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.
Files changed (344) hide show
  1. package/README.md +105 -34
  2. package/dist/brain-schemas.d.ts +10 -10
  3. package/dist/brain-schemas.d.ts.map +1 -1
  4. package/dist/brain-schemas.js +0 -4
  5. package/dist/brain-schemas.js.map +1 -1
  6. package/dist/http/app.d.ts +16 -0
  7. package/dist/http/app.d.ts.map +1 -0
  8. package/dist/http/app.js +176 -0
  9. package/dist/http/app.js.map +1 -0
  10. package/dist/http/auth.d.ts +38 -0
  11. package/dist/http/auth.d.ts.map +1 -0
  12. package/dist/http/auth.js +79 -0
  13. package/dist/http/auth.js.map +1 -0
  14. package/dist/http/config.d.ts +41 -0
  15. package/dist/http/config.d.ts.map +1 -0
  16. package/dist/http/config.js +65 -0
  17. package/dist/http/config.js.map +1 -0
  18. package/dist/http/rate-limiter.d.ts +24 -0
  19. package/dist/http/rate-limiter.d.ts.map +1 -0
  20. package/dist/http/rate-limiter.js +55 -0
  21. package/dist/http/rate-limiter.js.map +1 -0
  22. package/dist/http/sessions.d.ts +34 -0
  23. package/dist/http/sessions.d.ts.map +1 -0
  24. package/dist/http/sessions.js +62 -0
  25. package/dist/http/sessions.js.map +1 -0
  26. package/dist/http.d.ts +6 -48
  27. package/dist/http.d.ts.map +1 -1
  28. package/dist/http.js +35 -252
  29. package/dist/http.js.map +1 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +11 -517
  32. package/dist/index.js.map +1 -1
  33. package/dist/prompts-brain.d.ts +4 -0
  34. package/dist/prompts-brain.d.ts.map +1 -0
  35. package/dist/prompts-brain.js +48 -0
  36. package/dist/prompts-brain.js.map +1 -0
  37. package/dist/prompts-research.d.ts +4 -0
  38. package/dist/prompts-research.d.ts.map +1 -0
  39. package/dist/prompts-research.js +74 -0
  40. package/dist/prompts-research.js.map +1 -0
  41. package/dist/prompts-shared.d.ts +13 -0
  42. package/dist/prompts-shared.d.ts.map +1 -0
  43. package/dist/prompts-shared.js +7 -0
  44. package/dist/prompts-shared.js.map +1 -0
  45. package/dist/prompts.d.ts.map +1 -1
  46. package/dist/prompts.js +5 -100
  47. package/dist/prompts.js.map +1 -1
  48. package/dist/registry/brain-tools.d.ts +4 -0
  49. package/dist/registry/brain-tools.d.ts.map +1 -0
  50. package/dist/registry/brain-tools.js +104 -0
  51. package/dist/registry/brain-tools.js.map +1 -0
  52. package/dist/registry/channel-tools.d.ts +4 -0
  53. package/dist/registry/channel-tools.d.ts.map +1 -0
  54. package/dist/registry/channel-tools.js +53 -0
  55. package/dist/registry/channel-tools.js.map +1 -0
  56. package/dist/registry/health-tools.d.ts +4 -0
  57. package/dist/registry/health-tools.d.ts.map +1 -0
  58. package/dist/registry/health-tools.js +21 -0
  59. package/dist/registry/health-tools.js.map +1 -0
  60. package/dist/registry/index.d.ts +8 -0
  61. package/dist/registry/index.d.ts.map +1 -0
  62. package/dist/registry/index.js +27 -0
  63. package/dist/registry/index.js.map +1 -0
  64. package/dist/registry/library-tools.d.ts +4 -0
  65. package/dist/registry/library-tools.d.ts.map +1 -0
  66. package/dist/registry/library-tools.js +116 -0
  67. package/dist/registry/library-tools.js.map +1 -0
  68. package/dist/registry/media-tools.d.ts +4 -0
  69. package/dist/registry/media-tools.d.ts.map +1 -0
  70. package/dist/registry/media-tools.js +100 -0
  71. package/dist/registry/media-tools.js.map +1 -0
  72. package/dist/registry/thumbnail-tools.d.ts +7 -0
  73. package/dist/registry/thumbnail-tools.d.ts.map +1 -0
  74. package/dist/registry/thumbnail-tools.js +72 -0
  75. package/dist/registry/thumbnail-tools.js.map +1 -0
  76. package/dist/registry/transcript-tools.d.ts +4 -0
  77. package/dist/registry/transcript-tools.d.ts.map +1 -0
  78. package/dist/registry/transcript-tools.js +68 -0
  79. package/dist/registry/transcript-tools.js.map +1 -0
  80. package/dist/registry/types.d.ts +41 -0
  81. package/dist/registry/types.d.ts.map +1 -0
  82. package/dist/registry/types.js +17 -0
  83. package/dist/registry/types.js.map +1 -0
  84. package/dist/registry/video-tools.d.ts +4 -0
  85. package/dist/registry/video-tools.d.ts.map +1 -0
  86. package/dist/registry/video-tools.js +101 -0
  87. package/dist/registry/video-tools.js.map +1 -0
  88. package/dist/resources.d.ts.map +1 -1
  89. package/dist/resources.js +13 -10
  90. package/dist/resources.js.map +1 -1
  91. package/dist/schemas.d.ts +11 -2
  92. package/dist/schemas.d.ts.map +1 -1
  93. package/dist/schemas.js +5 -0
  94. package/dist/schemas.js.map +1 -1
  95. package/dist/thumbnail-resource.d.ts +8 -0
  96. package/dist/thumbnail-resource.d.ts.map +1 -0
  97. package/dist/thumbnail-resource.js +38 -0
  98. package/dist/thumbnail-resource.js.map +1 -0
  99. package/dist/thumbnail-schemas.d.ts +512 -0
  100. package/dist/thumbnail-schemas.d.ts.map +1 -0
  101. package/dist/thumbnail-schemas.js +95 -0
  102. package/dist/thumbnail-schemas.js.map +1 -0
  103. package/dist/tools/batch.js +1 -1
  104. package/dist/tools/batch.js.map +1 -1
  105. package/dist/tools/build-brain.js +1 -1
  106. package/dist/tools/build-brain.js.map +1 -1
  107. package/dist/tools/check-health.d.ts +2 -0
  108. package/dist/tools/check-health.d.ts.map +1 -1
  109. package/dist/tools/check-health.js +9 -0
  110. package/dist/tools/check-health.js.map +1 -1
  111. package/dist/tools/clips.d.ts +9 -41
  112. package/dist/tools/clips.d.ts.map +1 -1
  113. package/dist/tools/clips.js +52 -128
  114. package/dist/tools/clips.js.map +1 -1
  115. package/dist/tools/fetch-channel-thumbnails.d.ts +179 -0
  116. package/dist/tools/fetch-channel-thumbnails.d.ts.map +1 -0
  117. package/dist/tools/fetch-channel-thumbnails.js +123 -0
  118. package/dist/tools/fetch-channel-thumbnails.js.map +1 -0
  119. package/dist/tools/fetch-videos.d.ts +3 -0
  120. package/dist/tools/fetch-videos.d.ts.map +1 -1
  121. package/dist/tools/fetch-videos.js +3 -0
  122. package/dist/tools/fetch-videos.js.map +1 -1
  123. package/dist/tools/frames-subtitles.d.ts +44 -0
  124. package/dist/tools/frames-subtitles.d.ts.map +1 -0
  125. package/dist/tools/frames-subtitles.js +104 -0
  126. package/dist/tools/frames-subtitles.js.map +1 -0
  127. package/dist/tools/get-channel-info.d.ts +2 -0
  128. package/dist/tools/get-channel-info.d.ts.map +1 -1
  129. package/dist/tools/get-channel-info.js +8 -1
  130. package/dist/tools/get-channel-info.js.map +1 -1
  131. package/dist/tools/get-playlist-info.d.ts.map +1 -1
  132. package/dist/tools/get-playlist-info.js +1 -0
  133. package/dist/tools/get-playlist-info.js.map +1 -1
  134. package/dist/tools/get-thumbnail.d.ts +38 -0
  135. package/dist/tools/get-thumbnail.d.ts.map +1 -0
  136. package/dist/tools/get-thumbnail.js +152 -0
  137. package/dist/tools/get-thumbnail.js.map +1 -0
  138. package/dist/tools/get-transcript.d.ts.map +1 -1
  139. package/dist/tools/get-transcript.js +2 -14
  140. package/dist/tools/get-transcript.js.map +1 -1
  141. package/dist/tools/library.d.ts.map +1 -1
  142. package/dist/tools/library.js +13 -3
  143. package/dist/tools/library.js.map +1 -1
  144. package/dist/tools/list-channel-thumbnails.d.ts +235 -0
  145. package/dist/tools/list-channel-thumbnails.d.ts.map +1 -0
  146. package/dist/tools/list-channel-thumbnails.js +79 -0
  147. package/dist/tools/list-channel-thumbnails.js.map +1 -0
  148. package/dist/tools/save-to-library.d.ts.map +1 -1
  149. package/dist/tools/save-to-library.js +5 -1
  150. package/dist/tools/save-to-library.js.map +1 -1
  151. package/dist/tools/search-channels.d.ts +6 -0
  152. package/dist/tools/search-channels.d.ts.map +1 -1
  153. package/dist/tools/search-transcript.d.ts.map +1 -1
  154. package/dist/tools/search-transcript.js +6 -13
  155. package/dist/tools/search-transcript.js.map +1 -1
  156. package/dist/tools/search-videos.d.ts +3 -0
  157. package/dist/tools/search-videos.d.ts.map +1 -1
  158. package/dist/utils/batches.d.ts +15 -0
  159. package/dist/utils/batches.d.ts.map +1 -0
  160. package/dist/utils/batches.js +31 -0
  161. package/dist/utils/batches.js.map +1 -0
  162. package/dist/utils/brain-build.d.ts.map +1 -1
  163. package/dist/utils/brain-build.js +1 -12
  164. package/dist/utils/brain-build.js.map +1 -1
  165. package/dist/utils/brain-index.d.ts.map +1 -1
  166. package/dist/utils/brain-index.js +3 -2
  167. package/dist/utils/brain-index.js.map +1 -1
  168. package/dist/utils/brain-lock.d.ts +2 -18
  169. package/dist/utils/brain-lock.d.ts.map +1 -1
  170. package/dist/utils/brain-lock.js +4 -80
  171. package/dist/utils/brain-lock.js.map +1 -1
  172. package/dist/utils/brain-lookup.d.ts +8 -0
  173. package/dist/utils/brain-lookup.d.ts.map +1 -1
  174. package/dist/utils/brain-lookup.js +5 -31
  175. package/dist/utils/brain-lookup.js.map +1 -1
  176. package/dist/utils/brain-storage.d.ts.map +1 -1
  177. package/dist/utils/brain-storage.js +5 -5
  178. package/dist/utils/brain-storage.js.map +1 -1
  179. package/dist/utils/caption-probe.d.ts +7 -0
  180. package/dist/utils/caption-probe.d.ts.map +1 -0
  181. package/dist/utils/caption-probe.js +36 -0
  182. package/dist/utils/caption-probe.js.map +1 -0
  183. package/dist/utils/channel-images.d.ts +16 -0
  184. package/dist/utils/channel-images.d.ts.map +1 -0
  185. package/dist/utils/channel-images.js +35 -0
  186. package/dist/utils/channel-images.js.map +1 -0
  187. package/dist/utils/channel-lookup.d.ts +5 -0
  188. package/dist/utils/channel-lookup.d.ts.map +1 -0
  189. package/dist/utils/channel-lookup.js +37 -0
  190. package/dist/utils/channel-lookup.js.map +1 -0
  191. package/dist/utils/chapters.d.ts +11 -0
  192. package/dist/utils/chapters.d.ts.map +1 -0
  193. package/dist/utils/chapters.js +24 -0
  194. package/dist/utils/chapters.js.map +1 -0
  195. package/dist/utils/clips.d.ts.map +1 -1
  196. package/dist/utils/clips.js +18 -26
  197. package/dist/utils/clips.js.map +1 -1
  198. package/dist/utils/env.d.ts +26 -0
  199. package/dist/utils/env.d.ts.map +1 -0
  200. package/dist/utils/env.js +72 -0
  201. package/dist/utils/env.js.map +1 -0
  202. package/dist/utils/errors.d.ts +1 -1
  203. package/dist/utils/errors.d.ts.map +1 -1
  204. package/dist/utils/errors.js +20 -2
  205. package/dist/utils/errors.js.map +1 -1
  206. package/dist/utils/file-lock.d.ts +34 -0
  207. package/dist/utils/file-lock.d.ts.map +1 -0
  208. package/dist/utils/file-lock.js +91 -0
  209. package/dist/utils/file-lock.js.map +1 -0
  210. package/dist/utils/flat-listing.d.ts +89 -0
  211. package/dist/utils/flat-listing.d.ts.map +1 -0
  212. package/dist/utils/flat-listing.js +68 -0
  213. package/dist/utils/flat-listing.js.map +1 -0
  214. package/dist/utils/format.d.ts +1 -3
  215. package/dist/utils/format.d.ts.map +1 -1
  216. package/dist/utils/format.js +1 -5
  217. package/dist/utils/format.js.map +1 -1
  218. package/dist/utils/guard.d.ts +21 -0
  219. package/dist/utils/guard.d.ts.map +1 -0
  220. package/dist/utils/guard.js +105 -0
  221. package/dist/utils/guard.js.map +1 -0
  222. package/dist/utils/image-dimensions.d.ts +19 -0
  223. package/dist/utils/image-dimensions.d.ts.map +1 -0
  224. package/dist/utils/image-dimensions.js +93 -0
  225. package/dist/utils/image-dimensions.js.map +1 -0
  226. package/dist/utils/image-fetch.d.ts +31 -0
  227. package/dist/utils/image-fetch.d.ts.map +1 -0
  228. package/dist/utils/image-fetch.js +141 -0
  229. package/dist/utils/image-fetch.js.map +1 -0
  230. package/dist/utils/json-file.d.ts +14 -5
  231. package/dist/utils/json-file.d.ts.map +1 -1
  232. package/dist/utils/json-file.js +24 -9
  233. package/dist/utils/json-file.js.map +1 -1
  234. package/dist/utils/paths.d.ts +10 -0
  235. package/dist/utils/paths.d.ts.map +1 -1
  236. package/dist/utils/paths.js +14 -0
  237. package/dist/utils/paths.js.map +1 -1
  238. package/dist/utils/pattern.d.ts +30 -0
  239. package/dist/utils/pattern.d.ts.map +1 -0
  240. package/dist/utils/pattern.js +130 -0
  241. package/dist/utils/pattern.js.map +1 -0
  242. package/dist/utils/search-index.d.ts.map +1 -1
  243. package/dist/utils/search-index.js +9 -4
  244. package/dist/utils/search-index.js.map +1 -1
  245. package/dist/utils/storage.d.ts.map +1 -1
  246. package/dist/utils/storage.js +14 -8
  247. package/dist/utils/storage.js.map +1 -1
  248. package/dist/utils/subtitles.d.ts +7 -0
  249. package/dist/utils/subtitles.d.ts.map +1 -0
  250. package/dist/utils/subtitles.js +30 -0
  251. package/dist/utils/subtitles.js.map +1 -0
  252. package/dist/utils/thumbnail-entry.d.ts +19 -0
  253. package/dist/utils/thumbnail-entry.d.ts.map +1 -0
  254. package/dist/utils/thumbnail-entry.js +119 -0
  255. package/dist/utils/thumbnail-entry.js.map +1 -0
  256. package/dist/utils/thumbnail-fetch.d.ts +37 -0
  257. package/dist/utils/thumbnail-fetch.d.ts.map +1 -0
  258. package/dist/utils/thumbnail-fetch.js +89 -0
  259. package/dist/utils/thumbnail-fetch.js.map +1 -0
  260. package/dist/utils/thumbnail-ladder.d.ts +44 -0
  261. package/dist/utils/thumbnail-ladder.d.ts.map +1 -0
  262. package/dist/utils/thumbnail-ladder.js +68 -0
  263. package/dist/utils/thumbnail-ladder.js.map +1 -0
  264. package/dist/utils/thumbnail-listing.d.ts +16 -0
  265. package/dist/utils/thumbnail-listing.d.ts.map +1 -0
  266. package/dist/utils/thumbnail-listing.js +26 -0
  267. package/dist/utils/thumbnail-listing.js.map +1 -0
  268. package/dist/utils/thumbnail-paths.d.ts +20 -0
  269. package/dist/utils/thumbnail-paths.d.ts.map +1 -0
  270. package/dist/utils/thumbnail-paths.js +35 -0
  271. package/dist/utils/thumbnail-paths.js.map +1 -0
  272. package/dist/utils/thumbnail-store.d.ts +19 -0
  273. package/dist/utils/thumbnail-store.d.ts.map +1 -0
  274. package/dist/utils/thumbnail-store.js +125 -0
  275. package/dist/utils/thumbnail-store.js.map +1 -0
  276. package/dist/utils/transcript-cache.d.ts +20 -0
  277. package/dist/utils/transcript-cache.d.ts.map +1 -0
  278. package/dist/utils/transcript-cache.js +127 -0
  279. package/dist/utils/transcript-cache.js.map +1 -0
  280. package/dist/utils/transcript-search.d.ts +20 -0
  281. package/dist/utils/transcript-search.d.ts.map +1 -0
  282. package/dist/utils/transcript-search.js +35 -0
  283. package/dist/utils/transcript-search.js.map +1 -0
  284. package/dist/utils/transcript.d.ts +10 -31
  285. package/dist/utils/transcript.d.ts.map +1 -1
  286. package/dist/utils/transcript.js +2 -64
  287. package/dist/utils/transcript.js.map +1 -1
  288. package/dist/utils/validate-youtube.d.ts +35 -0
  289. package/dist/utils/validate-youtube.d.ts.map +1 -0
  290. package/dist/utils/validate-youtube.js +87 -0
  291. package/dist/utils/validate-youtube.js.map +1 -0
  292. package/dist/utils/validate.d.ts +11 -4
  293. package/dist/utils/validate.d.ts.map +1 -1
  294. package/dist/utils/validate.js +43 -36
  295. package/dist/utils/validate.js.map +1 -1
  296. package/dist/utils/version.d.ts +2 -0
  297. package/dist/utils/version.d.ts.map +1 -0
  298. package/dist/utils/version.js +19 -0
  299. package/dist/utils/version.js.map +1 -0
  300. package/dist/utils/youtube-channel.d.ts +44 -0
  301. package/dist/utils/youtube-channel.d.ts.map +1 -0
  302. package/dist/utils/youtube-channel.js +61 -0
  303. package/dist/utils/youtube-channel.js.map +1 -0
  304. package/dist/utils/youtube-download.d.ts +27 -0
  305. package/dist/utils/youtube-download.d.ts.map +1 -0
  306. package/dist/utils/youtube-download.js +118 -0
  307. package/dist/utils/youtube-download.js.map +1 -0
  308. package/dist/utils/youtube-schemas.d.ts +394 -0
  309. package/dist/utils/youtube-schemas.d.ts.map +1 -0
  310. package/dist/utils/youtube-schemas.js +118 -0
  311. package/dist/utils/youtube-schemas.js.map +1 -0
  312. package/dist/utils/youtube-search.d.ts +14 -0
  313. package/dist/utils/youtube-search.d.ts.map +1 -0
  314. package/dist/utils/youtube-search.js +39 -0
  315. package/dist/utils/youtube-search.js.map +1 -0
  316. package/dist/utils/youtube-url.d.ts +25 -0
  317. package/dist/utils/youtube-url.d.ts.map +1 -0
  318. package/dist/utils/youtube-url.js +75 -0
  319. package/dist/utils/youtube-url.js.map +1 -0
  320. package/dist/utils/youtube-video.d.ts +47 -0
  321. package/dist/utils/youtube-video.d.ts.map +1 -0
  322. package/dist/utils/youtube-video.js +98 -0
  323. package/dist/utils/youtube-video.js.map +1 -0
  324. package/dist/utils/youtube.d.ts +10 -122
  325. package/dist/utils/youtube.d.ts.map +1 -1
  326. package/dist/utils/youtube.js +11 -486
  327. package/dist/utils/youtube.js.map +1 -1
  328. package/dist/utils/ytdlp-env.d.ts +33 -0
  329. package/dist/utils/ytdlp-env.d.ts.map +1 -0
  330. package/dist/utils/ytdlp-env.js +121 -0
  331. package/dist/utils/ytdlp-env.js.map +1 -0
  332. package/dist/utils/ytdlp-limiter.d.ts +24 -0
  333. package/dist/utils/ytdlp-limiter.d.ts.map +1 -0
  334. package/dist/utils/ytdlp-limiter.js +69 -0
  335. package/dist/utils/ytdlp-limiter.js.map +1 -0
  336. package/dist/utils/ytdlp-parse.d.ts +13 -0
  337. package/dist/utils/ytdlp-parse.d.ts.map +1 -0
  338. package/dist/utils/ytdlp-parse.js +48 -0
  339. package/dist/utils/ytdlp-parse.js.map +1 -0
  340. package/dist/utils/ytdlp.d.ts +13 -18
  341. package/dist/utils/ytdlp.d.ts.map +1 -1
  342. package/dist/utils/ytdlp.js +21 -110
  343. package/dist/utils/ytdlp.js.map +1 -1
  344. package/package.json +2 -1
package/README.md CHANGED
@@ -18,6 +18,7 @@ Supports both **local** (stdio) and **remote** (Streamable HTTP) transports.
18
18
  - **Search** videos and channels by keyword
19
19
  - **Fetch** videos from a playlist or channel
20
20
  - **Video, channel and playlist metadata**, chapters, and top comments
21
+ - **Thumbnails**: look at any video's thumbnail or a channel's avatar or banner as an image, or save every thumbnail of a channel to disk
21
22
  - **Transcripts with timestamps**, sliced by time range or chapter, and capped so a
22
23
  three-hour video cannot flood your context
23
24
  - **Search inside a transcript** and get back `?t=` links that open the video at
@@ -50,6 +51,10 @@ Supports both **local** (stdio) and **remote** (Streamable HTTP) transports.
50
51
  speaking rate, and the phrases it repeats across videos
51
52
  - **Keep a written profile** beside the corpus, grounded in passages you can cite
52
53
 
54
+ **Study a channel's thumbnails** (local mode)
55
+
56
+ - **Save every thumbnail** of a channel, plus its avatar and banner, resumably, at the largest size YouTube serves, with each image's real pixel size recorded
57
+
53
58
  **Built to stay working**
54
59
 
55
60
  - WebVTT parsed by the W3C reference implementation, not a hand-written matcher
@@ -57,7 +62,9 @@ Supports both **local** (stdio) and **remote** (Streamable HTTP) transports.
57
62
  wall of yt-dlp stderr
58
63
  - Timeouts, retry with backoff, and a concurrency limit on every yt-dlp call
59
64
  - `check_health` diagnoses missing or outdated yt-dlp and ffmpeg
65
+ - Bearer auth, a loopback default bind, an allowlist on every URL and image host, and typed error codes on every failure
60
66
  - Structured output on every tool, plus MCP resources, prompts and completions
67
+ - An end-to-end suite runs the built server against real yt-dlp over both transports every week
61
68
 
62
69
  ## Prerequisites
63
70
 
@@ -154,7 +161,11 @@ npm run build
154
161
  npm run start:http
155
162
  ```
156
163
 
157
- The server listens on `PORT` (default 3000). Set `PORT` environment variable to change.
164
+ The server listens on `127.0.0.1:PORT` (default 3000). To reach it from another
165
+ machine set `MCP_BIND_HOST=0.0.0.0` together with `MCP_AUTH_TOKEN`; the server
166
+ refuses to start exposed and unauthenticated unless you also set
167
+ `MCP_ALLOW_UNAUTHENTICATED=true`. Behind a reverse proxy, set `MCP_TRUST_PROXY`
168
+ to the number of hops and `MCP_PUBLIC_URL` to the address clients use.
158
169
 
159
170
  #### Docker
160
171
 
@@ -210,7 +221,7 @@ a cold start. Any paid plan removes that.
210
221
 
211
222
  ## MCP Tools
212
223
 
213
- 33 tools. The 14 read-only ones work over both transports; the 19 that touch
224
+ 37 tools. The 15 read-only ones work over both transports; the 22 that touch
214
225
  your filesystem are registered only in local (stdio) mode, so a remote
215
226
  deployment cannot reach the host's disk.
216
227
 
@@ -220,18 +231,19 @@ available for this video. Call get_transcript again with one of: fr, es, de.`
220
231
 
221
232
  ### Discovery — remote + local
222
233
 
223
- | Tool | Key parameters | Returns |
224
- | ------------------- | ---------------- | ---------------------------------------------------------------- |
225
- | `search_videos` | `query`, `limit` | Matching videos with durations, channels and view counts |
226
- | `search_channels` | `query`, `limit` | Matching channels with subscriber counts |
227
- | `fetch_videos` | `url`, `limit` | Videos in a playlist or channel |
228
- | `get_video_info` | `video` | Title, channel, duration, views, likes, description, tags |
229
- | `get_channel_info` | `channel` | Name, handle, subscriber count, description |
230
- | `get_playlist_info` | `url` | Title, channel, video count, last updated |
231
- | `get_chapters` | `video` | Chapter titles with start/end times and deep links |
232
- | `get_comments` | `video`, `limit` | Top-level comments by popularity |
233
- | `list_formats` | `video` | Available formats grouped by video+audio, video-only, audio-only |
234
- | `check_health` | — | yt-dlp and ffmpeg status, versions, and staleness warnings |
234
+ | Tool | Key parameters | Returns |
235
+ | ------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------- |
236
+ | `search_videos` | `query`, `limit` | Matching videos with durations, channels and view counts |
237
+ | `search_channels` | `query`, `limit` | Matching channels with subscriber counts |
238
+ | `fetch_videos` | `url`, `limit` | Videos in a playlist or channel, with thumbnail URLs |
239
+ | `get_video_info` | `video` | Title, channel, duration, views, likes, description, tags |
240
+ | `get_channel_info` | `channel` | Name, handle, subscriber count, description, avatar and banner |
241
+ | `get_playlist_info` | `url` | Title, channel, video count, last updated |
242
+ | `get_chapters` | `video` | Chapter titles with start/end times and deep links |
243
+ | `get_comments` | `video`, `limit` | Top-level comments by popularity |
244
+ | `list_formats` | `video` | Available formats grouped by video+audio, video-only, audio-only |
245
+ | `get_thumbnail` | `video`, `channel`, `image`, `quality` | A video thumbnail, channel avatar or banner as an image, with its real size |
246
+ | `check_health` | — | yt-dlp and ffmpeg status, versions, staleness warnings, and which session options are set |
235
247
 
236
248
  ### Transcripts — remote + local
237
249
 
@@ -289,6 +301,24 @@ keyframe-aligned cut. All of these require ffmpeg.
289
301
  channel from what is already on disk, so they work offline and cost nothing to
290
302
  call.
291
303
 
304
+ ### Channel thumbnails — local only
305
+
306
+ | Tool | Key parameters | Returns |
307
+ | --------------------------- | -------------------------------------------- | --------------------------------------------------------- |
308
+ | `fetch_channel_thumbnails` | `channel`, `maxVideos`, `tabs`, `quality` | Every thumbnail saved to disk, plus the avatar and banner |
309
+ | `list_channel_thumbnails` | `channel`, `tab`, `state`, `limit`, `offset` | Saved entries with paths and decoded sizes |
310
+ | `delete_channel_thumbnails` | `channel` | What was removed |
311
+
312
+ One yt-dlp listing per tab, then the images from YouTube's image hosts
313
+ directly, so a five-hundred-video channel costs a handful of listings and not
314
+ five hundred extractions. For each video the fetch tries the largest image
315
+ YouTube serves (`maxresdefault`, 1280 wide where it exists) and keeps it only if
316
+ it decodes wider than the image the listing offered; otherwise it keeps the
317
+ listed image, and failing that the small `hqdefault`. Every width and height in
318
+ the manifest was decoded from the saved bytes, never read off a URL. Shorts are
319
+ listed with portrait thumbnails and saved under `shorts/`. The run is resumable:
320
+ interrupt it and call again, and only what is missing or truncated is fetched.
321
+
292
322
  A brain holds one caption language; pass `language` to read another, and build a
293
323
  separate brain per language.
294
324
 
@@ -309,16 +339,18 @@ skipped forever as already done.
309
339
 
310
340
  Reusable workflows your client can invoke directly: `summarize_video`,
311
341
  `extract_skill`, `compare_videos`, `research_topic`, `channel_deep_dive`,
312
- `clip_from_quote` (find a phrase, then cut the clip around it), and — local
313
- only — `review_library`, `create_brain` (build a channel's corpus, then write
314
- its profile from it) and `ask_creator` (answer a question strictly from a
315
- brain, with citations).
342
+ `clip_from_quote` (find a phrase, then cut the clip around it),
343
+ `study_thumbnails` (save a channel's thumbnails, look at a sample, describe
344
+ what recurs), and — local only — `review_library`, `create_brain` (build a
345
+ channel's corpus, then write its profile from it) and `ask_creator` (answer a
346
+ question strictly from a brain, with citations).
316
347
 
317
348
  ## Resources
318
349
 
319
350
  - `youtube://transcript/{videoId}` — a timestamped transcript, fetched and cached on first read
320
351
  - `youtube://library/{videoId}/{summary|skill}` — a saved note (local only, and enumerable)
321
352
  - `youtube://brain/{channelId}/{manifest|profile}` — what a channel brain covers, or the profile written from it (local only, and enumerable)
353
+ - `youtube://thumbnails/{channelId}/manifest` — what `fetch_channel_thumbnails` saved for a channel (local only, and enumerable)
322
354
 
323
355
  ## Error codes
324
356
 
@@ -334,25 +366,50 @@ them, each prefixed with a code and followed by a next step.
334
366
  | `RATE_LIMITED`, `TIMEOUT` | Transient; retried automatically with backoff before surfacing |
335
367
  | `YTDLP_MISSING`, `FFMPEG_MISSING`, `YTDLP_FAILED` | A tooling problem; the message says how to fix it |
336
368
  | `INVALID_INPUT` | A bad argument, caught before any network call |
369
+ | `BOT_CHECK` | YouTube asked this address to sign in; set the cookie settings below |
370
+ | `FETCH_FAILED` | An image could not be downloaded from YouTube's image hosts |
337
371
  | `CANCELLED` | The client cancelled the request |
338
372
 
339
373
  ## Environment variables
340
374
 
341
375
  All optional.
342
376
 
343
- | Variable | Default | Purpose |
344
- | ------------------------------- | --------- | -------------------------------------------------------------------------------------------------------- |
345
- | `MCP_AUTH_TOKEN` | unset | Require this bearer token on the HTTP transport. **Set this if you expose the server beyond localhost.** |
346
- | `MCP_ALLOWED_HOSTS` | unset | Comma-separated Host allowlist; enables DNS-rebinding protection |
347
- | `MCP_ALLOWED_ORIGINS` | unset | Comma-separated Origin allowlist |
348
- | `MCP_BIND_HOST` | `0.0.0.0` | Interface to bind |
349
- | `PORT` | `3000` | HTTP port. The Docker image sets `10000`; Render and similar platforms inject their own |
350
- | `MCP_RATE_LIMIT` | `60` | Requests per window, per client |
351
- | `MCP_RATE_WINDOW_MS` | `60000` | Rate-limit window |
352
- | `MCP_SESSION_IDLE_MS` | `1800000` | Close HTTP sessions idle this long |
353
- | `MCP_MAX_SESSIONS` | `1000` | Reject new sessions past this many |
354
- | `YOUTUBE_MCP_MAX_CONCURRENCY` | `3` | Concurrent yt-dlp processes |
355
- | `YOUTUBE_MCP_TRANSCRIPT_TTL_MS` | 30 days | Transcript cache lifetime |
377
+ | Variable | Default | Purpose |
378
+ | ---------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
379
+ | `MCP_AUTH_TOKEN` | unset | Require this bearer token on the HTTP transport. **Set this if you expose the server beyond localhost.** |
380
+ | `MCP_ALLOWED_HOSTS` | unset | Comma-separated Host allowlist; enables DNS-rebinding protection |
381
+ | `MCP_ALLOWED_ORIGINS` | unset | Comma-separated Origin allowlist |
382
+ | `MCP_BIND_HOST` | `127.0.0.1` | Interface to bind. The Docker image sets `0.0.0.0`. A non-loopback bind refuses to start without `MCP_AUTH_TOKEN` unless `MCP_ALLOW_UNAUTHENTICATED=true` |
383
+ | `MCP_ALLOW_UNAUTHENTICATED` | `false` | Explicit consent to listen on a network interface with no token |
384
+ | `MCP_TRUST_PROXY` | `false` | `true`, `false`, or the number of reverse-proxy hops whose `X-Forwarded-*` headers to believe. Off, forwarded headers are ignored for rate limiting and URLs |
385
+ | `MCP_PUBLIC_URL` | unset | The URL clients reach the server at; used in the OAuth metadata it publishes instead of the request's Host |
386
+ | `MCP_MAX_BODY_BYTES` | `1048576` | Largest request body accepted |
387
+ | `MCP_MODE` | unset | `http` selects the HTTP transport when no `--http` flag is given |
388
+ | `PORT` | `3000` | HTTP port. The Docker image sets `10000`; Render and similar platforms inject their own |
389
+ | `MCP_RATE_LIMIT` | `60` | Requests per window, per client |
390
+ | `MCP_RATE_WINDOW_MS` | `60000` | Rate-limit window |
391
+ | `MCP_SESSION_IDLE_MS` | `1800000` | Close HTTP sessions idle this long |
392
+ | `MCP_MAX_SESSIONS` | `1000` | Reject new sessions past this many |
393
+ | `YOUTUBE_MCP_MAX_CONCURRENCY` | `3` | Concurrent yt-dlp processes |
394
+ | `YOUTUBE_MCP_TRANSCRIPT_TTL_MS` | 30 days | Transcript cache lifetime |
395
+ | `YOUTUBE_MCP_COOKIES_FROM_BROWSER` | unset | Browser whose cookies yt-dlp should read: `brave`, `chrome`, `chromium`, `edge`, `firefox`, `opera`, `safari`, `vivaldi` or `whale`. See Signed-in content |
396
+ | `YOUTUBE_MCP_COOKIES_FILE` | unset | A Netscape-format cookies file inside your home directory, as an alternative to a browser |
397
+ | `YOUTUBE_MCP_PROXY` | unset | An `http`, `https`, `socks4` or `socks5` proxy URL for yt-dlp |
398
+ | `YOUTUBE_MCP_SLEEP_REQUESTS_S` | unset | Seconds yt-dlp sleeps between its own requests, to stay under YouTube's limits |
399
+
400
+ ### Signed-in content
401
+
402
+ Some videos need a signed-in session — age-restricted, members-only, private
403
+ ones you have access to — and YouTube sometimes asks an address to prove it is
404
+ not a bot before serving any video at all. Those failures arrive as
405
+ `LOGIN_REQUIRED`, `AGE_GATED` or `BOT_CHECK`, and each names the fix: set
406
+ `YOUTUBE_MCP_COOKIES_FROM_BROWSER` to a browser you are signed in with, or
407
+ `YOUTUBE_MCP_COOKIES_FILE` to a cookies file, and restart the server.
408
+
409
+ Cookies are your account. Use them in local (stdio) mode, keep a cookies file
410
+ readable by you alone, and know that content read this way may be personal.
411
+ The server validates both settings at boot, never logs the file's path or
412
+ contents, and never surfaces yt-dlp's output.
356
413
 
357
414
  ## Library Storage
358
415
 
@@ -372,6 +429,13 @@ Content is stored in `~/.youtube-knowledge/`:
372
429
  │ ├── manifest.json # What the brain covers, and where a build stopped
373
430
  │ ├── chunks.json # The timestamped passages
374
431
  │ └── profile.md # The written account, if one was saved
432
+ ├── thumbnails/ # Saved channel thumbnails
433
+ │ └── {channel_id}/
434
+ │ ├── manifest.json # Every image, its decoded size, where it is
435
+ │ ├── channel/ # avatar.* and banner.*
436
+ │ ├── videos/ # {video_id}.jpg (or .png / .webp)
437
+ │ ├── shorts/
438
+ │ └── streams/
375
439
  ├── downloads/ # Full downloads
376
440
  ├── clips/ # Extracted clips
377
441
  ├── frames/ # Captured stills
@@ -469,6 +533,11 @@ client over an in-memory transport, exercises the library against a real
469
533
  temporary filesystem, and snapshots the tool manifest so any change to the
470
534
  public surface shows up as a reviewable diff.
471
535
 
536
+ An end-to-end lane (`E2E=1 npm run test:e2e`) drives the built server through
537
+ real MCP clients over both transports against real yt-dlp and the real network,
538
+ in an isolated temporary home. It runs weekly in CI and on every release before
539
+ anything is published; see CONTRIBUTING.md.
540
+
472
541
  ## Development
473
542
 
474
543
  ```bash
@@ -490,9 +559,11 @@ project layout, coding standards, and how to add a tool.
490
559
 
491
560
  ## Security
492
561
 
493
- The HTTP transport is unauthenticated unless you set `MCP_AUTH_TOKEN`. See
494
- [SECURITY.md](SECURITY.md) before exposing it beyond localhost, and to report a
495
- vulnerability.
562
+ The HTTP transport binds loopback and refuses to listen on a network interface
563
+ without `MCP_AUTH_TOKEN`. Caller-supplied URLs must be on a YouTube host before
564
+ anything is handed to yt-dlp, every yt-dlp target follows a `--` terminator, and
565
+ ids are validated before they become paths. See [SECURITY.md](SECURITY.md) for
566
+ the deployment checklist and how to report a vulnerability.
496
567
 
497
568
  ## License
498
569
 
@@ -226,6 +226,8 @@ export declare const brainManifestSchema: z.ZodObject<{
226
226
  subscriberCount: z.ZodNumber;
227
227
  channelUrl: z.ZodString;
228
228
  description: z.ZodString;
229
+ avatarUrl: z.ZodOptional<z.ZodString>;
230
+ bannerUrl: z.ZodOptional<z.ZodString>;
229
231
  }, "strip", z.ZodTypeAny, {
230
232
  description: string;
231
233
  name: string;
@@ -233,6 +235,8 @@ export declare const brainManifestSchema: z.ZodObject<{
233
235
  handle: string;
234
236
  subscriberCount: number;
235
237
  channelUrl: string;
238
+ avatarUrl?: string | undefined;
239
+ bannerUrl?: string | undefined;
236
240
  }, {
237
241
  description: string;
238
242
  name: string;
@@ -240,6 +244,8 @@ export declare const brainManifestSchema: z.ZodObject<{
240
244
  handle: string;
241
245
  subscriberCount: number;
242
246
  channelUrl: string;
247
+ avatarUrl?: string | undefined;
248
+ bannerUrl?: string | undefined;
243
249
  }>;
244
250
  /**
245
251
  * A brain is built from one caption language. Defaulted rather than required
@@ -345,6 +351,8 @@ export declare const brainManifestSchema: z.ZodObject<{
345
351
  handle: string;
346
352
  subscriberCount: number;
347
353
  channelUrl: string;
354
+ avatarUrl?: string | undefined;
355
+ bannerUrl?: string | undefined;
348
356
  };
349
357
  version: number;
350
358
  videos: Record<string, {
@@ -391,6 +399,8 @@ export declare const brainManifestSchema: z.ZodObject<{
391
399
  handle: string;
392
400
  subscriberCount: number;
393
401
  channelUrl: string;
402
+ avatarUrl?: string | undefined;
403
+ bannerUrl?: string | undefined;
394
404
  };
395
405
  version: number;
396
406
  videos: Record<string, unknown>;
@@ -489,14 +499,4 @@ export declare const brainSummarySchema: z.ZodObject<{
489
499
  updatedAt: string;
490
500
  hasProfile: boolean;
491
501
  }>;
492
- export declare const brainLockSchema: z.ZodObject<{
493
- pid: z.ZodNumber;
494
- startedAt: z.ZodString;
495
- }, "strip", z.ZodTypeAny, {
496
- pid: number;
497
- startedAt: string;
498
- }, {
499
- pid: number;
500
- startedAt: string;
501
- }>;
502
502
  //# sourceMappingURL=brain-schemas.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"brain-schemas.d.ts","sourceRoot":"","sources":["../src/brain-schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;GAMG;AAEH,qDAAqD;AACrD,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAchC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,uDAAuD;AACvD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;EAO3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAG/B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;EAI5B,CAAC;AAEH,eAAO,MAAM,yBAAyB;;;;;;;;;EAGpC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmB3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,4BAA4B;AAC5B,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;IAG9B;;;OAGG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAMH,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;EAS7B,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,8BAA8B;AAC9B,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAY7B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;EAG1B,CAAC"}
1
+ {"version":3,"file":"brain-schemas.d.ts","sourceRoot":"","sources":["../src/brain-schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;GAMG;AAEH,qDAAqD;AACrD,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAchC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,uDAAuD;AACvD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;EAO3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAG/B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;EAI5B,CAAC;AAEH,eAAO,MAAM,yBAAyB;;;;;;;;;EAGpC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmB3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,4BAA4B;AAC5B,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAG9B;;;OAGG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAMH,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;EAS7B,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,8BAA8B;AAC9B,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAY7B,CAAC"}
@@ -106,8 +106,4 @@ export const brainSummarySchema = z.object({
106
106
  indexedCount: z.number().int(),
107
107
  chunkCount: z.number().int(),
108
108
  });
109
- export const brainLockSchema = z.object({
110
- pid: z.number().int(),
111
- startedAt: z.string(),
112
- });
113
109
  //# sourceMappingURL=brain-schemas.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"brain-schemas.js","sourceRoot":"","sources":["../src/brain-schemas.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"brain-schemas.js","sourceRoot":"","sources":["../src/brain-schemas.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { Express } from 'express';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { type HttpConfig } from './config.js';
4
+ import type { RateLimiter } from './rate-limiter.js';
5
+ import type { SessionStore } from './sessions.js';
6
+ /** The Express application: routes, authentication and rate limiting. */
7
+ export interface AppDependencies {
8
+ createServer: (mode: 'http') => McpServer;
9
+ sessions: SessionStore;
10
+ limiter: RateLimiter;
11
+ /** The bound port, known only once listening; read lazily for that reason. */
12
+ port: () => number;
13
+ startedAt: number;
14
+ }
15
+ export declare function buildApp(config: HttpConfig, deps: AppDependencies): Express;
16
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/http/app.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAmC,MAAM,SAAS,CAAC;AAOxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAYzE,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,yEAAyE;AAEzE,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,CAAC;IAC1C,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,WAAW,CAAC;IACrB,8EAA8E;IAC9E,IAAI,EAAE,MAAM,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAqK3E"}
@@ -0,0 +1,176 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import express from 'express';
3
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
4
+ import { hostHeaderValidation, localhostHostValidation, } from '@modelcontextprotocol/sdk/server/middleware/hostHeaderValidation.js';
5
+ import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
6
+ import { runPreflight } from '../utils/preflight.js';
7
+ import { serverVersion } from '../utils/version.js';
8
+ import { bearerToken, clientKey, jsonRpcError, publicBaseUrl, securityHeaders, sessionIdOf, tokenMatches, } from './auth.js';
9
+ import { isLoopback } from './config.js';
10
+ export function buildApp(config, deps) {
11
+ const { createServer, sessions, limiter, port, startedAt } = deps;
12
+ const app = express();
13
+ // `req.ip` honours forwarded headers only when the operator names the proxies.
14
+ app.set('trust proxy', config.trustProxy);
15
+ app.disable('x-powered-by');
16
+ app.use(securityHeaders());
17
+ // Every route, before anything else: an unauthenticated flood is still a flood.
18
+ app.use((req, res, next) => {
19
+ const { allowed, remaining, retryAfter } = limiter.check(clientKey(req));
20
+ res.setHeader('RateLimit-Limit', String(config.rateLimit));
21
+ res.setHeader('RateLimit-Remaining', String(remaining));
22
+ if (allowed) {
23
+ next();
24
+ return;
25
+ }
26
+ // Without Retry-After a client has no idea how long to wait.
27
+ res.setHeader('Retry-After', String(retryAfter));
28
+ jsonRpcError(res, 429, -32000, `Rate limit exceeded. Retry in ${retryAfter}s.`);
29
+ });
30
+ function hasValidToken(req) {
31
+ return (config.authToken === undefined ||
32
+ tokenMatches(bearerToken(req.headers.authorization), config.authToken));
33
+ }
34
+ /** Returns true when the request may proceed; otherwise it has been answered. */
35
+ function authorize(req, res) {
36
+ if (hasValidToken(req))
37
+ return true;
38
+ // RFC 9728: point unauthenticated callers at the resource metadata.
39
+ const metadata = `${publicBaseUrl(config, req, port())}/.well-known/oauth-protected-resource`;
40
+ res.setHeader('WWW-Authenticate', `Bearer realm="youtube-knowledge-mcp", resource_metadata="${metadata}"`);
41
+ jsonRpcError(res, 401, -32001, 'Unauthorized');
42
+ return false;
43
+ }
44
+ // Ahead of Host validation on purpose: a container's own HEALTHCHECK and every
45
+ // platform probe arrive over loopback with a Host the allowlist may not name,
46
+ // and a service that 403s its probe never goes live. Unauthenticated callers
47
+ // learn only whether the service is up; versions and counts need the token.
48
+ app.get('/health', (req, res) => {
49
+ void runPreflight().then((report) => {
50
+ const status = report.ok ? 'ok' : 'degraded';
51
+ const body = hasValidToken(req)
52
+ ? {
53
+ status,
54
+ version: serverVersion(),
55
+ uptimeSeconds: Math.round((Date.now() - startedAt) / 1000),
56
+ ytDlp: report.ytDlp,
57
+ ffmpeg: report.ffmpeg,
58
+ sessions: sessions.size,
59
+ }
60
+ : { status };
61
+ res.writeHead(report.ok ? 200 : 503, { 'Content-Type': 'application/json' });
62
+ res.end(JSON.stringify(body));
63
+ });
64
+ });
65
+ app.use(express.json({ limit: config.maxBodyBytes }));
66
+ app.use(bodyErrorHandler);
67
+ // The same decision the SDK's createMcpExpressApp makes, made explicitly so a
68
+ // body limit could be configured: an allowlist when one is given, loopback
69
+ // validation when bound to loopback, nothing when bound wide (a token is
70
+ // then required by assertSafeToStart).
71
+ if (config.allowedHosts.length > 0)
72
+ app.use(hostHeaderValidation(config.allowedHosts));
73
+ else if (isLoopback(config.bindHost))
74
+ app.use(localhostHostValidation());
75
+ app.post('/mcp', async (req, res) => {
76
+ if (!authorize(req, res))
77
+ return;
78
+ const sessionId = sessionIdOf(req);
79
+ try {
80
+ const existing = sessionId === undefined ? undefined : sessions.get(sessionId);
81
+ if (existing !== undefined && sessionId !== undefined) {
82
+ sessions.touch(sessionId);
83
+ await existing.transport.handleRequest(req, res, req.body);
84
+ return;
85
+ }
86
+ if (!sessionId && isInitializeRequest(req.body)) {
87
+ await openSession(req, res);
88
+ return;
89
+ }
90
+ jsonRpcError(res, 400, -32000, 'Bad Request: No valid session ID provided');
91
+ }
92
+ catch (error) {
93
+ console.error('Error handling MCP request:', error);
94
+ jsonRpcError(res, 500, -32603, 'Internal server error');
95
+ }
96
+ });
97
+ async function openSession(req, res) {
98
+ // Reserved before the first await, so concurrent initializes cannot all
99
+ // pass a size check and overshoot the cap together.
100
+ if (!sessions.reserve()) {
101
+ jsonRpcError(res, 503, -32000, 'Server is at capacity. Try again shortly.');
102
+ return;
103
+ }
104
+ const transport = new StreamableHTTPServerTransport({
105
+ sessionIdGenerator: () => randomUUID(),
106
+ // Hosts are checked by the port-agnostic middleware above on every
107
+ // route; the transport's own check would compare the whole Host header,
108
+ // port included, and reject every deployment on a non-default port.
109
+ ...(config.allowedOrigins.length > 0
110
+ ? { enableDnsRebindingProtection: true, allowedOrigins: config.allowedOrigins }
111
+ : {}),
112
+ onsessioninitialized: (id) => {
113
+ sessions.commit(id, transport);
114
+ },
115
+ });
116
+ transport.onclose = () => {
117
+ if (transport.sessionId)
118
+ sessions.delete(transport.sessionId);
119
+ };
120
+ try {
121
+ await createServer('http').connect(transport);
122
+ await transport.handleRequest(req, res, req.body);
123
+ }
124
+ finally {
125
+ // The transport carries an id only once the session was initialised, so
126
+ // its absence means the reservation was never turned into a session.
127
+ if (transport.sessionId === undefined)
128
+ sessions.release();
129
+ }
130
+ }
131
+ const bySession = async (req, res) => {
132
+ if (!authorize(req, res))
133
+ return;
134
+ const sessionId = sessionIdOf(req);
135
+ const session = sessionId === undefined ? undefined : sessions.get(sessionId);
136
+ if (sessionId === undefined || session === undefined) {
137
+ jsonRpcError(res, 404, -32001, 'Invalid or missing session ID');
138
+ return;
139
+ }
140
+ sessions.touch(sessionId);
141
+ await session.transport.handleRequest(req, res);
142
+ };
143
+ app.get('/mcp', bySession);
144
+ app.delete('/mcp', bySession);
145
+ // RFC 9728 protected-resource metadata, so OAuth-capable clients can discover
146
+ // how to authenticate instead of guessing.
147
+ app.get('/.well-known/oauth-protected-resource', (req, res) => {
148
+ res.writeHead(200, { 'Content-Type': 'application/json' });
149
+ res.end(JSON.stringify({
150
+ resource: `${publicBaseUrl(config, req, port())}/mcp`,
151
+ bearer_methods_supported: ['header'],
152
+ resource_documentation: 'https://github.com/teobouancheau/youtube-knowledge-mcp#readme',
153
+ }));
154
+ });
155
+ return app;
156
+ }
157
+ /**
158
+ * Body-parser failures arrive as errors with an HTTP status; Express 5 routes
159
+ * them to the first four-argument middleware. Answered in the JSON-RPC shape
160
+ * every other refusal uses, and never with the parser's own message.
161
+ */
162
+ function bodyErrorHandler(error, _req, res, next) {
163
+ const status = typeof error === 'object' && error !== null && 'status' in error
164
+ ? Number(error.status)
165
+ : Number.NaN;
166
+ if (status === 413) {
167
+ jsonRpcError(res, 413, -32600, 'Request body too large.');
168
+ return;
169
+ }
170
+ if (status >= 400 && status < 500) {
171
+ jsonRpcError(res, status, -32700, 'Request body could not be parsed.');
172
+ return;
173
+ }
174
+ next(error);
175
+ }
176
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/http/app.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { Request, RequestHandler, Response } from 'express';
2
+ import type { HttpConfig } from './config.js';
3
+ /** Bearer tokens, client identity, session ids and the JSON-RPC error envelope. */
4
+ /**
5
+ * Compare a presented token against the configured one in constant time, so the
6
+ * endpoint does not leak the token's prefix through response timing.
7
+ */
8
+ export declare function tokenMatches(presented: string | undefined, expected: string): boolean;
9
+ export declare function bearerToken(header: string | undefined): string | undefined;
10
+ /**
11
+ * The key a client is rate-limited under.
12
+ *
13
+ * `req.ip` is what Express derives after applying the `trust proxy` setting:
14
+ * the socket address by default, and the forwarded client only when the
15
+ * operator has said which proxies to believe. Reading `X-Forwarded-For`
16
+ * directly, as this once did, let any caller mint a fresh quota per request.
17
+ */
18
+ export declare function clientKey(req: Request): string;
19
+ /**
20
+ * Node parses a repeated header into an array, so this field is genuinely
21
+ * `string | string[] | undefined`. Asserting it was always a string meant a
22
+ * request that sent `Mcp-Session-Id` twice reached `sessions.get()` holding an
23
+ * array, missed every session, and was reported as an invalid session ID.
24
+ * Ambiguous is treated as absent: two session IDs identify no session.
25
+ */
26
+ export declare function sessionIdOf(req: Request): string | undefined;
27
+ export declare function jsonRpcError(res: Response, status: number, code: number, message: string): void;
28
+ /**
29
+ * The base URL this server publishes in OAuth metadata.
30
+ *
31
+ * Configuration first; the request's own view of itself only when a proxy is
32
+ * trusted to have set it. Reflecting `Host` unconditionally let anyone who
33
+ * could reach the endpoint choose the address other clients were told to use.
34
+ */
35
+ export declare function publicBaseUrl(config: HttpConfig, req: Request, port: number): string;
36
+ /** Headers every response carries; none of them cost anything for an API. */
37
+ export declare function securityHeaders(): RequestHandler;
38
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/http/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,mFAAmF;AAEnF;;;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;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAE9C;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAG5D;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAI/F;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAIpF;AAED,6EAA6E;AAC7E,wBAAgB,eAAe,IAAI,cAAc,CAQhD"}