skybridge 0.0.0-dev.a08ce31 → 0.0.0-dev.a09ed26

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 (347) hide show
  1. package/README.md +132 -124
  2. package/dist/cli/build-helpers.d.ts +7 -0
  3. package/dist/cli/build-helpers.js +82 -0
  4. package/dist/cli/build-helpers.js.map +1 -0
  5. package/dist/cli/build-helpers.test.js +64 -0
  6. package/dist/cli/build-helpers.test.js.map +1 -0
  7. package/dist/cli/detect-port.d.ts +2 -2
  8. package/dist/cli/detect-port.js +9 -20
  9. package/dist/cli/detect-port.js.map +1 -1
  10. package/dist/cli/header.js +1 -1
  11. package/dist/cli/header.js.map +1 -1
  12. package/dist/cli/resolve-views-dir.d.ts +1 -0
  13. package/dist/cli/resolve-views-dir.js +17 -0
  14. package/dist/cli/resolve-views-dir.js.map +1 -0
  15. package/dist/cli/run-command.js.map +1 -1
  16. package/dist/cli/telemetry.js.map +1 -1
  17. package/dist/cli/tunnel-control-server.d.ts +9 -0
  18. package/dist/cli/tunnel-control-server.js +31 -0
  19. package/dist/cli/tunnel-control-server.js.map +1 -0
  20. package/dist/cli/tunnel-control-server.test.js +39 -0
  21. package/dist/cli/tunnel-control-server.test.js.map +1 -0
  22. package/dist/cli/tunnel-handler.d.ts +3 -0
  23. package/dist/cli/tunnel-handler.js +48 -0
  24. package/dist/cli/tunnel-handler.js.map +1 -0
  25. package/dist/cli/tunnel-handler.test.js +105 -0
  26. package/dist/cli/tunnel-handler.test.js.map +1 -0
  27. package/dist/cli/tunnel.d.ts +57 -0
  28. package/dist/cli/tunnel.js +154 -0
  29. package/dist/cli/tunnel.js.map +1 -0
  30. package/dist/cli/tunnel.test.d.ts +1 -0
  31. package/dist/cli/tunnel.test.js +190 -0
  32. package/dist/cli/tunnel.test.js.map +1 -0
  33. package/dist/cli/types.js.map +1 -1
  34. package/dist/cli/use-execute-steps.js.map +1 -1
  35. package/dist/cli/use-messages.d.ts +3 -0
  36. package/dist/cli/use-messages.js +11 -0
  37. package/dist/cli/use-messages.js.map +1 -0
  38. package/dist/cli/use-nodemon.d.ts +2 -2
  39. package/dist/cli/use-nodemon.js +18 -25
  40. package/dist/cli/use-nodemon.js.map +1 -1
  41. package/dist/cli/use-open-browser.d.ts +1 -0
  42. package/dist/cli/use-open-browser.js +44 -0
  43. package/dist/cli/use-open-browser.js.map +1 -0
  44. package/dist/cli/use-tunnel.d.ts +13 -7
  45. package/dist/cli/use-tunnel.js +103 -73
  46. package/dist/cli/use-tunnel.js.map +1 -1
  47. package/dist/cli/use-typescript-check.d.ts +1 -0
  48. package/dist/cli/use-typescript-check.js +42 -7
  49. package/dist/cli/use-typescript-check.js.map +1 -1
  50. package/dist/commands/build.d.ts +0 -1
  51. package/dist/commands/build.js +52 -8
  52. package/dist/commands/build.js.map +1 -1
  53. package/dist/commands/create.d.ts +9 -0
  54. package/dist/commands/create.js +30 -0
  55. package/dist/commands/create.js.map +1 -0
  56. package/dist/commands/dev.d.ts +2 -0
  57. package/dist/commands/dev.js +58 -6
  58. package/dist/commands/dev.js.map +1 -1
  59. package/dist/commands/start.js +13 -10
  60. package/dist/commands/start.js.map +1 -1
  61. package/dist/commands/telemetry/disable.js.map +1 -1
  62. package/dist/commands/telemetry/enable.js.map +1 -1
  63. package/dist/commands/telemetry/status.js.map +1 -1
  64. package/dist/server/asset-base-url-transform-plugin.d.ts +1 -0
  65. package/dist/server/asset-base-url-transform-plugin.js +17 -2
  66. package/dist/server/asset-base-url-transform-plugin.js.map +1 -1
  67. package/dist/server/asset-base-url-transform-plugin.test.js +80 -1
  68. package/dist/server/asset-base-url-transform-plugin.test.js.map +1 -1
  69. package/dist/server/auth/discovery.d.ts +32 -0
  70. package/dist/server/auth/discovery.js +44 -0
  71. package/dist/server/auth/discovery.js.map +1 -0
  72. package/dist/server/auth/discovery.test.d.ts +1 -0
  73. package/dist/server/auth/discovery.test.js +71 -0
  74. package/dist/server/auth/discovery.test.js.map +1 -0
  75. package/dist/server/auth/index.d.ts +18 -0
  76. package/dist/server/auth/index.js +2 -0
  77. package/dist/server/auth/index.js.map +1 -0
  78. package/dist/server/auth/providers/custom.d.ts +12 -0
  79. package/dist/server/auth/providers/custom.js +28 -0
  80. package/dist/server/auth/providers/custom.js.map +1 -0
  81. package/dist/server/auth/providers/custom.test.d.ts +1 -0
  82. package/dist/server/auth/providers/custom.test.js +91 -0
  83. package/dist/server/auth/providers/custom.test.js.map +1 -0
  84. package/dist/server/auth/setup.d.ts +4 -0
  85. package/dist/server/auth/setup.js +51 -0
  86. package/dist/server/auth/setup.js.map +1 -0
  87. package/dist/server/auth/setup.test.d.ts +1 -0
  88. package/dist/server/auth/setup.test.js +185 -0
  89. package/dist/server/auth/setup.test.js.map +1 -0
  90. package/dist/server/auth/verify.d.ts +11 -0
  91. package/dist/server/auth/verify.js +37 -0
  92. package/dist/server/auth/verify.js.map +1 -0
  93. package/dist/server/auth/verify.test.d.ts +1 -0
  94. package/dist/server/auth/verify.test.js +87 -0
  95. package/dist/server/auth/verify.test.js.map +1 -0
  96. package/dist/server/auth.d.ts +20 -0
  97. package/dist/server/auth.js +28 -0
  98. package/dist/server/auth.js.map +1 -0
  99. package/dist/server/build-manifest.test.d.ts +1 -0
  100. package/dist/server/build-manifest.test.js +27 -0
  101. package/dist/server/build-manifest.test.js.map +1 -0
  102. package/dist/server/content-helpers.d.ts +67 -0
  103. package/dist/server/content-helpers.js +79 -0
  104. package/dist/server/content-helpers.js.map +1 -0
  105. package/dist/server/content-helpers.test.d.ts +1 -0
  106. package/dist/server/content-helpers.test.js +70 -0
  107. package/dist/server/content-helpers.test.js.map +1 -0
  108. package/dist/server/express.d.ts +2 -6
  109. package/dist/server/express.js +34 -10
  110. package/dist/server/express.js.map +1 -1
  111. package/dist/server/express.test.js +310 -71
  112. package/dist/server/express.test.js.map +1 -1
  113. package/dist/server/file-ref.d.ts +28 -0
  114. package/dist/server/file-ref.js +27 -0
  115. package/dist/server/file-ref.js.map +1 -0
  116. package/dist/server/index.d.ts +8 -3
  117. package/dist/server/index.js +6 -2
  118. package/dist/server/index.js.map +1 -1
  119. package/dist/server/inferUtilityTypes.d.ts +6 -6
  120. package/dist/server/inferUtilityTypes.js.map +1 -1
  121. package/dist/server/metric.js.map +1 -1
  122. package/dist/server/middleware.d.ts +16 -3
  123. package/dist/server/middleware.js.map +1 -1
  124. package/dist/server/middleware.test-d.js.map +1 -1
  125. package/dist/server/middleware.test.js +12 -9
  126. package/dist/server/middleware.test.js.map +1 -1
  127. package/dist/server/requestOrigin.d.ts +7 -0
  128. package/dist/server/requestOrigin.js +25 -0
  129. package/dist/server/requestOrigin.js.map +1 -0
  130. package/dist/server/server.d.ts +326 -73
  131. package/dist/server/server.js +477 -138
  132. package/dist/server/server.js.map +1 -1
  133. package/dist/server/templateHelper.d.ts +5 -7
  134. package/dist/server/templateHelper.js +3 -22
  135. package/dist/server/templateHelper.js.map +1 -1
  136. package/dist/server/templates.generated.d.ts +4 -0
  137. package/dist/server/templates.generated.js +47 -0
  138. package/dist/server/templates.generated.js.map +1 -0
  139. package/dist/server/tunnel-proxy-router.d.ts +7 -0
  140. package/dist/server/tunnel-proxy-router.js +110 -0
  141. package/dist/server/tunnel-proxy-router.js.map +1 -0
  142. package/dist/server/tunnel-proxy-router.test.d.ts +1 -0
  143. package/dist/server/tunnel-proxy-router.test.js +229 -0
  144. package/dist/server/tunnel-proxy-router.test.js.map +1 -0
  145. package/dist/server/view-resource-resolution.test.d.ts +6 -0
  146. package/dist/server/view-resource-resolution.test.js +88 -0
  147. package/dist/server/view-resource-resolution.test.js.map +1 -0
  148. package/dist/server/viewsDevServer.d.ts +14 -0
  149. package/dist/server/viewsDevServer.js +45 -0
  150. package/dist/server/viewsDevServer.js.map +1 -0
  151. package/dist/test/utils.d.ts +13 -21
  152. package/dist/test/utils.js +42 -37
  153. package/dist/test/utils.js.map +1 -1
  154. package/dist/test/view.test.d.ts +1 -0
  155. package/dist/test/view.test.js +568 -0
  156. package/dist/test/view.test.js.map +1 -0
  157. package/dist/version.js +1 -3
  158. package/dist/version.js.map +1 -1
  159. package/dist/web/bridges/apps-sdk/adaptor.d.ts +11 -4
  160. package/dist/web/bridges/apps-sdk/adaptor.js +59 -17
  161. package/dist/web/bridges/apps-sdk/adaptor.js.map +1 -1
  162. package/dist/web/bridges/apps-sdk/bridge.d.ts +1 -0
  163. package/dist/web/bridges/apps-sdk/bridge.js +1 -0
  164. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
  165. package/dist/web/bridges/apps-sdk/index.js.map +1 -1
  166. package/dist/web/bridges/apps-sdk/types.d.ts +18 -6
  167. package/dist/web/bridges/apps-sdk/types.js.map +1 -1
  168. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.d.ts +11 -0
  169. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js +11 -0
  170. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
  171. package/dist/web/bridges/get-adaptor.d.ts +7 -0
  172. package/dist/web/bridges/get-adaptor.js +7 -0
  173. package/dist/web/bridges/get-adaptor.js.map +1 -1
  174. package/dist/web/bridges/index.js.map +1 -1
  175. package/dist/web/bridges/mcp-app/adaptor.d.ts +18 -7
  176. package/dist/web/bridges/mcp-app/adaptor.js +51 -30
  177. package/dist/web/bridges/mcp-app/adaptor.js.map +1 -1
  178. package/dist/web/bridges/mcp-app/bridge.d.ts +4 -2
  179. package/dist/web/bridges/mcp-app/bridge.js +23 -1
  180. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  181. package/dist/web/bridges/mcp-app/index.js.map +1 -1
  182. package/dist/web/bridges/mcp-app/types.js.map +1 -1
  183. package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +12 -0
  184. package/dist/web/bridges/mcp-app/use-mcp-app-context.js +12 -0
  185. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
  186. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -1
  187. package/dist/web/bridges/mcp-app/view-tools.test.d.ts +1 -0
  188. package/dist/web/bridges/mcp-app/view-tools.test.js +144 -0
  189. package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -0
  190. package/dist/web/bridges/types.d.ts +112 -11
  191. package/dist/web/bridges/types.js.map +1 -1
  192. package/dist/web/bridges/use-host-context.d.ts +5 -0
  193. package/dist/web/bridges/use-host-context.js +5 -0
  194. package/dist/web/bridges/use-host-context.js.map +1 -1
  195. package/dist/web/components/modal-provider.js +2 -2
  196. package/dist/web/components/modal-provider.js.map +1 -1
  197. package/dist/web/create-store.d.ts +26 -0
  198. package/dist/web/create-store.js +35 -9
  199. package/dist/web/create-store.js.map +1 -1
  200. package/dist/web/create-store.test.js +14 -16
  201. package/dist/web/create-store.test.js.map +1 -1
  202. package/dist/web/data-llm.d.ts +34 -1
  203. package/dist/web/data-llm.js +31 -3
  204. package/dist/web/data-llm.js.map +1 -1
  205. package/dist/web/data-llm.test.js +22 -22
  206. package/dist/web/data-llm.test.js.map +1 -1
  207. package/dist/web/generate-helpers.d.ts +22 -18
  208. package/dist/web/generate-helpers.js +22 -18
  209. package/dist/web/generate-helpers.js.map +1 -1
  210. package/dist/web/generate-helpers.test-d.js +30 -28
  211. package/dist/web/generate-helpers.test-d.js.map +1 -1
  212. package/dist/web/generate-helpers.test.js.map +1 -1
  213. package/dist/web/helpers/state.d.ts +2 -2
  214. package/dist/web/helpers/state.js +11 -11
  215. package/dist/web/helpers/state.js.map +1 -1
  216. package/dist/web/helpers/state.test.js +9 -9
  217. package/dist/web/helpers/state.test.js.map +1 -1
  218. package/dist/web/hooks/index.d.ts +5 -1
  219. package/dist/web/hooks/index.js +5 -1
  220. package/dist/web/hooks/index.js.map +1 -1
  221. package/dist/web/hooks/test/utils.d.ts +6 -2
  222. package/dist/web/hooks/test/utils.js +13 -2
  223. package/dist/web/hooks/test/utils.js.map +1 -1
  224. package/dist/web/hooks/use-call-tool.d.ts +45 -0
  225. package/dist/web/hooks/use-call-tool.js +28 -0
  226. package/dist/web/hooks/use-call-tool.js.map +1 -1
  227. package/dist/web/hooks/use-call-tool.test-d.js.map +1 -1
  228. package/dist/web/hooks/use-call-tool.test.js +27 -6
  229. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  230. package/dist/web/hooks/use-display-mode.d.ts +20 -0
  231. package/dist/web/hooks/use-display-mode.js +20 -0
  232. package/dist/web/hooks/use-display-mode.js.map +1 -1
  233. package/dist/web/hooks/use-display-mode.test-d.js.map +1 -1
  234. package/dist/web/hooks/use-display-mode.test.js.map +1 -1
  235. package/dist/web/hooks/use-download.d.ts +5 -0
  236. package/dist/web/hooks/use-download.js +8 -0
  237. package/dist/web/hooks/use-download.js.map +1 -0
  238. package/dist/web/hooks/use-download.test.d.ts +1 -0
  239. package/dist/web/hooks/use-download.test.js +95 -0
  240. package/dist/web/hooks/use-download.test.js.map +1 -0
  241. package/dist/web/hooks/use-files.d.ts +34 -1
  242. package/dist/web/hooks/use-files.js +33 -0
  243. package/dist/web/hooks/use-files.js.map +1 -1
  244. package/dist/web/hooks/use-files.test.js +22 -2
  245. package/dist/web/hooks/use-files.test.js.map +1 -1
  246. package/dist/web/hooks/use-layout.d.ts +2 -0
  247. package/dist/web/hooks/use-layout.js +2 -0
  248. package/dist/web/hooks/use-layout.js.map +1 -1
  249. package/dist/web/hooks/use-layout.test.js.map +1 -1
  250. package/dist/web/hooks/use-open-external.d.ts +17 -0
  251. package/dist/web/hooks/use-open-external.js +16 -0
  252. package/dist/web/hooks/use-open-external.js.map +1 -1
  253. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  254. package/dist/web/hooks/use-register-view-tool.d.ts +38 -0
  255. package/dist/web/hooks/use-register-view-tool.js +50 -0
  256. package/dist/web/hooks/use-register-view-tool.js.map +1 -0
  257. package/dist/web/hooks/use-request-close.d.ts +16 -0
  258. package/dist/web/hooks/use-request-close.js +21 -0
  259. package/dist/web/hooks/use-request-close.js.map +1 -0
  260. package/dist/web/hooks/use-request-close.test.d.ts +1 -0
  261. package/dist/web/hooks/use-request-close.test.js +52 -0
  262. package/dist/web/hooks/use-request-close.test.js.map +1 -0
  263. package/dist/web/hooks/use-request-modal.d.ts +16 -1
  264. package/dist/web/hooks/use-request-modal.js +19 -4
  265. package/dist/web/hooks/use-request-modal.js.map +1 -1
  266. package/dist/web/hooks/use-request-modal.test.js +1 -1
  267. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  268. package/dist/web/hooks/use-request-size.d.ts +20 -0
  269. package/dist/web/hooks/use-request-size.js +24 -0
  270. package/dist/web/hooks/use-request-size.js.map +1 -0
  271. package/dist/web/hooks/use-request-size.test.d.ts +1 -0
  272. package/dist/web/hooks/use-request-size.test.js +65 -0
  273. package/dist/web/hooks/use-request-size.test.js.map +1 -0
  274. package/dist/web/hooks/use-send-follow-up-message.d.ts +19 -1
  275. package/dist/web/hooks/use-send-follow-up-message.js +19 -2
  276. package/dist/web/hooks/use-send-follow-up-message.js.map +1 -1
  277. package/dist/web/hooks/use-set-open-in-app-url.d.ts +17 -0
  278. package/dist/web/hooks/use-set-open-in-app-url.js +17 -0
  279. package/dist/web/hooks/use-set-open-in-app-url.js.map +1 -1
  280. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  281. package/dist/web/hooks/use-tool-info.d.ts +53 -2
  282. package/dist/web/hooks/use-tool-info.js +30 -7
  283. package/dist/web/hooks/use-tool-info.js.map +1 -1
  284. package/dist/web/hooks/use-tool-info.test-d.js +11 -29
  285. package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
  286. package/dist/web/hooks/use-tool-info.test.js +5 -5
  287. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  288. package/dist/web/hooks/use-user.d.ts +2 -0
  289. package/dist/web/hooks/use-user.js +2 -0
  290. package/dist/web/hooks/use-user.js.map +1 -1
  291. package/dist/web/hooks/use-user.test.js.map +1 -1
  292. package/dist/web/hooks/use-view-state.d.ts +25 -0
  293. package/dist/web/hooks/use-view-state.js +32 -0
  294. package/dist/web/hooks/use-view-state.js.map +1 -0
  295. package/dist/web/hooks/use-view-state.test.d.ts +1 -0
  296. package/dist/web/hooks/{use-widget-state.test.js → use-view-state.test.js} +17 -17
  297. package/dist/web/hooks/use-view-state.test.js.map +1 -0
  298. package/dist/web/index.d.ts +1 -2
  299. package/dist/web/index.js +1 -2
  300. package/dist/web/index.js.map +1 -1
  301. package/dist/web/mount-view.d.ts +20 -0
  302. package/dist/web/{mount-widget.js → mount-view.js} +21 -2
  303. package/dist/web/mount-view.js.map +1 -0
  304. package/dist/web/plugin/data-llm.test.js.map +1 -1
  305. package/dist/web/plugin/plugin.d.ts +32 -1
  306. package/dist/web/plugin/plugin.js +160 -25
  307. package/dist/web/plugin/plugin.js.map +1 -1
  308. package/dist/web/plugin/scan-views.d.ts +16 -0
  309. package/dist/web/plugin/scan-views.js +88 -0
  310. package/dist/web/plugin/scan-views.js.map +1 -0
  311. package/dist/web/plugin/scan-views.test.d.ts +1 -0
  312. package/dist/web/plugin/scan-views.test.js +99 -0
  313. package/dist/web/plugin/scan-views.test.js.map +1 -0
  314. package/dist/web/plugin/transform-data-llm.js.map +1 -1
  315. package/dist/web/plugin/transform-data-llm.test.js.map +1 -1
  316. package/dist/web/plugin/validate-view.d.ts +1 -0
  317. package/dist/web/plugin/validate-view.js +9 -0
  318. package/dist/web/plugin/validate-view.js.map +1 -0
  319. package/dist/web/plugin/validate-view.test.d.ts +1 -0
  320. package/dist/web/plugin/validate-view.test.js +24 -0
  321. package/dist/web/plugin/validate-view.test.js.map +1 -0
  322. package/dist/web/proxy.js.map +1 -1
  323. package/dist/web/types.d.ts +4 -0
  324. package/dist/web/types.js.map +1 -1
  325. package/package.json +26 -10
  326. package/tsconfig.base.json +2 -0
  327. package/dist/server/templates/development.hbs +0 -12
  328. package/dist/server/templates/production.hbs +0 -6
  329. package/dist/server/widgetsDevServer.d.ts +0 -13
  330. package/dist/server/widgetsDevServer.js +0 -52
  331. package/dist/server/widgetsDevServer.js.map +0 -1
  332. package/dist/test/widget.test.js +0 -303
  333. package/dist/test/widget.test.js.map +0 -1
  334. package/dist/web/hooks/use-widget-state.d.ts +0 -4
  335. package/dist/web/hooks/use-widget-state.js +0 -32
  336. package/dist/web/hooks/use-widget-state.js.map +0 -1
  337. package/dist/web/hooks/use-widget-state.test.js.map +0 -1
  338. package/dist/web/mount-widget.d.ts +0 -1
  339. package/dist/web/mount-widget.js.map +0 -1
  340. package/dist/web/plugin/validate-widget.d.ts +0 -5
  341. package/dist/web/plugin/validate-widget.js +0 -27
  342. package/dist/web/plugin/validate-widget.js.map +0 -1
  343. package/dist/web/plugin/validate-widget.test.js +0 -42
  344. package/dist/web/plugin/validate-widget.test.js.map +0 -1
  345. /package/dist/{test/widget.test.d.ts → cli/build-helpers.test.d.ts} +0 -0
  346. /package/dist/{web/hooks/use-widget-state.test.d.ts → cli/tunnel-control-server.test.d.ts} +0 -0
  347. /package/dist/{web/plugin/validate-widget.test.d.ts → cli/tunnel-handler.test.d.ts} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.test.js","sourceRoot":"","sources":["../../../src/server/auth/discovery.test.ts"],"names":[],"mappings":"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","sourcesContent":["// @vitest-environment node\nimport http from \"node:http\";\nimport { afterEach, describe, expect, it } from \"vitest\";\nimport { discoverAuthorizationServer } from \"./discovery.js\";\n\nlet server: http.Server | undefined;\nafterEach(() => server?.close());\n\nfunction doc(origin: string, extra: Record<string, unknown> = {}) {\n return {\n issuer: origin,\n authorization_endpoint: `${origin}/authorize`,\n token_endpoint: `${origin}/token`,\n registration_endpoint: `${origin}/register`,\n response_types_supported: [\"code\"],\n scopes_supported: [\"openid\", \"email\"],\n jwks_uri: `${origin}/jwks`,\n ...extra,\n };\n}\n\n// Serves JSON bodies built from the live origin at their well-known paths;\n// returns the origin URL.\nasync function serve(build: (origin: string) => Record<string, unknown>) {\n let origin = \"\";\n const srv = http.createServer((req, res) => {\n const body = build(origin)[req.url ?? \"\"];\n if (body === undefined) {\n res.writeHead(404).end();\n return;\n }\n res.setHeader(\"content-type\", \"application/json\");\n res.end(JSON.stringify(body));\n });\n await new Promise<void>((resolve) => srv.listen(0, resolve));\n server = srv;\n origin = `http://localhost:${(srv.address() as { port: number }).port}`;\n return origin;\n}\n\ndescribe(\"discoverAuthorizationServer\", () => {\n it(\"fetches and validates openid-configuration\", async () => {\n const base = await serve((o) => ({\n \"/.well-known/openid-configuration\": doc(o),\n }));\n const meta = await discoverAuthorizationServer(base);\n expect(meta.registration_endpoint).toBe(`${base}/register`);\n expect(meta.jwks_uri).toBe(`${base}/jwks`);\n });\n\n it(\"accepts a canonically slash-terminated issuer\", async () => {\n const base = await serve((o) => ({\n \"/.well-known/openid-configuration\": doc(o, { issuer: `${o}/` }),\n }));\n const meta = await discoverAuthorizationServer(base);\n expect(meta.registration_endpoint).toBe(`${base}/register`);\n });\n\n it(\"falls back to oauth-authorization-server when oidc is 404\", async () => {\n const base = await serve((o) => ({\n \"/.well-known/oauth-authorization-server\": doc(o),\n }));\n const meta = await discoverAuthorizationServer(base);\n expect(meta.registration_endpoint).toBe(`${base}/register`);\n });\n\n it(\"throws when no well-known doc is reachable\", async () => {\n const base = await serve(() => ({}));\n await expect(discoverAuthorizationServer(base)).rejects.toThrow(\n /discovery failed/i,\n );\n });\n\n it(\"rejects a doc whose issuer does not match the fetch origin\", async () => {\n const base = await serve(() => ({\n \"/.well-known/openid-configuration\": doc(\"https://evil.test\"),\n }));\n await expect(discoverAuthorizationServer(base)).rejects.toThrow(\n /discovery failed/i,\n );\n });\n});\n"]}
@@ -0,0 +1,18 @@
1
+ import type { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
2
+ import type { JwksVerifyConfig } from "./verify.js";
3
+ /** Resource-server OAuth config for `SkybridgeServerOptions.oauth`. */
4
+ export type OAuthConfig = {
5
+ /**
6
+ * Public URL of this server; sets `resourceServerUrl` and the
7
+ * `resource_metadata` URL. When omitted, it is inferred per request from
8
+ * `x-forwarded-host`/`origin`/`host` headers.
9
+ */
10
+ baseUrl?: string;
11
+ /** AS metadata served at `/.well-known/oauth-authorization-server`. */
12
+ oauthMetadata: OAuthMetadata;
13
+ verify: JwksVerifyConfig;
14
+ /** Scopes advertised in protected-resource metadata. */
15
+ scopesSupported?: string[];
16
+ /** Server-wide required-scope floor. */
17
+ requiredScopes?: string[];
18
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/server/auth/index.ts"],"names":[],"mappings":"","sourcesContent":["import type { OAuthMetadata } from \"@modelcontextprotocol/sdk/shared/auth.js\";\nimport type { JwksVerifyConfig } from \"./verify.js\";\n\n/** Resource-server OAuth config for `SkybridgeServerOptions.oauth`. */\nexport type OAuthConfig = {\n /**\n * Public URL of this server; sets `resourceServerUrl` and the\n * `resource_metadata` URL. When omitted, it is inferred per request from\n * `x-forwarded-host`/`origin`/`host` headers.\n */\n baseUrl?: string;\n /** AS metadata served at `/.well-known/oauth-authorization-server`. */\n oauthMetadata: OAuthMetadata;\n verify: JwksVerifyConfig;\n /** Scopes advertised in protected-resource metadata. */\n scopesSupported?: string[];\n /** Server-wide required-scope floor. */\n requiredScopes?: string[];\n};\n"]}
@@ -0,0 +1,12 @@
1
+ import type { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
2
+ import type { OAuthConfig } from "../index.js";
3
+ /** Builds a complete {@link OAuthConfig} for a DCR-compatible IdP via discovery. */
4
+ export declare function customProvider(opts: {
5
+ issuer: string;
6
+ audience: string;
7
+ /** Omit to let the server infer the resource origin from request headers. */
8
+ baseUrl?: string;
9
+ scopes?: string[];
10
+ requiredScopes?: string[];
11
+ metadataOverrides?: Omit<Partial<OAuthMetadata>, "issuer">;
12
+ }): Promise<OAuthConfig>;
@@ -0,0 +1,28 @@
1
+ import { discoverAuthorizationServer, } from "../discovery.js";
2
+ /** Builds a complete {@link OAuthConfig} for a DCR-compatible IdP via discovery. */
3
+ export async function customProvider(opts) {
4
+ const discovered = await discoverAuthorizationServer(opts.issuer);
5
+ // The IdP's own discovery must advertise DCR and a JWKS; overrides can't fake it.
6
+ if (!discovered.registration_endpoint) {
7
+ throw new Error(`${opts.issuer} is not DCR-compatible (no registration_endpoint); customProvider only supports DCR IdPs.`);
8
+ }
9
+ if (!discovered.jwks_uri) {
10
+ throw new Error(`${opts.issuer} discovery has no jwks_uri; JWKS verification requires it.`);
11
+ }
12
+ // Overrides adjust only advertised metadata (e.g. proxied endpoints). The
13
+ // trust anchor (issuer, jwks_uri) always comes from validated discovery.
14
+ const { issuer: _issuer, jwks_uri: _jwks, ...overrides } = opts.metadataOverrides ?? {};
15
+ const oauthMetadata = { ...discovered, ...overrides };
16
+ return {
17
+ baseUrl: opts.baseUrl,
18
+ oauthMetadata,
19
+ verify: {
20
+ issuer: discovered.issuer,
21
+ audience: opts.audience,
22
+ jwksUri: discovered.jwks_uri,
23
+ },
24
+ scopesSupported: opts.scopes ?? oauthMetadata.scopes_supported,
25
+ requiredScopes: opts.requiredScopes,
26
+ };
27
+ }
28
+ //# sourceMappingURL=custom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom.js","sourceRoot":"","sources":["../../../../src/server/auth/providers/custom.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,2BAA2B,GAC5B,MAAM,iBAAiB,CAAC;AAGzB,oFAAoF;AACpF,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAQpC;IACC,MAAM,UAAU,GAAG,MAAM,2BAA2B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAElE,kFAAkF;IAClF,IAAI,CAAC,UAAU,CAAC,qBAAqB,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,CAAC,MAAM,2FAA2F,CAC1G,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,CAAC,MAAM,4DAA4D,CAC3E,CAAC;IACJ,CAAC;IAED,0EAA0E;IAC1E,yEAAyE;IACzE,MAAM,EACJ,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,KAAK,EACf,GAAG,SAAS,EACb,GAAgC,IAAI,CAAC,iBAAiB,IAAI,EAAE,CAAC;IAC9D,MAAM,aAAa,GAAuB,EAAE,GAAG,UAAU,EAAE,GAAG,SAAS,EAAE,CAAC;IAE1E,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,aAAa;QACb,MAAM,EAAE;YACN,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,OAAO,EAAE,UAAU,CAAC,QAAQ;SAC7B;QACD,eAAe,EAAE,IAAI,CAAC,MAAM,IAAI,aAAa,CAAC,gBAAgB;QAC9D,cAAc,EAAE,IAAI,CAAC,cAAc;KACpC,CAAC;AACJ,CAAC","sourcesContent":["import type { OAuthMetadata } from \"@modelcontextprotocol/sdk/shared/auth.js\";\nimport {\n type DiscoveredMetadata,\n discoverAuthorizationServer,\n} from \"../discovery.js\";\nimport type { OAuthConfig } from \"../index.js\";\n\n/** Builds a complete {@link OAuthConfig} for a DCR-compatible IdP via discovery. */\nexport async function customProvider(opts: {\n issuer: string;\n audience: string;\n /** Omit to let the server infer the resource origin from request headers. */\n baseUrl?: string;\n scopes?: string[];\n requiredScopes?: string[];\n metadataOverrides?: Omit<Partial<OAuthMetadata>, \"issuer\">;\n}): Promise<OAuthConfig> {\n const discovered = await discoverAuthorizationServer(opts.issuer);\n\n // The IdP's own discovery must advertise DCR and a JWKS; overrides can't fake it.\n if (!discovered.registration_endpoint) {\n throw new Error(\n `${opts.issuer} is not DCR-compatible (no registration_endpoint); customProvider only supports DCR IdPs.`,\n );\n }\n if (!discovered.jwks_uri) {\n throw new Error(\n `${opts.issuer} discovery has no jwks_uri; JWKS verification requires it.`,\n );\n }\n\n // Overrides adjust only advertised metadata (e.g. proxied endpoints). The\n // trust anchor (issuer, jwks_uri) always comes from validated discovery.\n const {\n issuer: _issuer,\n jwks_uri: _jwks,\n ...overrides\n }: Partial<DiscoveredMetadata> = opts.metadataOverrides ?? {};\n const oauthMetadata: DiscoveredMetadata = { ...discovered, ...overrides };\n\n return {\n baseUrl: opts.baseUrl,\n oauthMetadata,\n verify: {\n issuer: discovered.issuer,\n audience: opts.audience,\n jwksUri: discovered.jwks_uri,\n },\n scopesSupported: opts.scopes ?? oauthMetadata.scopes_supported,\n requiredScopes: opts.requiredScopes,\n };\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,91 @@
1
+ // @vitest-environment node
2
+ import http from "node:http";
3
+ import { afterEach, describe, expect, it } from "vitest";
4
+ import { customProvider } from "./custom.js";
5
+ let server;
6
+ afterEach(() => server?.close());
7
+ function doc(origin, extra = {}) {
8
+ return {
9
+ issuer: origin,
10
+ authorization_endpoint: `${origin}/authorize`,
11
+ token_endpoint: `${origin}/token`,
12
+ registration_endpoint: `${origin}/register`,
13
+ response_types_supported: ["code"],
14
+ scopes_supported: ["openid", "email", "profile"],
15
+ jwks_uri: `${origin}/jwks`,
16
+ ...extra,
17
+ };
18
+ }
19
+ // Serves the discovery doc (built from the live origin) at the OIDC well-known path.
20
+ async function serveDiscovery(extra = {}) {
21
+ let origin = "";
22
+ const srv = http.createServer((req, res) => {
23
+ if (req.url !== "/.well-known/openid-configuration") {
24
+ res.writeHead(404).end();
25
+ return;
26
+ }
27
+ res.setHeader("content-type", "application/json");
28
+ res.end(JSON.stringify(doc(origin, extra)));
29
+ });
30
+ await new Promise((resolve) => srv.listen(0, resolve));
31
+ server = srv;
32
+ origin = `http://localhost:${srv.address().port}`;
33
+ return origin;
34
+ }
35
+ describe("customProvider", () => {
36
+ it("builds an OAuthConfig from discovery", async () => {
37
+ const base = await serveDiscovery();
38
+ const config = await customProvider({
39
+ issuer: base,
40
+ audience: "my-api",
41
+ baseUrl: "https://app.example.test",
42
+ scopes: ["openid"],
43
+ });
44
+ expect(config.baseUrl).toBe("https://app.example.test");
45
+ expect(config.verify).toEqual({
46
+ issuer: base,
47
+ audience: "my-api",
48
+ jwksUri: `${base}/jwks`,
49
+ });
50
+ expect(config.scopesSupported).toEqual(["openid"]);
51
+ expect(config.oauthMetadata.registration_endpoint).toBe(`${base}/register`);
52
+ });
53
+ it("ignores a runtime issuer override (keeps the discovered trust anchor)", async () => {
54
+ const base = await serveDiscovery();
55
+ const config = await customProvider({
56
+ issuer: base,
57
+ audience: "a",
58
+ metadataOverrides: { issuer: "https://evil.test" },
59
+ });
60
+ expect(config.verify.issuer).toBe(base);
61
+ expect(config.oauthMetadata.issuer).toBe(base);
62
+ });
63
+ it("ignores a runtime jwks_uri override (keeps the discovered signing keys)", async () => {
64
+ const base = await serveDiscovery();
65
+ const config = await customProvider({
66
+ issuer: base,
67
+ audience: "a",
68
+ metadataOverrides: { jwks_uri: "https://evil.test/keys" },
69
+ });
70
+ expect(config.verify.jwksUri).toBe(`${base}/jwks`);
71
+ });
72
+ it("rejects a non-DCR IdP even if an override supplies registration_endpoint", async () => {
73
+ const base = await serveDiscovery({ registration_endpoint: undefined });
74
+ await expect(customProvider({
75
+ issuer: base,
76
+ audience: "a",
77
+ metadataOverrides: { registration_endpoint: `${base}/register` },
78
+ })).rejects.toThrow(/not DCR-compatible/);
79
+ });
80
+ it("applies metadataOverrides over discovered values", async () => {
81
+ const base = await serveDiscovery();
82
+ const config = await customProvider({
83
+ issuer: base,
84
+ audience: "a",
85
+ baseUrl: "https://app.example.test",
86
+ metadataOverrides: { token_endpoint: "https://override/token" },
87
+ });
88
+ expect(config.oauthMetadata.token_endpoint).toBe("https://override/token");
89
+ });
90
+ });
91
+ //# sourceMappingURL=custom.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom.test.js","sourceRoot":"","sources":["../../../../src/server/auth/providers/custom.test.ts"],"names":[],"mappings":"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","sourcesContent":["// @vitest-environment node\nimport http from \"node:http\";\nimport { afterEach, describe, expect, it } from \"vitest\";\nimport { customProvider } from \"./custom.js\";\n\nlet server: http.Server | undefined;\nafterEach(() => server?.close());\n\nfunction doc(origin: string, extra: Record<string, unknown> = {}) {\n return {\n issuer: origin,\n authorization_endpoint: `${origin}/authorize`,\n token_endpoint: `${origin}/token`,\n registration_endpoint: `${origin}/register`,\n response_types_supported: [\"code\"],\n scopes_supported: [\"openid\", \"email\", \"profile\"],\n jwks_uri: `${origin}/jwks`,\n ...extra,\n };\n}\n\n// Serves the discovery doc (built from the live origin) at the OIDC well-known path.\nasync function serveDiscovery(extra: Record<string, unknown> = {}) {\n let origin = \"\";\n const srv = http.createServer((req, res) => {\n if (req.url !== \"/.well-known/openid-configuration\") {\n res.writeHead(404).end();\n return;\n }\n res.setHeader(\"content-type\", \"application/json\");\n res.end(JSON.stringify(doc(origin, extra)));\n });\n await new Promise<void>((resolve) => srv.listen(0, resolve));\n server = srv;\n origin = `http://localhost:${(srv.address() as { port: number }).port}`;\n return origin;\n}\n\ndescribe(\"customProvider\", () => {\n it(\"builds an OAuthConfig from discovery\", async () => {\n const base = await serveDiscovery();\n\n const config = await customProvider({\n issuer: base,\n audience: \"my-api\",\n baseUrl: \"https://app.example.test\",\n scopes: [\"openid\"],\n });\n\n expect(config.baseUrl).toBe(\"https://app.example.test\");\n expect(config.verify).toEqual({\n issuer: base,\n audience: \"my-api\",\n jwksUri: `${base}/jwks`,\n });\n expect(config.scopesSupported).toEqual([\"openid\"]);\n expect(config.oauthMetadata.registration_endpoint).toBe(`${base}/register`);\n });\n\n it(\"ignores a runtime issuer override (keeps the discovered trust anchor)\", async () => {\n const base = await serveDiscovery();\n const config = await customProvider({\n issuer: base,\n audience: \"a\",\n metadataOverrides: { issuer: \"https://evil.test\" } as never,\n });\n expect(config.verify.issuer).toBe(base);\n expect(config.oauthMetadata.issuer).toBe(base);\n });\n\n it(\"ignores a runtime jwks_uri override (keeps the discovered signing keys)\", async () => {\n const base = await serveDiscovery();\n const config = await customProvider({\n issuer: base,\n audience: \"a\",\n metadataOverrides: { jwks_uri: \"https://evil.test/keys\" } as never,\n });\n expect(config.verify.jwksUri).toBe(`${base}/jwks`);\n });\n\n it(\"rejects a non-DCR IdP even if an override supplies registration_endpoint\", async () => {\n const base = await serveDiscovery({ registration_endpoint: undefined });\n await expect(\n customProvider({\n issuer: base,\n audience: \"a\",\n metadataOverrides: { registration_endpoint: `${base}/register` },\n }),\n ).rejects.toThrow(/not DCR-compatible/);\n });\n\n it(\"applies metadataOverrides over discovered values\", async () => {\n const base = await serveDiscovery();\n const config = await customProvider({\n issuer: base,\n audience: \"a\",\n baseUrl: \"https://app.example.test\",\n metadataOverrides: { token_endpoint: \"https://override/token\" },\n });\n expect(config.oauthMetadata.token_endpoint).toBe(\"https://override/token\");\n });\n});\n"]}
@@ -0,0 +1,4 @@
1
+ import type { Express } from "express";
2
+ import type { OAuthConfig } from "./index.js";
3
+ /** Mounts the well-known OAuth metadata and bearer auth on `/mcp`. */
4
+ export declare function setupOAuth(app: Express, config: OAuthConfig): void;
@@ -0,0 +1,51 @@
1
+ import { getOAuthProtectedResourceMetadataUrl, mcpAuthMetadataRouter, } from "@modelcontextprotocol/sdk/server/auth/router.js";
2
+ import cors from "cors";
3
+ import { requireBearerAuth } from "../auth.js";
4
+ import { resolveServerOrigin } from "../requestOrigin.js";
5
+ import { createJwksVerifier } from "./verify.js";
6
+ /** Mounts the well-known OAuth metadata and bearer auth on `/mcp`. */
7
+ export function setupOAuth(app, config) {
8
+ if (!config.verify?.issuer || !config.verify?.audience) {
9
+ throw new Error("oauth.verify requires both `issuer` and `audience`");
10
+ }
11
+ const verifier = createJwksVerifier(config.verify);
12
+ // baseUrl known at boot: bake the resource URLs once, no Host-header trust.
13
+ if (config.baseUrl !== undefined) {
14
+ let baseUrl;
15
+ try {
16
+ baseUrl = new URL(config.baseUrl);
17
+ }
18
+ catch {
19
+ throw new Error(`oauth.baseUrl must be a valid absolute URL, got: ${JSON.stringify(config.baseUrl)}`);
20
+ }
21
+ app.use(mcpAuthMetadataRouter({
22
+ oauthMetadata: config.oauthMetadata,
23
+ resourceServerUrl: baseUrl,
24
+ scopesSupported: config.scopesSupported,
25
+ }));
26
+ app.use("/mcp", requireBearerAuth({
27
+ verifier,
28
+ requiredScopes: config.requiredScopes,
29
+ resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(baseUrl),
30
+ }));
31
+ return;
32
+ }
33
+ // No baseUrl: resolve the resource origin per request from forwarded headers
34
+ // (same precedence the framework uses for view serverUrl). Origin headers are
35
+ // pathless, so the PRM path stays at root, matching the SDK's static layout.
36
+ const resolveOrigin = (req) => new URL(resolveServerOrigin((key) => req.get(key)));
37
+ app.use("/.well-known/oauth-authorization-server", cors(), (_req, res) => void res.json(config.oauthMetadata));
38
+ app.use("/.well-known/oauth-protected-resource", cors(), (req, res) => {
39
+ res.json({
40
+ resource: resolveOrigin(req).href,
41
+ authorization_servers: [config.oauthMetadata.issuer],
42
+ scopes_supported: config.scopesSupported,
43
+ });
44
+ });
45
+ app.use("/mcp", (req, res, next) => requireBearerAuth({
46
+ verifier,
47
+ requiredScopes: config.requiredScopes,
48
+ resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(resolveOrigin(req)),
49
+ })(req, res, next));
50
+ }
51
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../src/server/auth/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oCAAoC,EACpC,qBAAqB,GACtB,MAAM,iDAAiD,CAAC;AACzD,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,sEAAsE;AACtE,MAAM,UAAU,UAAU,CAAC,GAAY,EAAE,MAAmB;IAC1D,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,QAAQ,GAAG,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAEnD,4EAA4E;IAC5E,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACjC,IAAI,OAAY,CAAC;QACjB,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CACb,oDAAoD,IAAI,CAAC,SAAS,CAChE,MAAM,CAAC,OAAO,CACf,EAAE,CACJ,CAAC;QACJ,CAAC;QACD,GAAG,CAAC,GAAG,CACL,qBAAqB,CAAC;YACpB,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,iBAAiB,EAAE,OAAO;YAC1B,eAAe,EAAE,MAAM,CAAC,eAAe;SACxC,CAAC,CACH,CAAC;QACF,GAAG,CAAC,GAAG,CACL,MAAM,EACN,iBAAiB,CAAC;YAChB,QAAQ;YACR,cAAc,EAAE,MAAM,CAAC,cAAc;YACrC,mBAAmB,EAAE,oCAAoC,CAAC,OAAO,CAAC;SACnE,CAAC,CACH,CAAC;QACF,OAAO;IACT,CAAC;IAED,6EAA6E;IAC7E,8EAA8E;IAC9E,6EAA6E;IAC7E,MAAM,aAAa,GAAG,CAAC,GAAY,EAAE,EAAE,CACrC,IAAI,GAAG,CAAC,mBAAmB,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAEtD,GAAG,CAAC,GAAG,CACL,yCAAyC,EACzC,IAAI,EAAE,EACN,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CACnD,CAAC;IACF,GAAG,CAAC,GAAG,CAAC,uCAAuC,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACpE,GAAG,CAAC,IAAI,CAAC;YACP,QAAQ,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI;YACjC,qBAAqB,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC;YACpD,gBAAgB,EAAE,MAAM,CAAC,eAAe;SACzC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,CACjC,iBAAiB,CAAC;QAChB,QAAQ;QACR,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,mBAAmB,EAAE,oCAAoC,CACvD,aAAa,CAAC,GAAG,CAAC,CACnB;KACF,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CACnB,CAAC;AACJ,CAAC","sourcesContent":["import {\n getOAuthProtectedResourceMetadataUrl,\n mcpAuthMetadataRouter,\n} from \"@modelcontextprotocol/sdk/server/auth/router.js\";\nimport cors from \"cors\";\nimport type { Express, Request } from \"express\";\nimport { requireBearerAuth } from \"../auth.js\";\nimport { resolveServerOrigin } from \"../requestOrigin.js\";\nimport type { OAuthConfig } from \"./index.js\";\nimport { createJwksVerifier } from \"./verify.js\";\n\n/** Mounts the well-known OAuth metadata and bearer auth on `/mcp`. */\nexport function setupOAuth(app: Express, config: OAuthConfig): void {\n if (!config.verify?.issuer || !config.verify?.audience) {\n throw new Error(\"oauth.verify requires both `issuer` and `audience`\");\n }\n\n const verifier = createJwksVerifier(config.verify);\n\n // baseUrl known at boot: bake the resource URLs once, no Host-header trust.\n if (config.baseUrl !== undefined) {\n let baseUrl: URL;\n try {\n baseUrl = new URL(config.baseUrl);\n } catch {\n throw new Error(\n `oauth.baseUrl must be a valid absolute URL, got: ${JSON.stringify(\n config.baseUrl,\n )}`,\n );\n }\n app.use(\n mcpAuthMetadataRouter({\n oauthMetadata: config.oauthMetadata,\n resourceServerUrl: baseUrl,\n scopesSupported: config.scopesSupported,\n }),\n );\n app.use(\n \"/mcp\",\n requireBearerAuth({\n verifier,\n requiredScopes: config.requiredScopes,\n resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(baseUrl),\n }),\n );\n return;\n }\n\n // No baseUrl: resolve the resource origin per request from forwarded headers\n // (same precedence the framework uses for view serverUrl). Origin headers are\n // pathless, so the PRM path stays at root, matching the SDK's static layout.\n const resolveOrigin = (req: Request) =>\n new URL(resolveServerOrigin((key) => req.get(key)));\n\n app.use(\n \"/.well-known/oauth-authorization-server\",\n cors(),\n (_req, res) => void res.json(config.oauthMetadata),\n );\n app.use(\"/.well-known/oauth-protected-resource\", cors(), (req, res) => {\n res.json({\n resource: resolveOrigin(req).href,\n authorization_servers: [config.oauthMetadata.issuer],\n scopes_supported: config.scopesSupported,\n });\n });\n app.use(\"/mcp\", (req, res, next) =>\n requireBearerAuth({\n verifier,\n requiredScopes: config.requiredScopes,\n resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(\n resolveOrigin(req),\n ),\n })(req, res, next),\n );\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,185 @@
1
+ // @vitest-environment node
2
+ import http from "node:http";
3
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
5
+ import * as jose from "jose";
6
+ import { afterEach, describe, expect, it, vi } from "vitest";
7
+ import { McpServer } from "../server.js";
8
+ vi.mock("@skybridge/devtools", () => ({
9
+ devtoolsStaticServer: () => ((_req, _res, next) => next()),
10
+ }));
11
+ vi.mock("../viewsDevServer.js", () => ({
12
+ viewsDevServer: (_httpServer) => ((_req, _res, next) => next()),
13
+ }));
14
+ const ISSUER = "https://issuer.test";
15
+ const AUDIENCE = "api://default";
16
+ let jwksServer;
17
+ let appServer;
18
+ afterEach(() => {
19
+ jwksServer?.close();
20
+ appServer?.close();
21
+ });
22
+ async function startJwks() {
23
+ const { publicKey, privateKey } = await jose.generateKeyPair("RS256");
24
+ const jwk = {
25
+ ...(await jose.exportJWK(publicKey)),
26
+ kid: "test-key",
27
+ alg: "RS256",
28
+ use: "sig",
29
+ };
30
+ const server = http.createServer((_req, res) => {
31
+ res.setHeader("content-type", "application/json");
32
+ res.end(JSON.stringify({ keys: [jwk] }));
33
+ });
34
+ await new Promise((resolve) => server.listen(0, resolve));
35
+ jwksServer = server;
36
+ const port = server.address().port;
37
+ return { privateKey, jwksUri: `http://localhost:${port}/jwks` };
38
+ }
39
+ function signToken(key) {
40
+ return new jose.SignJWT({
41
+ client_id: "client-1",
42
+ scope: "openid email",
43
+ sub: "user-1",
44
+ })
45
+ .setProtectedHeader({ alg: "RS256", kid: "test-key" })
46
+ .setIssuer(ISSUER)
47
+ .setAudience(AUDIENCE)
48
+ .setExpirationTime("1h")
49
+ .sign(key);
50
+ }
51
+ async function bootServer(jwksUri, { baseUrl = "https://app.example.test" } = {}) {
52
+ const { createApp } = await import("../express.js");
53
+ const server = new McpServer({ name: "auth-test", version: "0.0.0" }, { capabilities: {} }, {
54
+ oauth: {
55
+ ...(baseUrl === null ? {} : { baseUrl }),
56
+ oauthMetadata: {
57
+ issuer: ISSUER,
58
+ authorization_endpoint: `${ISSUER}/authorize`,
59
+ token_endpoint: `${ISSUER}/token`,
60
+ response_types_supported: ["code"],
61
+ },
62
+ verify: { issuer: ISSUER, audience: AUDIENCE, jwksUri },
63
+ scopesSupported: ["openid", "email"],
64
+ requiredScopes: ["openid"],
65
+ },
66
+ }).registerTool({
67
+ name: "whoami",
68
+ description: "Returns the caller identity.",
69
+ inputSchema: {},
70
+ }, (_args, extra) => ({
71
+ structuredContent: { clientId: extra.authInfo?.clientId ?? null },
72
+ content: [{ type: "text", text: extra.authInfo?.clientId ?? "anon" }],
73
+ }));
74
+ const httpServer = http.createServer();
75
+ await createApp({ mcpServer: server, httpServer });
76
+ const listening = http.createServer(server.express);
77
+ await new Promise((resolve) => listening.listen(0, resolve));
78
+ appServer = listening;
79
+ const port = listening.address().port;
80
+ return `http://localhost:${port}`;
81
+ }
82
+ describe("setupOAuth wiring", () => {
83
+ it("serves protected-resource metadata", async () => {
84
+ const { jwksUri } = await startJwks();
85
+ const base = await bootServer(jwksUri);
86
+ const res = await fetch(`${base}/.well-known/oauth-protected-resource`);
87
+ expect(res.status).toBe(200);
88
+ const body = (await res.json());
89
+ expect(body.resource).toBe("https://app.example.test/");
90
+ expect(body.authorization_servers).toContain(ISSUER);
91
+ expect(body.scopes_supported).toEqual(["openid", "email"]);
92
+ });
93
+ it("rejects /mcp without a token (401 + WWW-Authenticate)", async () => {
94
+ const { jwksUri } = await startJwks();
95
+ const base = await bootServer(jwksUri);
96
+ const res = await fetch(`${base}/mcp`, {
97
+ method: "POST",
98
+ headers: { "Content-Type": "application/json" },
99
+ body: JSON.stringify({ jsonrpc: "2.0", method: "initialize", id: 1 }),
100
+ });
101
+ expect(res.status).toBe(401);
102
+ expect(res.headers.get("www-authenticate")).toMatch(/resource_metadata=/);
103
+ });
104
+ it("threads authInfo into the tool handler for a valid token", async () => {
105
+ const { privateKey, jwksUri } = await startJwks();
106
+ const base = await bootServer(jwksUri);
107
+ const token = await signToken(privateKey);
108
+ const client = new Client({ name: "test-client", version: "0.0.0" });
109
+ const transport = new StreamableHTTPClientTransport(new URL(`${base}/mcp`), { requestInit: { headers: { Authorization: `Bearer ${token}` } } });
110
+ await client.connect(transport);
111
+ const result = (await client.callTool({
112
+ name: "whoami",
113
+ arguments: {},
114
+ }));
115
+ expect(result.content[0]?.text).toBe("client-1");
116
+ await client.close();
117
+ });
118
+ });
119
+ describe("baseUrl inferred from headers", () => {
120
+ it("derives the resource origin from x-forwarded-host", async () => {
121
+ const { jwksUri } = await startJwks();
122
+ const base = await bootServer(jwksUri, { baseUrl: null });
123
+ const res = await fetch(`${base}/.well-known/oauth-protected-resource`, {
124
+ headers: { "x-forwarded-host": "infer.example.test" },
125
+ });
126
+ expect(res.status).toBe(200);
127
+ const body = (await res.json());
128
+ expect(body.resource).toBe("https://infer.example.test/");
129
+ });
130
+ it("uses the first hop of a forwarded-host chain", async () => {
131
+ const { jwksUri } = await startJwks();
132
+ const base = await bootServer(jwksUri, { baseUrl: null });
133
+ const res = await fetch(`${base}/.well-known/oauth-protected-resource`, {
134
+ headers: {
135
+ "x-forwarded-host": "public.example, internal.local",
136
+ "x-forwarded-proto": "https, http",
137
+ },
138
+ });
139
+ expect(res.status).toBe(200);
140
+ const body = (await res.json());
141
+ expect(body.resource).toBe("https://public.example/");
142
+ });
143
+ it("ignores the client Origin header, using Host instead", async () => {
144
+ const { jwksUri } = await startJwks();
145
+ const base = await bootServer(jwksUri, { baseUrl: null });
146
+ const res = await fetch(`${base}/.well-known/oauth-protected-resource`, {
147
+ headers: { origin: "https://chatgpt.com" },
148
+ });
149
+ expect(res.status).toBe(200);
150
+ const body = (await res.json());
151
+ expect(body.resource).toMatch(/^http:\/\/(localhost|127\.0\.0\.1):/);
152
+ });
153
+ it("points the 401 WWW-Authenticate at the inferred host", async () => {
154
+ const { jwksUri } = await startJwks();
155
+ const base = await bootServer(jwksUri, { baseUrl: null });
156
+ const res = await fetch(`${base}/mcp`, {
157
+ method: "POST",
158
+ headers: {
159
+ "Content-Type": "application/json",
160
+ "x-forwarded-host": "infer.example.test",
161
+ },
162
+ body: JSON.stringify({ jsonrpc: "2.0", method: "initialize", id: 1 }),
163
+ });
164
+ expect(res.status).toBe(401);
165
+ expect(res.headers.get("www-authenticate")).toMatch(/resource_metadata="https:\/\/infer\.example\.test\//);
166
+ });
167
+ });
168
+ describe("oauth config validation", () => {
169
+ const validMetadata = {
170
+ issuer: ISSUER,
171
+ authorization_endpoint: `${ISSUER}/authorize`,
172
+ token_endpoint: `${ISSUER}/token`,
173
+ response_types_supported: ["code"],
174
+ };
175
+ it("throws on a non-absolute baseUrl", () => {
176
+ expect(() => new McpServer({ name: "t", version: "0" }, undefined, {
177
+ oauth: {
178
+ baseUrl: "not-a-url",
179
+ oauthMetadata: validMetadata,
180
+ verify: { issuer: ISSUER, audience: AUDIENCE },
181
+ },
182
+ })).toThrow(/baseUrl must be a valid absolute URL/);
183
+ });
184
+ });
185
+ //# sourceMappingURL=setup.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.test.js","sourceRoot":"","sources":["../../../src/server/auth/setup.test.ts"],"names":[],"mappings":"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","sourcesContent":["// @vitest-environment node\nimport http from \"node:http\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport type { RequestHandler } from \"express\";\nimport * as jose from \"jose\";\nimport { afterEach, describe, expect, it, vi } from \"vitest\";\nimport { McpServer } from \"../server.js\";\n\nvi.mock(\"@skybridge/devtools\", () => ({\n devtoolsStaticServer: () =>\n ((_req: unknown, _res: unknown, next: () => void) =>\n next()) as RequestHandler,\n}));\nvi.mock(\"../viewsDevServer.js\", () => ({\n viewsDevServer: (_httpServer: unknown) =>\n ((_req: unknown, _res: unknown, next: () => void) =>\n next()) as RequestHandler,\n}));\n\nconst ISSUER = \"https://issuer.test\";\nconst AUDIENCE = \"api://default\";\n\nlet jwksServer: http.Server | undefined;\nlet appServer: http.Server | undefined;\nafterEach(() => {\n jwksServer?.close();\n appServer?.close();\n});\n\nasync function startJwks() {\n const { publicKey, privateKey } = await jose.generateKeyPair(\"RS256\");\n const jwk = {\n ...(await jose.exportJWK(publicKey)),\n kid: \"test-key\",\n alg: \"RS256\",\n use: \"sig\",\n };\n const server = http.createServer((_req, res) => {\n res.setHeader(\"content-type\", \"application/json\");\n res.end(JSON.stringify({ keys: [jwk] }));\n });\n await new Promise<void>((resolve) => server.listen(0, resolve));\n jwksServer = server;\n const port = (server.address() as { port: number }).port;\n return { privateKey, jwksUri: `http://localhost:${port}/jwks` };\n}\n\nfunction signToken(key: CryptoKey) {\n return new jose.SignJWT({\n client_id: \"client-1\",\n scope: \"openid email\",\n sub: \"user-1\",\n })\n .setProtectedHeader({ alg: \"RS256\", kid: \"test-key\" })\n .setIssuer(ISSUER)\n .setAudience(AUDIENCE)\n .setExpirationTime(\"1h\")\n .sign(key);\n}\n\nasync function bootServer(\n jwksUri: string,\n { baseUrl = \"https://app.example.test\" }: { baseUrl?: string | null } = {},\n) {\n const { createApp } = await import(\"../express.js\");\n const server = new McpServer(\n { name: \"auth-test\", version: \"0.0.0\" },\n { capabilities: {} },\n {\n oauth: {\n ...(baseUrl === null ? {} : { baseUrl }),\n oauthMetadata: {\n issuer: ISSUER,\n authorization_endpoint: `${ISSUER}/authorize`,\n token_endpoint: `${ISSUER}/token`,\n response_types_supported: [\"code\"],\n },\n verify: { issuer: ISSUER, audience: AUDIENCE, jwksUri },\n scopesSupported: [\"openid\", \"email\"],\n requiredScopes: [\"openid\"],\n },\n },\n ).registerTool(\n {\n name: \"whoami\",\n description: \"Returns the caller identity.\",\n inputSchema: {},\n },\n (_args, extra) => ({\n structuredContent: { clientId: extra.authInfo?.clientId ?? null },\n content: [{ type: \"text\", text: extra.authInfo?.clientId ?? \"anon\" }],\n }),\n );\n\n const httpServer = http.createServer();\n await createApp({ mcpServer: server, httpServer });\n const listening = http.createServer(server.express);\n await new Promise<void>((resolve) => listening.listen(0, resolve));\n appServer = listening;\n const port = (listening.address() as { port: number }).port;\n return `http://localhost:${port}`;\n}\n\ndescribe(\"setupOAuth wiring\", () => {\n it(\"serves protected-resource metadata\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootServer(jwksUri);\n\n const res = await fetch(`${base}/.well-known/oauth-protected-resource`);\n expect(res.status).toBe(200);\n const body = (await res.json()) as {\n resource: string;\n authorization_servers: string[];\n scopes_supported: string[];\n };\n expect(body.resource).toBe(\"https://app.example.test/\");\n expect(body.authorization_servers).toContain(ISSUER);\n expect(body.scopes_supported).toEqual([\"openid\", \"email\"]);\n });\n\n it(\"rejects /mcp without a token (401 + WWW-Authenticate)\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootServer(jwksUri);\n\n const res = await fetch(`${base}/mcp`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ jsonrpc: \"2.0\", method: \"initialize\", id: 1 }),\n });\n expect(res.status).toBe(401);\n expect(res.headers.get(\"www-authenticate\")).toMatch(/resource_metadata=/);\n });\n\n it(\"threads authInfo into the tool handler for a valid token\", async () => {\n const { privateKey, jwksUri } = await startJwks();\n const base = await bootServer(jwksUri);\n const token = await signToken(privateKey);\n\n const client = new Client({ name: \"test-client\", version: \"0.0.0\" });\n const transport = new StreamableHTTPClientTransport(\n new URL(`${base}/mcp`),\n { requestInit: { headers: { Authorization: `Bearer ${token}` } } },\n );\n await client.connect(transport);\n\n const result = (await client.callTool({\n name: \"whoami\",\n arguments: {},\n })) as unknown as {\n content: { type: string; text: string }[];\n };\n expect(result.content[0]?.text).toBe(\"client-1\");\n\n await client.close();\n });\n});\n\ndescribe(\"baseUrl inferred from headers\", () => {\n it(\"derives the resource origin from x-forwarded-host\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootServer(jwksUri, { baseUrl: null });\n\n const res = await fetch(`${base}/.well-known/oauth-protected-resource`, {\n headers: { \"x-forwarded-host\": \"infer.example.test\" },\n });\n expect(res.status).toBe(200);\n const body = (await res.json()) as { resource: string };\n expect(body.resource).toBe(\"https://infer.example.test/\");\n });\n\n it(\"uses the first hop of a forwarded-host chain\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootServer(jwksUri, { baseUrl: null });\n\n const res = await fetch(`${base}/.well-known/oauth-protected-resource`, {\n headers: {\n \"x-forwarded-host\": \"public.example, internal.local\",\n \"x-forwarded-proto\": \"https, http\",\n },\n });\n expect(res.status).toBe(200);\n const body = (await res.json()) as { resource: string };\n expect(body.resource).toBe(\"https://public.example/\");\n });\n\n it(\"ignores the client Origin header, using Host instead\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootServer(jwksUri, { baseUrl: null });\n\n const res = await fetch(`${base}/.well-known/oauth-protected-resource`, {\n headers: { origin: \"https://chatgpt.com\" },\n });\n expect(res.status).toBe(200);\n const body = (await res.json()) as { resource: string };\n expect(body.resource).toMatch(/^http:\\/\\/(localhost|127\\.0\\.0\\.1):/);\n });\n\n it(\"points the 401 WWW-Authenticate at the inferred host\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootServer(jwksUri, { baseUrl: null });\n\n const res = await fetch(`${base}/mcp`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-forwarded-host\": \"infer.example.test\",\n },\n body: JSON.stringify({ jsonrpc: \"2.0\", method: \"initialize\", id: 1 }),\n });\n expect(res.status).toBe(401);\n expect(res.headers.get(\"www-authenticate\")).toMatch(\n /resource_metadata=\"https:\\/\\/infer\\.example\\.test\\//,\n );\n });\n});\n\ndescribe(\"oauth config validation\", () => {\n const validMetadata = {\n issuer: ISSUER,\n authorization_endpoint: `${ISSUER}/authorize`,\n token_endpoint: `${ISSUER}/token`,\n response_types_supported: [\"code\"],\n };\n\n it(\"throws on a non-absolute baseUrl\", () => {\n expect(\n () =>\n new McpServer({ name: \"t\", version: \"0\" }, undefined, {\n oauth: {\n baseUrl: \"not-a-url\",\n oauthMetadata: validMetadata,\n verify: { issuer: ISSUER, audience: AUDIENCE },\n },\n }),\n ).toThrow(/baseUrl must be a valid absolute URL/);\n });\n});\n"]}
@@ -0,0 +1,11 @@
1
+ import type { OAuthTokenVerifier } from "@modelcontextprotocol/sdk/server/auth/provider.js";
2
+ export type JwksVerifyConfig = {
3
+ /** Expected `iss` claim. */
4
+ issuer: string;
5
+ /** Expected `aud` claim. */
6
+ audience: string;
7
+ /** Defaults to `${issuer}/.well-known/jwks.json`. */
8
+ jwksUri?: string;
9
+ };
10
+ /** Builds an `OAuthTokenVerifier` validating JWTs against a remote JWKS. Internal, not exported. */
11
+ export declare function createJwksVerifier(config: JwksVerifyConfig): OAuthTokenVerifier;
@@ -0,0 +1,37 @@
1
+ import { InvalidTokenError } from "@modelcontextprotocol/sdk/server/auth/errors.js";
2
+ import * as jose from "jose";
3
+ /** Builds an `OAuthTokenVerifier` validating JWTs against a remote JWKS. Internal, not exported. */
4
+ export function createJwksVerifier(config) {
5
+ const jwksUri = config.jwksUri ??
6
+ `${config.issuer.replace(/\/$/, "")}/.well-known/jwks.json`;
7
+ const jwks = jose.createRemoteJWKSet(new URL(jwksUri));
8
+ return {
9
+ async verifyAccessToken(token) {
10
+ let payload;
11
+ try {
12
+ ({ payload } = await jose.jwtVerify(token, jwks, {
13
+ issuer: config.issuer,
14
+ audience: config.audience,
15
+ }));
16
+ }
17
+ catch (err) {
18
+ const message = err instanceof Error ? err.message : String(err);
19
+ throw new InvalidTokenError(`Token verification failed: ${message}`);
20
+ }
21
+ const { client_id, scope, exp, sub, ...rest } = payload;
22
+ const scopes = Array.isArray(scope)
23
+ ? scope.map(String)
24
+ : typeof scope === "string"
25
+ ? scope.split(/\s+/).filter(Boolean)
26
+ : [];
27
+ return {
28
+ token,
29
+ clientId: client_id ?? "",
30
+ scopes,
31
+ expiresAt: exp,
32
+ extra: { subject: sub, ...rest },
33
+ };
34
+ },
35
+ };
36
+ }
37
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../../../src/server/auth/verify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iDAAiD,CAAC;AAGpF,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAW7B,oGAAoG;AACpG,MAAM,UAAU,kBAAkB,CAChC,MAAwB;IAExB,MAAM,OAAO,GACX,MAAM,CAAC,OAAO;QACd,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,wBAAwB,CAAC;IAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IAEvD,OAAO;QACL,KAAK,CAAC,iBAAiB,CAAC,KAAa;YACnC,IAAI,OAAwB,CAAC;YAC7B,IAAI,CAAC;gBACH,CAAC,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE;oBAC/C,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;iBAC1B,CAAC,CAAC,CAAC;YACN,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACjE,MAAM,IAAI,iBAAiB,CAAC,8BAA8B,OAAO,EAAE,CAAC,CAAC;YACvE,CAAC;YAED,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,OAQ/C,CAAC;YAEF,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;gBACjC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC;gBACnB,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ;oBACzB,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;oBACpC,CAAC,CAAC,EAAE,CAAC;YAET,OAAO;gBACL,KAAK;gBACL,QAAQ,EAAE,SAAS,IAAI,EAAE;gBACzB,MAAM;gBACN,SAAS,EAAE,GAAG;gBACd,KAAK,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE;aACd,CAAC;QACvB,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { InvalidTokenError } from \"@modelcontextprotocol/sdk/server/auth/errors.js\";\nimport type { OAuthTokenVerifier } from \"@modelcontextprotocol/sdk/server/auth/provider.js\";\nimport type { AuthInfo } from \"@modelcontextprotocol/sdk/server/auth/types.js\";\nimport * as jose from \"jose\";\n\nexport type JwksVerifyConfig = {\n /** Expected `iss` claim. */\n issuer: string;\n /** Expected `aud` claim. */\n audience: string;\n /** Defaults to `${issuer}/.well-known/jwks.json`. */\n jwksUri?: string;\n};\n\n/** Builds an `OAuthTokenVerifier` validating JWTs against a remote JWKS. Internal, not exported. */\nexport function createJwksVerifier(\n config: JwksVerifyConfig,\n): OAuthTokenVerifier {\n const jwksUri =\n config.jwksUri ??\n `${config.issuer.replace(/\\/$/, \"\")}/.well-known/jwks.json`;\n const jwks = jose.createRemoteJWKSet(new URL(jwksUri));\n\n return {\n async verifyAccessToken(token: string): Promise<AuthInfo> {\n let payload: jose.JWTPayload;\n try {\n ({ payload } = await jose.jwtVerify(token, jwks, {\n issuer: config.issuer,\n audience: config.audience,\n }));\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new InvalidTokenError(`Token verification failed: ${message}`);\n }\n\n const { client_id, scope, exp, sub, ...rest } = payload as Record<\n string,\n unknown\n > & {\n client_id?: string;\n scope?: string | string[];\n exp?: number;\n sub?: string;\n };\n\n const scopes = Array.isArray(scope)\n ? scope.map(String)\n : typeof scope === \"string\"\n ? scope.split(/\\s+/).filter(Boolean)\n : [];\n\n return {\n token,\n clientId: client_id ?? \"\",\n scopes,\n expiresAt: exp,\n extra: { subject: sub, ...rest },\n } satisfies AuthInfo;\n },\n };\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};