youtube-knowledge-mcp 2.0.2 → 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 (390) hide show
  1. package/README.md +166 -33
  2. package/dist/brain-schemas.d.ts +502 -0
  3. package/dist/brain-schemas.d.ts.map +1 -0
  4. package/dist/brain-schemas.js +109 -0
  5. package/dist/brain-schemas.js.map +1 -0
  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 -431
  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 -58
  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 +75 -6
  90. package/dist/resources.js.map +1 -1
  91. package/dist/schemas.d.ts +74 -54
  92. package/dist/schemas.d.ts.map +1 -1
  93. package/dist/schemas.js +30 -4
  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/ask-brain.d.ts +62 -0
  104. package/dist/tools/ask-brain.d.ts.map +1 -0
  105. package/dist/tools/ask-brain.js +70 -0
  106. package/dist/tools/ask-brain.js.map +1 -0
  107. package/dist/tools/batch.d.ts +10 -10
  108. package/dist/tools/batch.js +3 -3
  109. package/dist/tools/batch.js.map +1 -1
  110. package/dist/tools/brain-info.d.ts +180 -0
  111. package/dist/tools/brain-info.d.ts.map +1 -0
  112. package/dist/tools/brain-info.js +108 -0
  113. package/dist/tools/brain-info.js.map +1 -0
  114. package/dist/tools/build-brain.d.ts +120 -0
  115. package/dist/tools/build-brain.d.ts.map +1 -0
  116. package/dist/tools/build-brain.js +123 -0
  117. package/dist/tools/build-brain.js.map +1 -0
  118. package/dist/tools/check-health.d.ts +2 -0
  119. package/dist/tools/check-health.d.ts.map +1 -1
  120. package/dist/tools/check-health.js +9 -0
  121. package/dist/tools/check-health.js.map +1 -1
  122. package/dist/tools/clips.d.ts +13 -45
  123. package/dist/tools/clips.d.ts.map +1 -1
  124. package/dist/tools/clips.js +52 -128
  125. package/dist/tools/clips.js.map +1 -1
  126. package/dist/tools/fetch-channel-thumbnails.d.ts +179 -0
  127. package/dist/tools/fetch-channel-thumbnails.d.ts.map +1 -0
  128. package/dist/tools/fetch-channel-thumbnails.js +123 -0
  129. package/dist/tools/fetch-channel-thumbnails.js.map +1 -0
  130. package/dist/tools/fetch-videos.d.ts +11 -8
  131. package/dist/tools/fetch-videos.d.ts.map +1 -1
  132. package/dist/tools/fetch-videos.js +3 -0
  133. package/dist/tools/fetch-videos.js.map +1 -1
  134. package/dist/tools/frames-subtitles.d.ts +44 -0
  135. package/dist/tools/frames-subtitles.d.ts.map +1 -0
  136. package/dist/tools/frames-subtitles.js +104 -0
  137. package/dist/tools/frames-subtitles.js.map +1 -0
  138. package/dist/tools/get-channel-info.d.ts +2 -0
  139. package/dist/tools/get-channel-info.d.ts.map +1 -1
  140. package/dist/tools/get-channel-info.js +8 -1
  141. package/dist/tools/get-channel-info.js.map +1 -1
  142. package/dist/tools/get-chapters.d.ts +2 -2
  143. package/dist/tools/get-comments.d.ts +2 -2
  144. package/dist/tools/get-playlist-info.d.ts.map +1 -1
  145. package/dist/tools/get-playlist-info.js +1 -0
  146. package/dist/tools/get-playlist-info.js.map +1 -1
  147. package/dist/tools/get-thumbnail.d.ts +38 -0
  148. package/dist/tools/get-thumbnail.d.ts.map +1 -0
  149. package/dist/tools/get-thumbnail.js +152 -0
  150. package/dist/tools/get-thumbnail.js.map +1 -0
  151. package/dist/tools/get-transcript.d.ts +2 -2
  152. package/dist/tools/get-transcript.d.ts.map +1 -1
  153. package/dist/tools/get-transcript.js +2 -14
  154. package/dist/tools/get-transcript.js.map +1 -1
  155. package/dist/tools/library.d.ts +9 -7
  156. package/dist/tools/library.d.ts.map +1 -1
  157. package/dist/tools/library.js +23 -7
  158. package/dist/tools/library.js.map +1 -1
  159. package/dist/tools/list-channel-thumbnails.d.ts +235 -0
  160. package/dist/tools/list-channel-thumbnails.d.ts.map +1 -0
  161. package/dist/tools/list-channel-thumbnails.js +79 -0
  162. package/dist/tools/list-channel-thumbnails.js.map +1 -0
  163. package/dist/tools/list-library.d.ts +4 -4
  164. package/dist/tools/manage-brain.d.ts +39 -0
  165. package/dist/tools/manage-brain.d.ts.map +1 -0
  166. package/dist/tools/manage-brain.js +53 -0
  167. package/dist/tools/manage-brain.js.map +1 -0
  168. package/dist/tools/save-to-library.d.ts.map +1 -1
  169. package/dist/tools/save-to-library.js +5 -1
  170. package/dist/tools/save-to-library.js.map +1 -1
  171. package/dist/tools/search-channels.d.ts +10 -4
  172. package/dist/tools/search-channels.d.ts.map +1 -1
  173. package/dist/tools/search-transcript.d.ts +2 -2
  174. package/dist/tools/search-transcript.d.ts.map +1 -1
  175. package/dist/tools/search-transcript.js +6 -13
  176. package/dist/tools/search-transcript.js.map +1 -1
  177. package/dist/tools/search-videos.d.ts +11 -8
  178. package/dist/tools/search-videos.d.ts.map +1 -1
  179. package/dist/utils/batches.d.ts +15 -0
  180. package/dist/utils/batches.d.ts.map +1 -0
  181. package/dist/utils/batches.js +31 -0
  182. package/dist/utils/batches.js.map +1 -0
  183. package/dist/utils/brain-build.d.ts +40 -0
  184. package/dist/utils/brain-build.d.ts.map +1 -0
  185. package/dist/utils/brain-build.js +200 -0
  186. package/dist/utils/brain-build.js.map +1 -0
  187. package/dist/utils/brain-chunks.d.ts +33 -0
  188. package/dist/utils/brain-chunks.d.ts.map +1 -0
  189. package/dist/utils/brain-chunks.js +87 -0
  190. package/dist/utils/brain-chunks.js.map +1 -0
  191. package/dist/utils/brain-index.d.ts +27 -0
  192. package/dist/utils/brain-index.d.ts.map +1 -0
  193. package/dist/utils/brain-index.js +101 -0
  194. package/dist/utils/brain-index.js.map +1 -0
  195. package/dist/utils/brain-ingest.d.ts +57 -0
  196. package/dist/utils/brain-ingest.d.ts.map +1 -0
  197. package/dist/utils/brain-ingest.js +127 -0
  198. package/dist/utils/brain-ingest.js.map +1 -0
  199. package/dist/utils/brain-lock.d.ts +4 -0
  200. package/dist/utils/brain-lock.d.ts.map +1 -0
  201. package/dist/utils/brain-lock.js +17 -0
  202. package/dist/utils/brain-lock.js.map +1 -0
  203. package/dist/utils/brain-lookup.d.ts +11 -0
  204. package/dist/utils/brain-lookup.d.ts.map +1 -0
  205. package/dist/utils/brain-lookup.js +36 -0
  206. package/dist/utils/brain-lookup.js.map +1 -0
  207. package/dist/utils/brain-paths.d.ts +19 -0
  208. package/dist/utils/brain-paths.d.ts.map +1 -0
  209. package/dist/utils/brain-paths.js +34 -0
  210. package/dist/utils/brain-paths.js.map +1 -0
  211. package/dist/utils/brain-phrases.d.ts +31 -0
  212. package/dist/utils/brain-phrases.d.ts.map +1 -0
  213. package/dist/utils/brain-phrases.js +116 -0
  214. package/dist/utils/brain-phrases.js.map +1 -0
  215. package/dist/utils/brain-stats.d.ts +25 -0
  216. package/dist/utils/brain-stats.d.ts.map +1 -0
  217. package/dist/utils/brain-stats.js +64 -0
  218. package/dist/utils/brain-stats.js.map +1 -0
  219. package/dist/utils/brain-storage.d.ts +28 -0
  220. package/dist/utils/brain-storage.d.ts.map +1 -0
  221. package/dist/utils/brain-storage.js +87 -0
  222. package/dist/utils/brain-storage.js.map +1 -0
  223. package/dist/utils/caption-probe.d.ts +7 -0
  224. package/dist/utils/caption-probe.d.ts.map +1 -0
  225. package/dist/utils/caption-probe.js +36 -0
  226. package/dist/utils/caption-probe.js.map +1 -0
  227. package/dist/utils/channel-images.d.ts +16 -0
  228. package/dist/utils/channel-images.d.ts.map +1 -0
  229. package/dist/utils/channel-images.js +35 -0
  230. package/dist/utils/channel-images.js.map +1 -0
  231. package/dist/utils/channel-lookup.d.ts +5 -0
  232. package/dist/utils/channel-lookup.d.ts.map +1 -0
  233. package/dist/utils/channel-lookup.js +37 -0
  234. package/dist/utils/channel-lookup.js.map +1 -0
  235. package/dist/utils/chapters.d.ts +11 -0
  236. package/dist/utils/chapters.d.ts.map +1 -0
  237. package/dist/utils/chapters.js +24 -0
  238. package/dist/utils/chapters.js.map +1 -0
  239. package/dist/utils/clips.d.ts.map +1 -1
  240. package/dist/utils/clips.js +22 -30
  241. package/dist/utils/clips.js.map +1 -1
  242. package/dist/utils/env.d.ts +26 -0
  243. package/dist/utils/env.d.ts.map +1 -0
  244. package/dist/utils/env.js +72 -0
  245. package/dist/utils/env.js.map +1 -0
  246. package/dist/utils/errors.d.ts +1 -1
  247. package/dist/utils/errors.d.ts.map +1 -1
  248. package/dist/utils/errors.js +20 -2
  249. package/dist/utils/errors.js.map +1 -1
  250. package/dist/utils/file-lock.d.ts +34 -0
  251. package/dist/utils/file-lock.d.ts.map +1 -0
  252. package/dist/utils/file-lock.js +91 -0
  253. package/dist/utils/file-lock.js.map +1 -0
  254. package/dist/utils/flat-listing.d.ts +89 -0
  255. package/dist/utils/flat-listing.d.ts.map +1 -0
  256. package/dist/utils/flat-listing.js +68 -0
  257. package/dist/utils/flat-listing.js.map +1 -0
  258. package/dist/utils/format.d.ts +1 -3
  259. package/dist/utils/format.d.ts.map +1 -1
  260. package/dist/utils/format.js +1 -5
  261. package/dist/utils/format.js.map +1 -1
  262. package/dist/utils/guard.d.ts +21 -0
  263. package/dist/utils/guard.d.ts.map +1 -0
  264. package/dist/utils/guard.js +105 -0
  265. package/dist/utils/guard.js.map +1 -0
  266. package/dist/utils/image-dimensions.d.ts +19 -0
  267. package/dist/utils/image-dimensions.d.ts.map +1 -0
  268. package/dist/utils/image-dimensions.js +93 -0
  269. package/dist/utils/image-dimensions.js.map +1 -0
  270. package/dist/utils/image-fetch.d.ts +31 -0
  271. package/dist/utils/image-fetch.d.ts.map +1 -0
  272. package/dist/utils/image-fetch.js +141 -0
  273. package/dist/utils/image-fetch.js.map +1 -0
  274. package/dist/utils/json-file.d.ts +45 -0
  275. package/dist/utils/json-file.d.ts.map +1 -0
  276. package/dist/utils/json-file.js +60 -0
  277. package/dist/utils/json-file.js.map +1 -0
  278. package/dist/utils/paths.d.ts +19 -0
  279. package/dist/utils/paths.d.ts.map +1 -0
  280. package/dist/utils/paths.js +27 -0
  281. package/dist/utils/paths.js.map +1 -0
  282. package/dist/utils/pattern.d.ts +30 -0
  283. package/dist/utils/pattern.d.ts.map +1 -0
  284. package/dist/utils/pattern.js +130 -0
  285. package/dist/utils/pattern.js.map +1 -0
  286. package/dist/utils/search-index.d.ts +22 -7
  287. package/dist/utils/search-index.d.ts.map +1 -1
  288. package/dist/utils/search-index.js +26 -8
  289. package/dist/utils/search-index.js.map +1 -1
  290. package/dist/utils/storage.d.ts +14 -44
  291. package/dist/utils/storage.d.ts.map +1 -1
  292. package/dist/utils/storage.js +47 -84
  293. package/dist/utils/storage.js.map +1 -1
  294. package/dist/utils/subtitles.d.ts +7 -0
  295. package/dist/utils/subtitles.d.ts.map +1 -0
  296. package/dist/utils/subtitles.js +30 -0
  297. package/dist/utils/subtitles.js.map +1 -0
  298. package/dist/utils/thumbnail-entry.d.ts +19 -0
  299. package/dist/utils/thumbnail-entry.d.ts.map +1 -0
  300. package/dist/utils/thumbnail-entry.js +119 -0
  301. package/dist/utils/thumbnail-entry.js.map +1 -0
  302. package/dist/utils/thumbnail-fetch.d.ts +37 -0
  303. package/dist/utils/thumbnail-fetch.d.ts.map +1 -0
  304. package/dist/utils/thumbnail-fetch.js +89 -0
  305. package/dist/utils/thumbnail-fetch.js.map +1 -0
  306. package/dist/utils/thumbnail-ladder.d.ts +44 -0
  307. package/dist/utils/thumbnail-ladder.d.ts.map +1 -0
  308. package/dist/utils/thumbnail-ladder.js +68 -0
  309. package/dist/utils/thumbnail-ladder.js.map +1 -0
  310. package/dist/utils/thumbnail-listing.d.ts +16 -0
  311. package/dist/utils/thumbnail-listing.d.ts.map +1 -0
  312. package/dist/utils/thumbnail-listing.js +26 -0
  313. package/dist/utils/thumbnail-listing.js.map +1 -0
  314. package/dist/utils/thumbnail-paths.d.ts +20 -0
  315. package/dist/utils/thumbnail-paths.d.ts.map +1 -0
  316. package/dist/utils/thumbnail-paths.js +35 -0
  317. package/dist/utils/thumbnail-paths.js.map +1 -0
  318. package/dist/utils/thumbnail-store.d.ts +19 -0
  319. package/dist/utils/thumbnail-store.d.ts.map +1 -0
  320. package/dist/utils/thumbnail-store.js +125 -0
  321. package/dist/utils/thumbnail-store.js.map +1 -0
  322. package/dist/utils/transcript-cache.d.ts +20 -0
  323. package/dist/utils/transcript-cache.d.ts.map +1 -0
  324. package/dist/utils/transcript-cache.js +127 -0
  325. package/dist/utils/transcript-cache.js.map +1 -0
  326. package/dist/utils/transcript-search.d.ts +20 -0
  327. package/dist/utils/transcript-search.d.ts.map +1 -0
  328. package/dist/utils/transcript-search.js +35 -0
  329. package/dist/utils/transcript-search.js.map +1 -0
  330. package/dist/utils/transcript.d.ts +14 -34
  331. package/dist/utils/transcript.d.ts.map +1 -1
  332. package/dist/utils/transcript.js +5 -64
  333. package/dist/utils/transcript.js.map +1 -1
  334. package/dist/utils/validate-youtube.d.ts +35 -0
  335. package/dist/utils/validate-youtube.d.ts.map +1 -0
  336. package/dist/utils/validate-youtube.js +87 -0
  337. package/dist/utils/validate-youtube.js.map +1 -0
  338. package/dist/utils/validate.d.ts +11 -3
  339. package/dist/utils/validate.d.ts.map +1 -1
  340. package/dist/utils/validate.js +43 -18
  341. package/dist/utils/validate.js.map +1 -1
  342. package/dist/utils/version.d.ts +2 -0
  343. package/dist/utils/version.d.ts.map +1 -0
  344. package/dist/utils/version.js +19 -0
  345. package/dist/utils/version.js.map +1 -0
  346. package/dist/utils/youtube-channel.d.ts +44 -0
  347. package/dist/utils/youtube-channel.d.ts.map +1 -0
  348. package/dist/utils/youtube-channel.js +61 -0
  349. package/dist/utils/youtube-channel.js.map +1 -0
  350. package/dist/utils/youtube-download.d.ts +27 -0
  351. package/dist/utils/youtube-download.d.ts.map +1 -0
  352. package/dist/utils/youtube-download.js +118 -0
  353. package/dist/utils/youtube-download.js.map +1 -0
  354. package/dist/utils/youtube-schemas.d.ts +394 -0
  355. package/dist/utils/youtube-schemas.d.ts.map +1 -0
  356. package/dist/utils/youtube-schemas.js +118 -0
  357. package/dist/utils/youtube-schemas.js.map +1 -0
  358. package/dist/utils/youtube-search.d.ts +14 -0
  359. package/dist/utils/youtube-search.d.ts.map +1 -0
  360. package/dist/utils/youtube-search.js +39 -0
  361. package/dist/utils/youtube-search.js.map +1 -0
  362. package/dist/utils/youtube-url.d.ts +25 -0
  363. package/dist/utils/youtube-url.d.ts.map +1 -0
  364. package/dist/utils/youtube-url.js +75 -0
  365. package/dist/utils/youtube-url.js.map +1 -0
  366. package/dist/utils/youtube-video.d.ts +47 -0
  367. package/dist/utils/youtube-video.d.ts.map +1 -0
  368. package/dist/utils/youtube-video.js +98 -0
  369. package/dist/utils/youtube-video.js.map +1 -0
  370. package/dist/utils/youtube.d.ts +13 -111
  371. package/dist/utils/youtube.d.ts.map +1 -1
  372. package/dist/utils/youtube.js +11 -480
  373. package/dist/utils/youtube.js.map +1 -1
  374. package/dist/utils/ytdlp-env.d.ts +33 -0
  375. package/dist/utils/ytdlp-env.d.ts.map +1 -0
  376. package/dist/utils/ytdlp-env.js +121 -0
  377. package/dist/utils/ytdlp-env.js.map +1 -0
  378. package/dist/utils/ytdlp-limiter.d.ts +24 -0
  379. package/dist/utils/ytdlp-limiter.d.ts.map +1 -0
  380. package/dist/utils/ytdlp-limiter.js +69 -0
  381. package/dist/utils/ytdlp-limiter.js.map +1 -0
  382. package/dist/utils/ytdlp-parse.d.ts +13 -0
  383. package/dist/utils/ytdlp-parse.d.ts.map +1 -0
  384. package/dist/utils/ytdlp-parse.js +48 -0
  385. package/dist/utils/ytdlp-parse.js.map +1 -0
  386. package/dist/utils/ytdlp.d.ts +13 -18
  387. package/dist/utils/ytdlp.d.ts.map +1 -1
  388. package/dist/utils/ytdlp.js +21 -110
  389. package/dist/utils/ytdlp.js.map +1 -1
  390. package/package.json +2 -1
@@ -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":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AACnG,OAAO,EACL,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,qEAAqE,CAAC;AAC7E,OAAO,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAEzE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EACL,WAAW,EACX,SAAS,EACT,YAAY,EACZ,aAAa,EACb,eAAe,EACf,WAAW,EACX,YAAY,GACb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,UAAU,EAAmB,MAAM,aAAa,CAAC;AAe1D,MAAM,UAAU,QAAQ,CAAC,MAAkB,EAAE,IAAqB;IAChE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAClE,MAAM,GAAG,GAAY,OAAO,EAAE,CAAC;IAE/B,+EAA+E;IAC/E,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IAC1C,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IAC5B,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC;IAE3B,gFAAgF;IAChF,GAAG,CAAC,GAAG,CAAC,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC1D,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;QACzE,GAAG,CAAC,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;QAC3D,GAAG,CAAC,SAAS,CAAC,qBAAqB,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;QACxD,IAAI,OAAO,EAAE,CAAC;YACZ,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QACD,6DAA6D;QAC7D,GAAG,CAAC,SAAS,CAAC,aAAa,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;QACjD,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,iCAAiC,UAAU,IAAI,CAAC,CAAC;IAClF,CAAC,CAAC,CAAC;IAEH,SAAS,aAAa,CAAC,GAAY;QACjC,OAAO,CACL,MAAM,CAAC,SAAS,KAAK,SAAS;YAC9B,YAAY,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC,SAAS,CAAC,CACvE,CAAC;IACJ,CAAC;IAED,iFAAiF;IACjF,SAAS,SAAS,CAAC,GAAY,EAAE,GAAa;QAC5C,IAAI,aAAa,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QACpC,oEAAoE;QACpE,MAAM,QAAQ,GAAG,GAAG,aAAa,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,uCAAuC,CAAC;QAC9F,GAAG,CAAC,SAAS,CACX,kBAAkB,EAClB,4DAA4D,QAAQ,GAAG,CACxE,CAAC;QACF,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;QAC/C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,+EAA+E;IAC/E,8EAA8E;IAC9E,6EAA6E;IAC7E,4EAA4E;IAC5E,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;QACjD,KAAK,YAAY,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;YAClC,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC;YAC7C,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC;gBAC7B,CAAC,CAAC;oBACE,MAAM;oBACN,OAAO,EAAE,aAAa,EAAE;oBACxB,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC;oBAC1D,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,QAAQ,EAAE,QAAQ,CAAC,IAAI;iBACxB;gBACH,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;YACf,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC7E,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;IACtD,GAAG,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAE1B,8EAA8E;IAC9E,2EAA2E;IAC3E,yEAAyE;IACzE,uCAAuC;IACvC,IAAI,MAAM,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC;QAAE,GAAG,CAAC,GAAG,CAAC,oBAAoB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;SAClF,IAAI,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC;QAAE,GAAG,CAAC,GAAG,CAAC,uBAAuB,EAAE,CAAC,CAAC;IAEzE,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;QACrD,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QAEjC,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QAEnC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAC/E,IAAI,QAAQ,KAAK,SAAS,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;gBACtD,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;gBAC1B,MAAM,QAAQ,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;gBAC3D,OAAO;YACT,CAAC;YAED,IAAI,CAAC,SAAS,IAAI,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChD,MAAM,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gBAC5B,OAAO;YACT,CAAC;YAED,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,2CAA2C,CAAC,CAAC;QAC9E,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,KAAK,CAAC,CAAC;YACpD,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,uBAAuB,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,KAAK,UAAU,WAAW,CAAC,GAAY,EAAE,GAAa;QACpD,wEAAwE;QACxE,oDAAoD;QACpD,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;YACxB,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,2CAA2C,CAAC,CAAC;YAC5E,OAAO;QACT,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,6BAA6B,CAAC;YAClD,kBAAkB,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE;YACtC,mEAAmE;YACnE,wEAAwE;YACxE,oEAAoE;YACpE,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC;gBAClC,CAAC,CAAC,EAAE,4BAA4B,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,CAAC,cAAc,EAAE;gBAC/E,CAAC,CAAC,EAAE,CAAC;YACP,oBAAoB,EAAE,CAAC,EAAU,EAAE,EAAE;gBACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;YACjC,CAAC;SACF,CAAC,CAAC;QAEH,SAAS,CAAC,OAAO,GAAG,GAAS,EAAE;YAC7B,IAAI,SAAS,CAAC,SAAS;gBAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QAChE,CAAC,CAAC;QAEF,IAAI,CAAC;YACH,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YAC9C,MAAM,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QACpD,CAAC;gBAAS,CAAC;YACT,wEAAwE;YACxE,qEAAqE;YACrE,IAAI,SAAS,CAAC,SAAS,KAAK,SAAS;gBAAE,QAAQ,CAAC,OAAO,EAAE,CAAC;QAC5D,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAG,KAAK,EAAE,GAAY,EAAE,GAAa,EAAiB,EAAE;QACrE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QAEjC,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,OAAO,GAAG,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC9E,IAAI,SAAS,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YACrD,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,+BAA+B,CAAC,CAAC;YAChE,OAAO;QACT,CAAC;QACD,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC1B,MAAM,OAAO,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAClD,CAAC,CAAC;IAEF,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC3B,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAE9B,8EAA8E;IAC9E,2CAA2C;IAC3C,GAAG,CAAC,GAAG,CAAC,uCAAuC,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;QAC/E,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC3D,GAAG,CAAC,GAAG,CACL,IAAI,CAAC,SAAS,CAAC;YACb,QAAQ,EAAE,GAAG,aAAa,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,MAAM;YACrD,wBAAwB,EAAE,CAAC,QAAQ,CAAC;YACpC,sBAAsB,EAAE,+DAA+D;SACxF,CAAC,CACH,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,KAAc,EAAE,IAAa,EAAE,GAAa,EAAE,IAAkB;IACxF,MAAM,MAAM,GACV,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,QAAQ,IAAI,KAAK;QAC9D,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;QACtB,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;IAEjB,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,yBAAyB,CAAC,CAAC;QAC1D,OAAO;IACT,CAAC;IACD,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG,EAAE,CAAC;QAClC,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,mCAAmC,CAAC,CAAC;QACvE,OAAO;IACT,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,CAAC;AACd,CAAC"}
@@ -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"}
@@ -0,0 +1,79 @@
1
+ import { timingSafeEqual } from 'node:crypto';
2
+ /** Bearer tokens, client identity, session ids and the JSON-RPC error envelope. */
3
+ /**
4
+ * Compare a presented token against the configured one in constant time, so the
5
+ * endpoint does not leak the token's prefix through response timing.
6
+ */
7
+ export function tokenMatches(presented, expected) {
8
+ if (presented === undefined)
9
+ return false;
10
+ const a = Buffer.from(presented);
11
+ const b = Buffer.from(expected);
12
+ // timingSafeEqual throws on length mismatch, which would itself be a leak;
13
+ // hash-free equalisation keeps the comparison constant-time for equal lengths
14
+ // and constant-result for unequal ones.
15
+ if (a.length !== b.length) {
16
+ timingSafeEqual(a, a);
17
+ return false;
18
+ }
19
+ return timingSafeEqual(a, b);
20
+ }
21
+ export function bearerToken(header) {
22
+ if (!header)
23
+ return undefined;
24
+ const match = /^Bearer\s+(.+)$/i.exec(header.trim());
25
+ return match?.[1];
26
+ }
27
+ /**
28
+ * The key a client is rate-limited under.
29
+ *
30
+ * `req.ip` is what Express derives after applying the `trust proxy` setting:
31
+ * the socket address by default, and the forwarded client only when the
32
+ * operator has said which proxies to believe. Reading `X-Forwarded-For`
33
+ * directly, as this once did, let any caller mint a fresh quota per request.
34
+ */
35
+ export function clientKey(req) {
36
+ return req.ip ?? req.socket.remoteAddress ?? 'unknown';
37
+ }
38
+ /**
39
+ * Node parses a repeated header into an array, so this field is genuinely
40
+ * `string | string[] | undefined`. Asserting it was always a string meant a
41
+ * request that sent `Mcp-Session-Id` twice reached `sessions.get()` holding an
42
+ * array, missed every session, and was reported as an invalid session ID.
43
+ * Ambiguous is treated as absent: two session IDs identify no session.
44
+ */
45
+ export function sessionIdOf(req) {
46
+ const header = req.headers['mcp-session-id'];
47
+ return typeof header === 'string' ? header : undefined;
48
+ }
49
+ export function jsonRpcError(res, status, code, message) {
50
+ if (res.headersSent)
51
+ return;
52
+ res.writeHead(status, { 'Content-Type': 'application/json' });
53
+ res.end(JSON.stringify({ jsonrpc: '2.0', error: { code, message }, id: null }));
54
+ }
55
+ /**
56
+ * The base URL this server publishes in OAuth metadata.
57
+ *
58
+ * Configuration first; the request's own view of itself only when a proxy is
59
+ * trusted to have set it. Reflecting `Host` unconditionally let anyone who
60
+ * could reach the endpoint choose the address other clients were told to use.
61
+ */
62
+ export function publicBaseUrl(config, req, port) {
63
+ if (config.publicUrl !== undefined)
64
+ return config.publicUrl;
65
+ if (config.trustProxy !== false)
66
+ return `${req.protocol}://${req.get('host') ?? ''}`;
67
+ return `http://${config.bindHost}:${port}`;
68
+ }
69
+ /** Headers every response carries; none of them cost anything for an API. */
70
+ export function securityHeaders() {
71
+ return (_req, res, next) => {
72
+ res.setHeader('X-Content-Type-Options', 'nosniff');
73
+ res.setHeader('Cache-Control', 'no-store');
74
+ res.setHeader('Referrer-Policy', 'no-referrer');
75
+ res.setHeader('X-Frame-Options', 'DENY');
76
+ next();
77
+ };
78
+ }
79
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/http/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAI9C,mFAAmF;AAEnF;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,SAA6B,EAAE,QAAgB;IAC1E,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAE1C,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACjC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChC,2EAA2E;IAC3E,8EAA8E;IAC9E,wCAAwC;IACxC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;QAC1B,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACtB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAA0B;IACpD,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACrD,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,GAAY;IACpC,OAAO,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,SAAS,CAAC;AACzD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,GAAY;IACtC,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC7C,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAa,EAAE,MAAc,EAAE,IAAY,EAAE,OAAe;IACvF,IAAI,GAAG,CAAC,WAAW;QAAE,OAAO;IAC5B,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAC9D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAClF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,MAAkB,EAAE,GAAY,EAAE,IAAY;IAC1E,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,SAAS,CAAC;IAC5D,IAAI,MAAM,CAAC,UAAU,KAAK,KAAK;QAAE,OAAO,GAAG,GAAG,CAAC,QAAQ,MAAM,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;IACrF,OAAO,UAAU,MAAM,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC;AAC7C,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,eAAe;IAC7B,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACzB,GAAG,CAAC,SAAS,CAAC,wBAAwB,EAAE,SAAS,CAAC,CAAC;QACnD,GAAG,CAAC,SAAS,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QAC3C,GAAG,CAAC,SAAS,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC;QAChD,GAAG,CAAC,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;QACzC,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,41 @@
1
+ /** Deployment configuration for the Streamable HTTP transport, read from the environment. */
2
+ export interface HttpConfig {
3
+ port: number;
4
+ /** Interface to bind. Loopback by default; containers set 0.0.0.0 explicitly. */
5
+ bindHost: string;
6
+ /** When set, every request must present this as a bearer token. */
7
+ authToken?: string;
8
+ /** Host header allowlist. Empty means "accept any", which is only safe behind a proxy. */
9
+ allowedHosts: string[];
10
+ /** Origin header allowlist for browser callers. */
11
+ allowedOrigins: string[];
12
+ rateLimit: number;
13
+ rateWindowMs: number;
14
+ /** Sessions idle longer than this are closed, so the map cannot grow forever. */
15
+ sessionIdleMs: number;
16
+ maxSessions: number;
17
+ /**
18
+ * Whether `X-Forwarded-*` headers are believed: `false`, `true`, or the number
19
+ * of proxy hops in front of the server. Off by default, because a client can
20
+ * write those headers itself.
21
+ */
22
+ trustProxy: boolean | number;
23
+ /** The URL clients reach this server at, for the OAuth metadata it publishes. */
24
+ publicUrl?: string;
25
+ /** Largest request body accepted, in bytes. */
26
+ maxBodyBytes: number;
27
+ /** Explicit consent to listen on a non-loopback interface without a token. */
28
+ allowUnauthenticated: boolean;
29
+ }
30
+ export declare const DEFAULT_MAX_BODY_BYTES: number;
31
+ export declare function readHttpConfig(): HttpConfig;
32
+ export declare function isLoopback(host: string): boolean;
33
+ /**
34
+ * Refuse to expose an unauthenticated server to a network.
35
+ *
36
+ * Binding beyond loopback with no token used to be the default and was merely
37
+ * logged. Now it is a startup error unless the operator has said, in so many
38
+ * words, that they mean it.
39
+ */
40
+ export declare function assertSafeToStart(config: HttpConfig): void;
41
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/http/config.ts"],"names":[],"mappings":"AAEA,6FAA6F;AAE7F,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0FAA0F;IAC1F,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,mDAAmD;IACnD,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,UAAU,EAAE,OAAO,GAAG,MAAM,CAAC;IAC7B,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,YAAY,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,oBAAoB,EAAE,OAAO,CAAC;CAC/B;AAED,eAAO,MAAM,sBAAsB,QAAc,CAAC;AAsBlD,wBAAgB,cAAc,IAAI,UAAU,CAkB3C;AAID,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAY1D"}
@@ -0,0 +1,65 @@
1
+ import { envBool, envInt, envList, envString } from '../utils/env.js';
2
+ export const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;
3
+ function readTrustProxy() {
4
+ const raw = envString('MCP_TRUST_PROXY');
5
+ if (raw === undefined)
6
+ return false;
7
+ if (/^\d+$/.test(raw))
8
+ return Number(raw);
9
+ return envBool('MCP_TRUST_PROXY', false);
10
+ }
11
+ function readPublicUrl() {
12
+ const raw = envString('MCP_PUBLIC_URL');
13
+ if (raw === undefined)
14
+ return undefined;
15
+ try {
16
+ const url = new URL(raw);
17
+ if (url.protocol !== 'http:' && url.protocol !== 'https:')
18
+ throw new Error('scheme');
19
+ return url.toString().replace(/\/+$/, '');
20
+ }
21
+ catch {
22
+ console.error('Ignoring MCP_PUBLIC_URL: not an http(s) URL.');
23
+ return undefined;
24
+ }
25
+ }
26
+ export function readHttpConfig() {
27
+ const publicUrl = readPublicUrl();
28
+ return {
29
+ port: envInt('PORT', 3000, { min: 1, max: 65_535 }),
30
+ bindHost: envString('MCP_BIND_HOST') ?? '127.0.0.1',
31
+ // An empty MCP_AUTH_TOKEN must mean "no auth", not "the empty token".
32
+ authToken: envString('MCP_AUTH_TOKEN'),
33
+ allowedHosts: envList('MCP_ALLOWED_HOSTS'),
34
+ allowedOrigins: envList('MCP_ALLOWED_ORIGINS'),
35
+ rateLimit: envInt('MCP_RATE_LIMIT', 60, { min: 1 }),
36
+ rateWindowMs: envInt('MCP_RATE_WINDOW_MS', 60_000, { min: 1 }),
37
+ sessionIdleMs: envInt('MCP_SESSION_IDLE_MS', 30 * 60_000, { min: 1 }),
38
+ maxSessions: envInt('MCP_MAX_SESSIONS', 1000, { min: 1 }),
39
+ trustProxy: readTrustProxy(),
40
+ ...(publicUrl === undefined ? {} : { publicUrl }),
41
+ maxBodyBytes: envInt('MCP_MAX_BODY_BYTES', DEFAULT_MAX_BODY_BYTES, { min: 1024 }),
42
+ allowUnauthenticated: envBool('MCP_ALLOW_UNAUTHENTICATED', false),
43
+ };
44
+ }
45
+ const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '::ffff:127.0.0.1']);
46
+ export function isLoopback(host) {
47
+ return LOOPBACK_HOSTS.has(host.toLowerCase());
48
+ }
49
+ /**
50
+ * Refuse to expose an unauthenticated server to a network.
51
+ *
52
+ * Binding beyond loopback with no token used to be the default and was merely
53
+ * logged. Now it is a startup error unless the operator has said, in so many
54
+ * words, that they mean it.
55
+ */
56
+ export function assertSafeToStart(config) {
57
+ if (isLoopback(config.bindHost) ||
58
+ config.authToken !== undefined ||
59
+ config.allowUnauthenticated) {
60
+ return;
61
+ }
62
+ throw new Error(`Refusing to listen on ${config.bindHost} without authentication. ` +
63
+ 'Set MCP_AUTH_TOKEN, or set MCP_ALLOW_UNAUTHENTICATED=true to run open on purpose.');
64
+ }
65
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/http/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Fixed-window counter, per client.
3
+ *
4
+ * The map is capped as well as swept. A client that can vary its key at will
5
+ * — which is what an unvalidated forwarding header allowed — could otherwise
6
+ * grow it without limit inside a single window.
7
+ */
8
+ export declare class RateLimiter {
9
+ private readonly limit;
10
+ private readonly windowMs;
11
+ private readonly maxKeys;
12
+ private readonly hits;
13
+ constructor(limit: number, windowMs: number, maxKeys?: number);
14
+ check(key: string, now?: number): {
15
+ allowed: boolean;
16
+ remaining: number;
17
+ retryAfter: number;
18
+ };
19
+ /** Expired entries go first; failing that, the oldest live one (Map insertion order). */
20
+ private makeRoom;
21
+ sweep(now?: number): void;
22
+ get size(): number;
23
+ }
24
+ //# sourceMappingURL=rate-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/http/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,qBAAa,WAAW;IAIpB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyD;gBAG3D,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,SAAS;IAGnC,KAAK,CACH,GAAG,EAAE,MAAM,EACX,GAAG,SAAa,GACf;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;IAkB9D,yFAAyF;IACzF,OAAO,CAAC,QAAQ;IAQhB,KAAK,CAAC,GAAG,SAAa,GAAG,IAAI;IAM7B,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Fixed-window counter, per client.
3
+ *
4
+ * The map is capped as well as swept. A client that can vary its key at will
5
+ * — which is what an unvalidated forwarding header allowed — could otherwise
6
+ * grow it without limit inside a single window.
7
+ */
8
+ export class RateLimiter {
9
+ limit;
10
+ windowMs;
11
+ maxKeys;
12
+ hits = new Map();
13
+ constructor(limit, windowMs, maxKeys = 10_000) {
14
+ this.limit = limit;
15
+ this.windowMs = windowMs;
16
+ this.maxKeys = maxKeys;
17
+ }
18
+ check(key, now = Date.now()) {
19
+ const entry = this.hits.get(key);
20
+ if (!entry || now > entry.resetAt) {
21
+ if (!entry)
22
+ this.makeRoom(now);
23
+ this.hits.set(key, { count: 1, resetAt: now + this.windowMs });
24
+ return { allowed: true, remaining: this.limit - 1, retryAfter: 0 };
25
+ }
26
+ entry.count++;
27
+ const allowed = entry.count <= this.limit;
28
+ return {
29
+ allowed,
30
+ remaining: Math.max(0, this.limit - entry.count),
31
+ retryAfter: allowed ? 0 : Math.ceil((entry.resetAt - now) / 1000),
32
+ };
33
+ }
34
+ /** Expired entries go first; failing that, the oldest live one (Map insertion order). */
35
+ makeRoom(now) {
36
+ if (this.hits.size < this.maxKeys)
37
+ return;
38
+ this.sweep(now);
39
+ if (this.hits.size < this.maxKeys)
40
+ return;
41
+ const oldest = this.hits.keys().next();
42
+ if (!oldest.done)
43
+ this.hits.delete(oldest.value);
44
+ }
45
+ sweep(now = Date.now()) {
46
+ for (const [key, entry] of this.hits) {
47
+ if (now > entry.resetAt)
48
+ this.hits.delete(key);
49
+ }
50
+ }
51
+ get size() {
52
+ return this.hits.size;
53
+ }
54
+ }
55
+ //# sourceMappingURL=rate-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/http/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,OAAO,WAAW;IAIH;IACA;IACA;IALF,IAAI,GAAG,IAAI,GAAG,EAA8C,CAAC;IAE9E,YACmB,KAAa,EACb,QAAgB,EAChB,UAAU,MAAM;QAFhB,UAAK,GAAL,KAAK,CAAQ;QACb,aAAQ,GAAR,QAAQ,CAAQ;QAChB,YAAO,GAAP,OAAO,CAAS;IAChC,CAAC;IAEJ,KAAK,CACH,GAAW,EACX,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QAEhB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAEjC,IAAI,CAAC,KAAK,IAAI,GAAG,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;YAClC,IAAI,CAAC,KAAK;gBAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC/B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;YAC/D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,GAAG,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;QACrE,CAAC;QAED,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC;QAC1C,OAAO;YACL,OAAO;YACP,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;YAChD,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC;SAClE,CAAC;IACJ,CAAC;IAED,yFAAyF;IACjF,QAAQ,CAAC,GAAW;QAC1B,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAChB,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;QACvC,IAAI,CAAC,MAAM,CAAC,IAAI;YAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QACpB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACrC,IAAI,GAAG,GAAG,KAAK,CAAC,OAAO;gBAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IACxB,CAAC;CACF"}
@@ -0,0 +1,34 @@
1
+ import type { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
2
+ /** Live MCP sessions, keyed by the id the transport issued. */
3
+ export interface Session {
4
+ transport: StreamableHTTPServerTransport;
5
+ lastSeen: number;
6
+ }
7
+ /**
8
+ * Capacity is reserved synchronously, before the awaits that create a session.
9
+ *
10
+ * Checking `size` and then awaiting let every concurrent initialize pass the
11
+ * check together and overshoot the cap. A reservation counts from the moment
12
+ * the decision is made until the session is committed or abandoned.
13
+ */
14
+ export declare class SessionStore {
15
+ private readonly maxSessions;
16
+ private readonly sessions;
17
+ private reserved;
18
+ constructor(maxSessions: number);
19
+ /** Claims a slot, or reports that none is free. Pending reservations count. */
20
+ reserve(): boolean;
21
+ /** Turns a reservation into a session. */
22
+ commit(id: string, transport: StreamableHTTPServerTransport): void;
23
+ /** Gives a reservation back when the session never came to be. */
24
+ release(): void;
25
+ get(id: string): Session | undefined;
26
+ touch(id: string): void;
27
+ delete(id: string): void;
28
+ /** Closes and forgets every session idle longer than `idleMs`. */
29
+ sweep(now: number, idleMs: number): void;
30
+ closeAll(): Promise<void>;
31
+ /** Committed sessions only; reservations are transient. */
32
+ get size(): number;
33
+ }
34
+ //# sourceMappingURL=sessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/http/sessions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAExG,+DAA+D;AAE/D,MAAM,WAAW,OAAO;IACtB,SAAS,EAAE,6BAA6B,CAAC;IACzC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,qBAAa,YAAY;IAIX,OAAO,CAAC,QAAQ,CAAC,WAAW;IAHxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8B;IACvD,OAAO,CAAC,QAAQ,CAAK;gBAEQ,WAAW,EAAE,MAAM;IAEhD,+EAA+E;IAC/E,OAAO,IAAI,OAAO;IAMlB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,6BAA6B,GAAG,IAAI;IAKlE,kEAAkE;IAClE,OAAO,IAAI,IAAI;IAIf,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAIpC,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAKvB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAIxB,kEAAkE;IAClE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IASlC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAO/B,2DAA2D;IAC3D,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Capacity is reserved synchronously, before the awaits that create a session.
3
+ *
4
+ * Checking `size` and then awaiting let every concurrent initialize pass the
5
+ * check together and overshoot the cap. A reservation counts from the moment
6
+ * the decision is made until the session is committed or abandoned.
7
+ */
8
+ export class SessionStore {
9
+ maxSessions;
10
+ sessions = new Map();
11
+ reserved = 0;
12
+ constructor(maxSessions) {
13
+ this.maxSessions = maxSessions;
14
+ }
15
+ /** Claims a slot, or reports that none is free. Pending reservations count. */
16
+ reserve() {
17
+ if (this.sessions.size + this.reserved >= this.maxSessions)
18
+ return false;
19
+ this.reserved++;
20
+ return true;
21
+ }
22
+ /** Turns a reservation into a session. */
23
+ commit(id, transport) {
24
+ this.reserved = Math.max(0, this.reserved - 1);
25
+ this.sessions.set(id, { transport, lastSeen: Date.now() });
26
+ }
27
+ /** Gives a reservation back when the session never came to be. */
28
+ release() {
29
+ this.reserved = Math.max(0, this.reserved - 1);
30
+ }
31
+ get(id) {
32
+ return this.sessions.get(id);
33
+ }
34
+ touch(id) {
35
+ const session = this.sessions.get(id);
36
+ if (session)
37
+ session.lastSeen = Date.now();
38
+ }
39
+ delete(id) {
40
+ this.sessions.delete(id);
41
+ }
42
+ /** Closes and forgets every session idle longer than `idleMs`. */
43
+ sweep(now, idleMs) {
44
+ for (const [id, session] of this.sessions) {
45
+ if (now - session.lastSeen > idleMs) {
46
+ this.sessions.delete(id);
47
+ void session.transport.close();
48
+ }
49
+ }
50
+ }
51
+ async closeAll() {
52
+ for (const [id, session] of this.sessions) {
53
+ this.sessions.delete(id);
54
+ await session.transport.close();
55
+ }
56
+ }
57
+ /** Committed sessions only; reservations are transient. */
58
+ get size() {
59
+ return this.sessions.size;
60
+ }
61
+ }
62
+ //# sourceMappingURL=sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/http/sessions.ts"],"names":[],"mappings":"AASA;;;;;;GAMG;AACH,MAAM,OAAO,YAAY;IAIM;IAHZ,QAAQ,GAAG,IAAI,GAAG,EAAmB,CAAC;IAC/C,QAAQ,GAAG,CAAC,CAAC;IAErB,YAA6B,WAAmB;QAAnB,gBAAW,GAAX,WAAW,CAAQ;IAAG,CAAC;IAEpD,+EAA+E;IAC/E,OAAO;QACL,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,WAAW;YAAE,OAAO,KAAK,CAAC;QACzE,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,0CAA0C;IAC1C,MAAM,CAAC,EAAU,EAAE,SAAwC;QACzD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;QAC/C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,kEAAkE;IAClE,OAAO;QACL,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,EAAU;QACd,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACtC,IAAI,OAAO;YAAE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7C,CAAC;IAED,MAAM,CAAC,EAAU;QACf,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC;IAED,kEAAkE;IAClE,KAAK,CAAC,GAAW,EAAE,MAAc;QAC/B,KAAK,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC1C,IAAI,GAAG,GAAG,OAAO,CAAC,QAAQ,GAAG,MAAM,EAAE,CAAC;gBACpC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACzB,KAAK,OAAO,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,KAAK,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC1C,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACzB,MAAM,OAAO,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QAClC,CAAC;IACH,CAAC;IAED,2DAA2D;IAC3D,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;CACF"}