skybridge 0.0.0-dev.fb0f54e → 0.0.0-dev.fb11426

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 (364) hide show
  1. package/README.md +152 -0
  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 +18 -0
  8. package/dist/cli/detect-port.js +50 -0
  9. package/dist/cli/detect-port.js.map +1 -0
  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.d.ts +1 -0
  26. package/dist/cli/tunnel-handler.test.js +105 -0
  27. package/dist/cli/tunnel-handler.test.js.map +1 -0
  28. package/dist/cli/tunnel.d.ts +57 -0
  29. package/dist/cli/tunnel.js +154 -0
  30. package/dist/cli/tunnel.js.map +1 -0
  31. package/dist/cli/tunnel.test.d.ts +1 -0
  32. package/dist/cli/tunnel.test.js +190 -0
  33. package/dist/cli/tunnel.test.js.map +1 -0
  34. package/dist/cli/types.d.ts +5 -0
  35. package/dist/cli/types.js +2 -0
  36. package/dist/cli/types.js.map +1 -0
  37. package/dist/cli/use-execute-steps.d.ts +2 -1
  38. package/dist/cli/use-execute-steps.js +6 -1
  39. package/dist/cli/use-execute-steps.js.map +1 -1
  40. package/dist/cli/use-messages.d.ts +3 -0
  41. package/dist/cli/use-messages.js +11 -0
  42. package/dist/cli/use-messages.js.map +1 -0
  43. package/dist/cli/use-nodemon.d.ts +2 -0
  44. package/dist/cli/use-nodemon.js +73 -0
  45. package/dist/cli/use-nodemon.js.map +1 -0
  46. package/dist/cli/use-open-browser.d.ts +1 -0
  47. package/dist/cli/use-open-browser.js +44 -0
  48. package/dist/cli/use-open-browser.js.map +1 -0
  49. package/dist/cli/use-tunnel.d.ts +14 -0
  50. package/dist/cli/use-tunnel.js +131 -0
  51. package/dist/cli/use-tunnel.js.map +1 -0
  52. package/dist/cli/use-typescript-check.d.ts +9 -0
  53. package/dist/cli/use-typescript-check.js +94 -0
  54. package/dist/cli/use-typescript-check.js.map +1 -0
  55. package/dist/commands/build.d.ts +0 -1
  56. package/dist/commands/build.js +53 -7
  57. package/dist/commands/build.js.map +1 -1
  58. package/dist/commands/create.d.ts +9 -0
  59. package/dist/commands/create.js +30 -0
  60. package/dist/commands/create.js.map +1 -0
  61. package/dist/commands/dev.d.ts +4 -1
  62. package/dist/commands/dev.js +77 -13
  63. package/dist/commands/dev.js.map +1 -1
  64. package/dist/commands/start.d.ts +3 -1
  65. package/dist/commands/start.js +37 -15
  66. package/dist/commands/start.js.map +1 -1
  67. package/dist/commands/telemetry/disable.js.map +1 -1
  68. package/dist/commands/telemetry/enable.js.map +1 -1
  69. package/dist/commands/telemetry/status.js.map +1 -1
  70. package/dist/server/asset-base-url-transform-plugin.d.ts +11 -0
  71. package/dist/server/asset-base-url-transform-plugin.js +48 -0
  72. package/dist/server/asset-base-url-transform-plugin.js.map +1 -0
  73. package/dist/server/asset-base-url-transform-plugin.test.d.ts +1 -0
  74. package/dist/server/asset-base-url-transform-plugin.test.js +134 -0
  75. package/dist/server/asset-base-url-transform-plugin.test.js.map +1 -0
  76. package/dist/server/auth/discovery.d.ts +7 -0
  77. package/dist/server/auth/discovery.js +41 -0
  78. package/dist/server/auth/discovery.js.map +1 -0
  79. package/dist/server/auth/discovery.test.d.ts +1 -0
  80. package/dist/server/auth/discovery.test.js +73 -0
  81. package/dist/server/auth/discovery.test.js.map +1 -0
  82. package/dist/server/auth/index.d.ts +16 -0
  83. package/dist/server/auth/index.js +2 -0
  84. package/dist/server/auth/index.js.map +1 -0
  85. package/dist/server/auth/providers/custom.d.ts +12 -0
  86. package/dist/server/auth/providers/custom.js +28 -0
  87. package/dist/server/auth/providers/custom.js.map +1 -0
  88. package/dist/server/auth/providers/custom.test.d.ts +1 -0
  89. package/dist/server/auth/providers/custom.test.js +82 -0
  90. package/dist/server/auth/providers/custom.test.js.map +1 -0
  91. package/dist/server/auth/setup.d.ts +4 -0
  92. package/dist/server/auth/setup.js +28 -0
  93. package/dist/server/auth/setup.js.map +1 -0
  94. package/dist/server/auth/setup.test.d.ts +1 -0
  95. package/dist/server/auth/setup.test.js +161 -0
  96. package/dist/server/auth/setup.test.js.map +1 -0
  97. package/dist/server/auth/verify.d.ts +11 -0
  98. package/dist/server/auth/verify.js +37 -0
  99. package/dist/server/auth/verify.js.map +1 -0
  100. package/dist/server/auth/verify.test.d.ts +1 -0
  101. package/dist/server/auth/verify.test.js +87 -0
  102. package/dist/server/auth/verify.test.js.map +1 -0
  103. package/dist/server/auth.d.ts +20 -0
  104. package/dist/server/auth.js +28 -0
  105. package/dist/server/auth.js.map +1 -0
  106. package/dist/server/build-manifest.test.d.ts +1 -0
  107. package/dist/server/build-manifest.test.js +27 -0
  108. package/dist/server/build-manifest.test.js.map +1 -0
  109. package/dist/server/content-helpers.d.ts +67 -0
  110. package/dist/server/content-helpers.js +79 -0
  111. package/dist/server/content-helpers.js.map +1 -0
  112. package/dist/server/content-helpers.test.d.ts +1 -0
  113. package/dist/server/content-helpers.test.js +70 -0
  114. package/dist/server/content-helpers.test.js.map +1 -0
  115. package/dist/server/express.d.ts +11 -0
  116. package/dist/server/express.js +101 -0
  117. package/dist/server/express.js.map +1 -0
  118. package/dist/server/express.test.d.ts +1 -0
  119. package/dist/server/express.test.js +491 -0
  120. package/dist/server/express.test.js.map +1 -0
  121. package/dist/server/file-ref.d.ts +28 -0
  122. package/dist/server/file-ref.js +27 -0
  123. package/dist/server/file-ref.js.map +1 -0
  124. package/dist/server/index.d.ts +9 -3
  125. package/dist/server/index.js +6 -2
  126. package/dist/server/index.js.map +1 -1
  127. package/dist/server/inferUtilityTypes.d.ts +6 -6
  128. package/dist/server/inferUtilityTypes.js.map +1 -1
  129. package/dist/server/metric.d.ts +14 -0
  130. package/dist/server/metric.js +62 -0
  131. package/dist/server/metric.js.map +1 -0
  132. package/dist/server/middleware.d.ts +137 -0
  133. package/dist/server/middleware.js +93 -0
  134. package/dist/server/middleware.js.map +1 -0
  135. package/dist/server/middleware.test-d.d.ts +1 -0
  136. package/dist/server/middleware.test-d.js +75 -0
  137. package/dist/server/middleware.test-d.js.map +1 -0
  138. package/dist/server/middleware.test.d.ts +1 -0
  139. package/dist/server/middleware.test.js +493 -0
  140. package/dist/server/middleware.test.js.map +1 -0
  141. package/dist/server/server.d.ts +364 -60
  142. package/dist/server/server.js +590 -69
  143. package/dist/server/server.js.map +1 -1
  144. package/dist/server/templateHelper.d.ts +5 -7
  145. package/dist/server/templateHelper.js +3 -22
  146. package/dist/server/templateHelper.js.map +1 -1
  147. package/dist/server/templates.generated.d.ts +4 -0
  148. package/dist/server/templates.generated.js +47 -0
  149. package/dist/server/templates.generated.js.map +1 -0
  150. package/dist/server/tunnel-proxy-router.d.ts +7 -0
  151. package/dist/server/tunnel-proxy-router.js +110 -0
  152. package/dist/server/tunnel-proxy-router.js.map +1 -0
  153. package/dist/server/tunnel-proxy-router.test.d.ts +1 -0
  154. package/dist/server/tunnel-proxy-router.test.js +229 -0
  155. package/dist/server/tunnel-proxy-router.test.js.map +1 -0
  156. package/dist/server/view-resource-resolution.test.d.ts +6 -0
  157. package/dist/server/view-resource-resolution.test.js +88 -0
  158. package/dist/server/view-resource-resolution.test.js.map +1 -0
  159. package/dist/server/viewsDevServer.d.ts +14 -0
  160. package/dist/server/viewsDevServer.js +45 -0
  161. package/dist/server/viewsDevServer.js.map +1 -0
  162. package/dist/test/utils.d.ts +13 -21
  163. package/dist/test/utils.js +42 -37
  164. package/dist/test/utils.js.map +1 -1
  165. package/dist/test/view.test.d.ts +1 -0
  166. package/dist/test/view.test.js +568 -0
  167. package/dist/test/view.test.js.map +1 -0
  168. package/dist/version.d.ts +1 -0
  169. package/dist/version.js +3 -0
  170. package/dist/version.js.map +1 -0
  171. package/dist/web/bridges/apps-sdk/adaptor.d.ts +14 -7
  172. package/dist/web/bridges/apps-sdk/adaptor.js +73 -20
  173. package/dist/web/bridges/apps-sdk/adaptor.js.map +1 -1
  174. package/dist/web/bridges/apps-sdk/bridge.d.ts +2 -1
  175. package/dist/web/bridges/apps-sdk/bridge.js +1 -0
  176. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
  177. package/dist/web/bridges/apps-sdk/index.d.ts +1 -1
  178. package/dist/web/bridges/apps-sdk/index.js.map +1 -1
  179. package/dist/web/bridges/apps-sdk/types.d.ts +32 -14
  180. package/dist/web/bridges/apps-sdk/types.js.map +1 -1
  181. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.d.ts +11 -0
  182. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js +11 -0
  183. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
  184. package/dist/web/bridges/get-adaptor.d.ts +7 -0
  185. package/dist/web/bridges/get-adaptor.js +7 -0
  186. package/dist/web/bridges/get-adaptor.js.map +1 -1
  187. package/dist/web/bridges/index.js.map +1 -1
  188. package/dist/web/bridges/mcp-app/adaptor.d.ts +26 -9
  189. package/dist/web/bridges/mcp-app/adaptor.js +162 -64
  190. package/dist/web/bridges/mcp-app/adaptor.js.map +1 -1
  191. package/dist/web/bridges/mcp-app/bridge.d.ts +16 -31
  192. package/dist/web/bridges/mcp-app/bridge.js +65 -196
  193. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  194. package/dist/web/bridges/mcp-app/index.js.map +1 -1
  195. package/dist/web/bridges/mcp-app/types.js.map +1 -1
  196. package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +17 -3
  197. package/dist/web/bridges/mcp-app/use-mcp-app-context.js +14 -2
  198. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
  199. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js +1 -41
  200. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -1
  201. package/dist/web/bridges/mcp-app/view-tools.test.d.ts +1 -0
  202. package/dist/web/bridges/mcp-app/view-tools.test.js +144 -0
  203. package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -0
  204. package/dist/web/bridges/types.d.ts +120 -14
  205. package/dist/web/bridges/types.js.map +1 -1
  206. package/dist/web/bridges/use-host-context.d.ts +5 -0
  207. package/dist/web/bridges/use-host-context.js +5 -0
  208. package/dist/web/bridges/use-host-context.js.map +1 -1
  209. package/dist/web/components/modal-provider.js +3 -5
  210. package/dist/web/components/modal-provider.js.map +1 -1
  211. package/dist/web/create-store.d.ts +26 -0
  212. package/dist/web/create-store.js +43 -3
  213. package/dist/web/create-store.js.map +1 -1
  214. package/dist/web/create-store.test.js +23 -20
  215. package/dist/web/create-store.test.js.map +1 -1
  216. package/dist/web/data-llm.d.ts +34 -1
  217. package/dist/web/data-llm.js +31 -3
  218. package/dist/web/data-llm.js.map +1 -1
  219. package/dist/web/data-llm.test.js +33 -30
  220. package/dist/web/data-llm.test.js.map +1 -1
  221. package/dist/web/generate-helpers.d.ts +22 -18
  222. package/dist/web/generate-helpers.js +22 -18
  223. package/dist/web/generate-helpers.js.map +1 -1
  224. package/dist/web/generate-helpers.test-d.js +30 -28
  225. package/dist/web/generate-helpers.test-d.js.map +1 -1
  226. package/dist/web/generate-helpers.test.js.map +1 -1
  227. package/dist/web/helpers/state.d.ts +2 -2
  228. package/dist/web/helpers/state.js +11 -11
  229. package/dist/web/helpers/state.js.map +1 -1
  230. package/dist/web/helpers/state.test.js +9 -9
  231. package/dist/web/helpers/state.test.js.map +1 -1
  232. package/dist/web/hooks/index.d.ts +6 -2
  233. package/dist/web/hooks/index.js +5 -1
  234. package/dist/web/hooks/index.js.map +1 -1
  235. package/dist/web/hooks/test/utils.d.ts +6 -2
  236. package/dist/web/hooks/test/utils.js +17 -2
  237. package/dist/web/hooks/test/utils.js.map +1 -1
  238. package/dist/web/hooks/use-call-tool.d.ts +45 -0
  239. package/dist/web/hooks/use-call-tool.js +28 -0
  240. package/dist/web/hooks/use-call-tool.js.map +1 -1
  241. package/dist/web/hooks/use-call-tool.test-d.js.map +1 -1
  242. package/dist/web/hooks/use-call-tool.test.js +27 -6
  243. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  244. package/dist/web/hooks/use-display-mode.d.ts +23 -3
  245. package/dist/web/hooks/use-display-mode.js +20 -0
  246. package/dist/web/hooks/use-display-mode.js.map +1 -1
  247. package/dist/web/hooks/use-display-mode.test-d.d.ts +1 -0
  248. package/dist/web/hooks/use-display-mode.test-d.js +8 -0
  249. package/dist/web/hooks/use-display-mode.test-d.js.map +1 -0
  250. package/dist/web/hooks/use-display-mode.test.js.map +1 -1
  251. package/dist/web/hooks/use-download.d.ts +5 -0
  252. package/dist/web/hooks/use-download.js +8 -0
  253. package/dist/web/hooks/use-download.js.map +1 -0
  254. package/dist/web/hooks/use-download.test.d.ts +1 -0
  255. package/dist/web/hooks/use-download.test.js +95 -0
  256. package/dist/web/hooks/use-download.test.js.map +1 -0
  257. package/dist/web/hooks/use-files.d.ts +34 -1
  258. package/dist/web/hooks/use-files.js +33 -0
  259. package/dist/web/hooks/use-files.js.map +1 -1
  260. package/dist/web/hooks/use-files.test.js +27 -3
  261. package/dist/web/hooks/use-files.test.js.map +1 -1
  262. package/dist/web/hooks/use-layout.d.ts +2 -0
  263. package/dist/web/hooks/use-layout.js +2 -0
  264. package/dist/web/hooks/use-layout.js.map +1 -1
  265. package/dist/web/hooks/use-layout.test.js +3 -3
  266. package/dist/web/hooks/use-layout.test.js.map +1 -1
  267. package/dist/web/hooks/use-open-external.d.ts +20 -1
  268. package/dist/web/hooks/use-open-external.js +17 -1
  269. package/dist/web/hooks/use-open-external.js.map +1 -1
  270. package/dist/web/hooks/use-open-external.test.js +26 -11
  271. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  272. package/dist/web/hooks/use-register-view-tool.d.ts +38 -0
  273. package/dist/web/hooks/use-register-view-tool.js +50 -0
  274. package/dist/web/hooks/use-register-view-tool.js.map +1 -0
  275. package/dist/web/hooks/use-request-close.d.ts +16 -0
  276. package/dist/web/hooks/use-request-close.js +21 -0
  277. package/dist/web/hooks/use-request-close.js.map +1 -0
  278. package/dist/web/hooks/use-request-close.test.d.ts +1 -0
  279. package/dist/web/hooks/use-request-close.test.js +52 -0
  280. package/dist/web/hooks/use-request-close.test.js.map +1 -0
  281. package/dist/web/hooks/use-request-modal.d.ts +16 -1
  282. package/dist/web/hooks/use-request-modal.js +19 -4
  283. package/dist/web/hooks/use-request-modal.js.map +1 -1
  284. package/dist/web/hooks/use-request-modal.test.js +5 -1
  285. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  286. package/dist/web/hooks/use-request-size.d.ts +20 -0
  287. package/dist/web/hooks/use-request-size.js +24 -0
  288. package/dist/web/hooks/use-request-size.js.map +1 -0
  289. package/dist/web/hooks/use-request-size.test.d.ts +1 -0
  290. package/dist/web/hooks/use-request-size.test.js +65 -0
  291. package/dist/web/hooks/use-request-size.test.js.map +1 -0
  292. package/dist/web/hooks/use-send-follow-up-message.d.ts +19 -1
  293. package/dist/web/hooks/use-send-follow-up-message.js +19 -2
  294. package/dist/web/hooks/use-send-follow-up-message.js.map +1 -1
  295. package/dist/web/hooks/use-set-open-in-app-url.d.ts +17 -0
  296. package/dist/web/hooks/use-set-open-in-app-url.js +17 -0
  297. package/dist/web/hooks/use-set-open-in-app-url.js.map +1 -1
  298. package/dist/web/hooks/use-set-open-in-app-url.test.js +5 -11
  299. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  300. package/dist/web/hooks/use-tool-info.d.ts +53 -2
  301. package/dist/web/hooks/use-tool-info.js +30 -7
  302. package/dist/web/hooks/use-tool-info.js.map +1 -1
  303. package/dist/web/hooks/use-tool-info.test-d.js +11 -29
  304. package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
  305. package/dist/web/hooks/use-tool-info.test.js +6 -6
  306. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  307. package/dist/web/hooks/use-user.d.ts +2 -0
  308. package/dist/web/hooks/use-user.js +20 -2
  309. package/dist/web/hooks/use-user.js.map +1 -1
  310. package/dist/web/hooks/use-user.test.js +29 -1
  311. package/dist/web/hooks/use-user.test.js.map +1 -1
  312. package/dist/web/hooks/use-view-state.d.ts +25 -0
  313. package/dist/web/hooks/use-view-state.js +32 -0
  314. package/dist/web/hooks/use-view-state.js.map +1 -0
  315. package/dist/web/hooks/use-view-state.test.d.ts +1 -0
  316. package/dist/web/hooks/use-view-state.test.js +177 -0
  317. package/dist/web/hooks/use-view-state.test.js.map +1 -0
  318. package/dist/web/index.d.ts +1 -2
  319. package/dist/web/index.js +1 -2
  320. package/dist/web/index.js.map +1 -1
  321. package/dist/web/mount-view.d.ts +20 -0
  322. package/dist/web/{mount-widget.js → mount-view.js} +21 -2
  323. package/dist/web/mount-view.js.map +1 -0
  324. package/dist/web/plugin/data-llm.test.js.map +1 -1
  325. package/dist/web/plugin/plugin.d.ts +32 -1
  326. package/dist/web/plugin/plugin.js +168 -18
  327. package/dist/web/plugin/plugin.js.map +1 -1
  328. package/dist/web/plugin/scan-views.d.ts +16 -0
  329. package/dist/web/plugin/scan-views.js +88 -0
  330. package/dist/web/plugin/scan-views.js.map +1 -0
  331. package/dist/web/plugin/scan-views.test.d.ts +1 -0
  332. package/dist/web/plugin/scan-views.test.js +99 -0
  333. package/dist/web/plugin/scan-views.test.js.map +1 -0
  334. package/dist/web/plugin/transform-data-llm.js +1 -1
  335. package/dist/web/plugin/transform-data-llm.js.map +1 -1
  336. package/dist/web/plugin/transform-data-llm.test.js.map +1 -1
  337. package/dist/web/plugin/validate-view.d.ts +1 -0
  338. package/dist/web/plugin/validate-view.js +9 -0
  339. package/dist/web/plugin/validate-view.js.map +1 -0
  340. package/dist/web/plugin/validate-view.test.d.ts +1 -0
  341. package/dist/web/plugin/validate-view.test.js +24 -0
  342. package/dist/web/plugin/validate-view.test.js.map +1 -0
  343. package/dist/web/proxy.js +0 -1
  344. package/dist/web/proxy.js.map +1 -1
  345. package/dist/web/types.d.ts +4 -0
  346. package/dist/web/types.js.map +1 -1
  347. package/package.json +51 -29
  348. package/tsconfig.base.json +33 -0
  349. package/dist/server/templates/development.hbs +0 -66
  350. package/dist/server/templates/production.hbs +0 -7
  351. package/dist/server/widgetsDevServer.d.ts +0 -12
  352. package/dist/server/widgetsDevServer.js +0 -47
  353. package/dist/server/widgetsDevServer.js.map +0 -1
  354. package/dist/test/widget.test.js +0 -255
  355. package/dist/test/widget.test.js.map +0 -1
  356. package/dist/web/hooks/use-widget-state.d.ts +0 -4
  357. package/dist/web/hooks/use-widget-state.js +0 -32
  358. package/dist/web/hooks/use-widget-state.js.map +0 -1
  359. package/dist/web/hooks/use-widget-state.test.js +0 -61
  360. package/dist/web/hooks/use-widget-state.test.js.map +0 -1
  361. package/dist/web/mount-widget.d.ts +0 -1
  362. package/dist/web/mount-widget.js.map +0 -1
  363. /package/dist/{test/widget.test.d.ts → cli/build-helpers.test.d.ts} +0 -0
  364. /package/dist/{web/hooks/use-widget-state.test.d.ts → cli/tunnel-control-server.test.d.ts} +0 -0
@@ -0,0 +1,134 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { assetBaseUrlTransform, assetBaseUrlTransformPlugin, isCssRequest, } from "./asset-base-url-transform-plugin.js";
3
+ describe("assetBaseUrlTransform", () => {
4
+ it("should transform asset paths to use window.skybridge.serverUrl", () => {
5
+ const cases = [
6
+ {
7
+ desc: "single-quoted",
8
+ code: `const image = '/assets/logo.png';`,
9
+ expected: `const image = (window.skybridge?.serverUrl ?? "") + "/assets/logo.png";`,
10
+ },
11
+ {
12
+ desc: "double-quoted",
13
+ code: `const image = "/assets/logo.png";`,
14
+ expected: `const image = (window.skybridge?.serverUrl ?? "") + "/assets/logo.png";`,
15
+ },
16
+ {
17
+ desc: "backtick-quoted",
18
+ code: "const image = `/assets/logo.png`;",
19
+ expected: `const image = (window.skybridge?.serverUrl ?? "") + "/assets/logo.png";`,
20
+ },
21
+ ];
22
+ for (const { code, expected } of cases) {
23
+ const result = assetBaseUrlTransform(code);
24
+ expect(result).toBe(expected);
25
+ }
26
+ });
27
+ it("should transform multiple asset paths", () => {
28
+ const code = `
29
+ const logo = '/assets/logo.png';
30
+ const icon = '/assets/icon.svg';
31
+ const font = '/assets/font.woff2';
32
+ `;
33
+ const result = assetBaseUrlTransform(code);
34
+ expect(result).toContain(`(window.skybridge?.serverUrl ?? "") + "/assets/logo.png"`);
35
+ expect(result).toContain(`(window.skybridge?.serverUrl ?? "") + "/assets/icon.svg"`);
36
+ expect(result).toContain(`(window.skybridge?.serverUrl ?? "") + "/assets/font.woff2"`);
37
+ });
38
+ it("should not transform already absolute URLs", () => {
39
+ const code = `
40
+ const local = '/assets/logo.png';
41
+ const http = 'http://example.com/image.png';
42
+ const https = 'https://example.com/image.png';
43
+ `;
44
+ const result = assetBaseUrlTransform(code);
45
+ expect(result).toContain(`(window.skybridge?.serverUrl ?? "") + "/assets/logo.png"`);
46
+ expect(result).toContain("http://example.com/image.png");
47
+ expect(result).toContain("https://example.com/image.png");
48
+ });
49
+ it("should not transform code without asset paths", () => {
50
+ const code = `const text = "Hello World";`;
51
+ const result = assetBaseUrlTransform(code);
52
+ expect(result).toBe(code);
53
+ });
54
+ it("should not transform asset paths inside static `import ... from` clauses", () => {
55
+ // Reproducer for #713: a dep does `import * as sprite from './icons.svg'`,
56
+ // Vite resolves the relative path to absolute, then this transform used
57
+ // to rewrite the resolved string — producing invalid JS like
58
+ // `import * as sprite from (expr) + "..."` that crashes vite:import-analysis.
59
+ const cases = [
60
+ `import * as sprite from "/Users/me/proj/node_modules/pkg/icons.svg";`,
61
+ `import sprite from '/assets/icons.svg';`,
62
+ `import sprite from "/assets/icons.svg";`,
63
+ `export { default } from "/assets/icons.svg";`,
64
+ `export * from '/assets/sprites.svg';`,
65
+ ];
66
+ for (const code of cases) {
67
+ expect(assetBaseUrlTransform(code)).toBe(code);
68
+ }
69
+ });
70
+ it("should still transform value-position asset paths in files that also have unrelated imports", () => {
71
+ const code = [
72
+ `import { foo } from "./foo.js";`,
73
+ `import * as sprite from "/assets/sprite.svg";`,
74
+ `const logo = "/assets/logo.png";`,
75
+ ].join("\n");
76
+ const result = assetBaseUrlTransform(code);
77
+ // Imports untouched
78
+ expect(result).toContain(`from "./foo.js"`);
79
+ expect(result).toContain(`from "/assets/sprite.svg"`);
80
+ // Value-position rewritten
81
+ expect(result).toContain(`const logo = (window.skybridge?.serverUrl ?? "") + "/assets/logo.png";`);
82
+ });
83
+ });
84
+ describe("isCssRequest", () => {
85
+ it("returns true for CSS-family extensions", () => {
86
+ expect(isCssRequest("/src/styles.css")).toBe(true);
87
+ expect(isCssRequest("/src/styles.module.css")).toBe(true);
88
+ expect(isCssRequest("/src/styles.scss")).toBe(true);
89
+ expect(isCssRequest("/src/styles.sass")).toBe(true);
90
+ expect(isCssRequest("/src/styles.less")).toBe(true);
91
+ expect(isCssRequest("/src/styles.styl")).toBe(true);
92
+ });
93
+ it("returns true for CSS modules with Vite query strings", () => {
94
+ expect(isCssRequest("/src/styles.css?direct")).toBe(true);
95
+ expect(isCssRequest("/src/styles.css?inline")).toBe(true);
96
+ expect(isCssRequest("/src/styles.css?used")).toBe(true);
97
+ expect(isCssRequest("/src/Foo.vue?vue&type=style&lang.css")).toBe(true);
98
+ });
99
+ it("returns false for non-CSS modules", () => {
100
+ expect(isCssRequest("/src/index.tsx")).toBe(false);
101
+ expect(isCssRequest("/src/utils.ts")).toBe(false);
102
+ expect(isCssRequest("/src/Logo.svg")).toBe(false);
103
+ expect(isCssRequest("/src/notes.cssx")).toBe(false);
104
+ });
105
+ });
106
+ describe("assetBaseUrlTransformPlugin", () => {
107
+ function runTransform(id, code) {
108
+ const plugin = assetBaseUrlTransformPlugin();
109
+ const hook = plugin.transform;
110
+ if (!hook) {
111
+ throw new Error("plugin.transform is not defined");
112
+ }
113
+ const handler = typeof hook === "function" ? hook : hook.handler;
114
+ return handler.call(
115
+ // biome-ignore lint/suspicious/noExplicitAny: vitest harness for plugin hook
116
+ {}, code, id, { moduleType: "js" });
117
+ }
118
+ it("rewrites asset paths in JS modules", () => {
119
+ const result = runTransform("/src/widget.tsx", `const logo = "/assets/logo.png";`);
120
+ expect(result?.code).toContain(`(window.skybridge?.serverUrl ?? "") + "/assets/logo.png"`);
121
+ });
122
+ // Reproducer for #697: CSS imports are served as JS modules with the
123
+ // stylesheet embedded as a string. Rewriting url("/foo.woff2") inside that
124
+ // string would produce invalid CSS once the styles are injected.
125
+ it("does not rewrite asset paths when transforming a CSS module", () => {
126
+ const cssCode = `__vite__updateStyle("style-id", "@font-face { src: url(\\"/fonts/Brand.woff2\\") format(\\"woff2\\"); }");`;
127
+ expect(runTransform("/src/fonts.css", cssCode)).toBeNull();
128
+ expect(runTransform("/src/fonts.css?direct", cssCode)).toBeNull();
129
+ expect(runTransform("/src/fonts.css?inline", cssCode)).toBeNull();
130
+ expect(runTransform("/src/styles.scss", cssCode)).toBeNull();
131
+ expect(runTransform("/src/Foo.vue?vue&type=style&lang.css", cssCode)).toBeNull();
132
+ });
133
+ });
134
+ //# sourceMappingURL=asset-base-url-transform-plugin.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-base-url-transform-plugin.test.js","sourceRoot":"","sources":["../../src/server/asset-base-url-transform-plugin.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport {\n assetBaseUrlTransform,\n assetBaseUrlTransformPlugin,\n isCssRequest,\n} from \"./asset-base-url-transform-plugin.js\";\n\ndescribe(\"assetBaseUrlTransform\", () => {\n it(\"should transform asset paths to use window.skybridge.serverUrl\", () => {\n const cases = [\n {\n desc: \"single-quoted\",\n code: `const image = '/assets/logo.png';`,\n expected: `const image = (window.skybridge?.serverUrl ?? \"\") + \"/assets/logo.png\";`,\n },\n {\n desc: \"double-quoted\",\n code: `const image = \"/assets/logo.png\";`,\n expected: `const image = (window.skybridge?.serverUrl ?? \"\") + \"/assets/logo.png\";`,\n },\n {\n desc: \"backtick-quoted\",\n code: \"const image = `/assets/logo.png`;\",\n expected: `const image = (window.skybridge?.serverUrl ?? \"\") + \"/assets/logo.png\";`,\n },\n ];\n\n for (const { code, expected } of cases) {\n const result = assetBaseUrlTransform(code);\n expect(result).toBe(expected);\n }\n });\n\n it(\"should transform multiple asset paths\", () => {\n const code = `\n const logo = '/assets/logo.png';\n const icon = '/assets/icon.svg';\n const font = '/assets/font.woff2';\n `;\n const result = assetBaseUrlTransform(code);\n\n expect(result).toContain(\n `(window.skybridge?.serverUrl ?? \"\") + \"/assets/logo.png\"`,\n );\n expect(result).toContain(\n `(window.skybridge?.serverUrl ?? \"\") + \"/assets/icon.svg\"`,\n );\n expect(result).toContain(\n `(window.skybridge?.serverUrl ?? \"\") + \"/assets/font.woff2\"`,\n );\n });\n\n it(\"should not transform already absolute URLs\", () => {\n const code = `\n const local = '/assets/logo.png';\n const http = 'http://example.com/image.png';\n const https = 'https://example.com/image.png';\n `;\n const result = assetBaseUrlTransform(code);\n\n expect(result).toContain(\n `(window.skybridge?.serverUrl ?? \"\") + \"/assets/logo.png\"`,\n );\n expect(result).toContain(\"http://example.com/image.png\");\n expect(result).toContain(\"https://example.com/image.png\");\n });\n\n it(\"should not transform code without asset paths\", () => {\n const code = `const text = \"Hello World\";`;\n const result = assetBaseUrlTransform(code);\n\n expect(result).toBe(code);\n });\n\n it(\"should not transform asset paths inside static `import ... from` clauses\", () => {\n // Reproducer for #713: a dep does `import * as sprite from './icons.svg'`,\n // Vite resolves the relative path to absolute, then this transform used\n // to rewrite the resolved string — producing invalid JS like\n // `import * as sprite from (expr) + \"...\"` that crashes vite:import-analysis.\n const cases = [\n `import * as sprite from \"/Users/me/proj/node_modules/pkg/icons.svg\";`,\n `import sprite from '/assets/icons.svg';`,\n `import sprite from \"/assets/icons.svg\";`,\n `export { default } from \"/assets/icons.svg\";`,\n `export * from '/assets/sprites.svg';`,\n ];\n\n for (const code of cases) {\n expect(assetBaseUrlTransform(code)).toBe(code);\n }\n });\n\n it(\"should still transform value-position asset paths in files that also have unrelated imports\", () => {\n const code = [\n `import { foo } from \"./foo.js\";`,\n `import * as sprite from \"/assets/sprite.svg\";`,\n `const logo = \"/assets/logo.png\";`,\n ].join(\"\\n\");\n const result = assetBaseUrlTransform(code);\n\n // Imports untouched\n expect(result).toContain(`from \"./foo.js\"`);\n expect(result).toContain(`from \"/assets/sprite.svg\"`);\n // Value-position rewritten\n expect(result).toContain(\n `const logo = (window.skybridge?.serverUrl ?? \"\") + \"/assets/logo.png\";`,\n );\n });\n});\n\ndescribe(\"isCssRequest\", () => {\n it(\"returns true for CSS-family extensions\", () => {\n expect(isCssRequest(\"/src/styles.css\")).toBe(true);\n expect(isCssRequest(\"/src/styles.module.css\")).toBe(true);\n expect(isCssRequest(\"/src/styles.scss\")).toBe(true);\n expect(isCssRequest(\"/src/styles.sass\")).toBe(true);\n expect(isCssRequest(\"/src/styles.less\")).toBe(true);\n expect(isCssRequest(\"/src/styles.styl\")).toBe(true);\n });\n\n it(\"returns true for CSS modules with Vite query strings\", () => {\n expect(isCssRequest(\"/src/styles.css?direct\")).toBe(true);\n expect(isCssRequest(\"/src/styles.css?inline\")).toBe(true);\n expect(isCssRequest(\"/src/styles.css?used\")).toBe(true);\n expect(isCssRequest(\"/src/Foo.vue?vue&type=style&lang.css\")).toBe(true);\n });\n\n it(\"returns false for non-CSS modules\", () => {\n expect(isCssRequest(\"/src/index.tsx\")).toBe(false);\n expect(isCssRequest(\"/src/utils.ts\")).toBe(false);\n expect(isCssRequest(\"/src/Logo.svg\")).toBe(false);\n expect(isCssRequest(\"/src/notes.cssx\")).toBe(false);\n });\n});\n\ndescribe(\"assetBaseUrlTransformPlugin\", () => {\n type TransformResult = { code: string; map: null } | null;\n\n function runTransform(id: string, code: string): TransformResult {\n const plugin = assetBaseUrlTransformPlugin();\n const hook = plugin.transform;\n if (!hook) {\n throw new Error(\"plugin.transform is not defined\");\n }\n const handler = typeof hook === \"function\" ? hook : hook.handler;\n return handler.call(\n // biome-ignore lint/suspicious/noExplicitAny: vitest harness for plugin hook\n {} as any,\n code,\n id,\n { moduleType: \"js\" },\n ) as TransformResult;\n }\n\n it(\"rewrites asset paths in JS modules\", () => {\n const result = runTransform(\n \"/src/widget.tsx\",\n `const logo = \"/assets/logo.png\";`,\n );\n expect(result?.code).toContain(\n `(window.skybridge?.serverUrl ?? \"\") + \"/assets/logo.png\"`,\n );\n });\n\n // Reproducer for #697: CSS imports are served as JS modules with the\n // stylesheet embedded as a string. Rewriting url(\"/foo.woff2\") inside that\n // string would produce invalid CSS once the styles are injected.\n it(\"does not rewrite asset paths when transforming a CSS module\", () => {\n const cssCode = `__vite__updateStyle(\"style-id\", \"@font-face { src: url(\\\\\"/fonts/Brand.woff2\\\\\") format(\\\\\"woff2\\\\\"); }\");`;\n\n expect(runTransform(\"/src/fonts.css\", cssCode)).toBeNull();\n expect(runTransform(\"/src/fonts.css?direct\", cssCode)).toBeNull();\n expect(runTransform(\"/src/fonts.css?inline\", cssCode)).toBeNull();\n expect(runTransform(\"/src/styles.scss\", cssCode)).toBeNull();\n expect(\n runTransform(\"/src/Foo.vue?vue&type=style&lang.css\", cssCode),\n ).toBeNull();\n });\n});\n"]}
@@ -0,0 +1,7 @@
1
+ import { type OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
2
+ /** Discovered metadata; `jwks_uri` is an RFC field the SDK type doesn't expose. */
3
+ export type DiscoveredMetadata = OAuthMetadata & {
4
+ jwks_uri?: string;
5
+ };
6
+ /** Fetches and validates an authorization server's discovery document. */
7
+ export declare function discoverAuthorizationServer(issuer: string): Promise<DiscoveredMetadata>;
@@ -0,0 +1,41 @@
1
+ import { OAuthMetadataSchema, } from "@modelcontextprotocol/sdk/shared/auth.js";
2
+ const WELL_KNOWN = [
3
+ "/.well-known/openid-configuration",
4
+ "/.well-known/oauth-authorization-server",
5
+ ];
6
+ /** Fetches and validates an authorization server's discovery document. */
7
+ export async function discoverAuthorizationServer(issuer) {
8
+ const base = issuer.replace(/\/$/, "");
9
+ const errors = [];
10
+ for (const path of WELL_KNOWN) {
11
+ const url = `${base}${path}`;
12
+ let res;
13
+ try {
14
+ res = await fetch(url);
15
+ }
16
+ catch (err) {
17
+ errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
18
+ continue;
19
+ }
20
+ if (!res.ok) {
21
+ errors.push(`${url}: HTTP ${res.status}`);
22
+ continue;
23
+ }
24
+ let json;
25
+ try {
26
+ json = await res.json();
27
+ }
28
+ catch {
29
+ errors.push(`${url}: response is not valid JSON`);
30
+ continue;
31
+ }
32
+ const parsed = OAuthMetadataSchema.safeParse(json);
33
+ if (!parsed.success) {
34
+ errors.push(`${url}: invalid metadata (${parsed.error.message})`);
35
+ continue;
36
+ }
37
+ return parsed.data;
38
+ }
39
+ throw new Error(`OAuth discovery failed for ${issuer}: ${errors.join("; ")}`);
40
+ }
41
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../src/server/auth/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,mBAAmB,GACpB,MAAM,0CAA0C,CAAC;AAKlD,MAAM,UAAU,GAAG;IACjB,mCAAmC;IACnC,yCAAyC;CAC1C,CAAC;AAEF,0EAA0E;AAC1E,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,MAAc;IAEd,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACvC,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;QAC7B,IAAI,GAAa,CAAC;QAClB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QACzB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CACT,GAAG,GAAG,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC9D,CAAC;YACF,SAAS;QACX,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,UAAU,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YAC1C,SAAS;QACX,CAAC;QACD,IAAI,IAAa,CAAC;QAClB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,8BAA8B,CAAC,CAAC;YAClD,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,mBAAmB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,uBAAuB,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC;YAClE,SAAS;QACX,CAAC;QACD,OAAO,MAAM,CAAC,IAA0B,CAAC;IAC3C,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,8BAA8B,MAAM,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAChF,CAAC","sourcesContent":["import {\n type OAuthMetadata,\n OAuthMetadataSchema,\n} from \"@modelcontextprotocol/sdk/shared/auth.js\";\n\n/** Discovered metadata; `jwks_uri` is an RFC field the SDK type doesn't expose. */\nexport type DiscoveredMetadata = OAuthMetadata & { jwks_uri?: string };\n\nconst WELL_KNOWN = [\n \"/.well-known/openid-configuration\",\n \"/.well-known/oauth-authorization-server\",\n];\n\n/** Fetches and validates an authorization server's discovery document. */\nexport async function discoverAuthorizationServer(\n issuer: string,\n): Promise<DiscoveredMetadata> {\n const base = issuer.replace(/\\/$/, \"\");\n const errors: string[] = [];\n\n for (const path of WELL_KNOWN) {\n const url = `${base}${path}`;\n let res: Response;\n try {\n res = await fetch(url);\n } catch (err) {\n errors.push(\n `${url}: ${err instanceof Error ? err.message : String(err)}`,\n );\n continue;\n }\n if (!res.ok) {\n errors.push(`${url}: HTTP ${res.status}`);\n continue;\n }\n let json: unknown;\n try {\n json = await res.json();\n } catch {\n errors.push(`${url}: response is not valid JSON`);\n continue;\n }\n const parsed = OAuthMetadataSchema.safeParse(json);\n if (!parsed.success) {\n errors.push(`${url}: invalid metadata (${parsed.error.message})`);\n continue;\n }\n return parsed.data as DiscoveredMetadata;\n }\n\n throw new Error(`OAuth discovery failed for ${issuer}: ${errors.join(\"; \")}`);\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,73 @@
1
+ // @vitest-environment node
2
+ import http from "node:http";
3
+ import { afterEach, describe, expect, it } from "vitest";
4
+ import { discoverAuthorizationServer } from "./discovery.js";
5
+ let server;
6
+ afterEach(() => server?.close());
7
+ const DOC = {
8
+ issuer: "https://idp.test",
9
+ authorization_endpoint: "https://idp.test/authorize",
10
+ token_endpoint: "https://idp.test/token",
11
+ registration_endpoint: "https://idp.test/register",
12
+ response_types_supported: ["code"],
13
+ scopes_supported: ["openid", "email"],
14
+ jwks_uri: "https://idp.test/jwks",
15
+ };
16
+ // Serves the given bodies at their well-known paths; returns the origin URL.
17
+ // A string body is sent raw (text/html); anything else is JSON-encoded.
18
+ async function serve(routes) {
19
+ const srv = http.createServer((req, res) => {
20
+ const body = routes[req.url ?? ""];
21
+ if (body === undefined) {
22
+ res.writeHead(404).end();
23
+ return;
24
+ }
25
+ if (typeof body === "string") {
26
+ res.writeHead(200, { "content-type": "text/html" }).end(body);
27
+ return;
28
+ }
29
+ res.setHeader("content-type", "application/json");
30
+ res.end(JSON.stringify(body));
31
+ });
32
+ await new Promise((resolve) => srv.listen(0, resolve));
33
+ server = srv;
34
+ const port = srv.address().port;
35
+ return `http://localhost:${port}`;
36
+ }
37
+ describe("discoverAuthorizationServer", () => {
38
+ it("fetches and validates openid-configuration", async () => {
39
+ const base = await serve({ "/.well-known/openid-configuration": DOC });
40
+ const meta = await discoverAuthorizationServer(base);
41
+ expect(meta.registration_endpoint).toBe("https://idp.test/register");
42
+ expect(meta.jwks_uri).toBe("https://idp.test/jwks");
43
+ });
44
+ it("falls back to oauth-authorization-server when oidc is 404", async () => {
45
+ const base = await serve({
46
+ "/.well-known/oauth-authorization-server": DOC,
47
+ });
48
+ const meta = await discoverAuthorizationServer(base);
49
+ expect(meta.registration_endpoint).toBe("https://idp.test/register");
50
+ });
51
+ it("throws when no well-known doc is reachable", async () => {
52
+ const base = await serve({});
53
+ await expect(discoverAuthorizationServer(base)).rejects.toThrow(/discovery failed/i);
54
+ });
55
+ it("falls through to oauth-authorization-server when oidc doc is schema-invalid", async () => {
56
+ const invalidDoc = { ...DOC };
57
+ delete invalidDoc.response_types_supported;
58
+ const base = await serve({
59
+ "/.well-known/openid-configuration": invalidDoc,
60
+ "/.well-known/oauth-authorization-server": DOC,
61
+ });
62
+ const meta = await discoverAuthorizationServer(base);
63
+ expect(meta.registration_endpoint).toBe("https://idp.test/register");
64
+ });
65
+ it("treats a 200 non-JSON body as unreachable and throws discovery failed", async () => {
66
+ const base = await serve({
67
+ "/.well-known/openid-configuration": "<html>nope</html>",
68
+ "/.well-known/oauth-authorization-server": "<html>nope</html>",
69
+ });
70
+ await expect(discoverAuthorizationServer(base)).rejects.toThrow(/discovery failed/i);
71
+ });
72
+ });
73
+ //# sourceMappingURL=discovery.test.js.map
@@ -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\nconst DOC = {\n issuer: \"https://idp.test\",\n authorization_endpoint: \"https://idp.test/authorize\",\n token_endpoint: \"https://idp.test/token\",\n registration_endpoint: \"https://idp.test/register\",\n response_types_supported: [\"code\"],\n scopes_supported: [\"openid\", \"email\"],\n jwks_uri: \"https://idp.test/jwks\",\n};\n\n// Serves the given bodies at their well-known paths; returns the origin URL.\n// A string body is sent raw (text/html); anything else is JSON-encoded.\nasync function serve(routes: Record<string, unknown>) {\n const srv = http.createServer((req, res) => {\n const body = routes[req.url ?? \"\"];\n if (body === undefined) {\n res.writeHead(404).end();\n return;\n }\n if (typeof body === \"string\") {\n res.writeHead(200, { \"content-type\": \"text/html\" }).end(body);\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 const port = (srv.address() as { port: number }).port;\n return `http://localhost:${port}`;\n}\n\ndescribe(\"discoverAuthorizationServer\", () => {\n it(\"fetches and validates openid-configuration\", async () => {\n const base = await serve({ \"/.well-known/openid-configuration\": DOC });\n const meta = await discoverAuthorizationServer(base);\n expect(meta.registration_endpoint).toBe(\"https://idp.test/register\");\n expect(meta.jwks_uri).toBe(\"https://idp.test/jwks\");\n });\n\n it(\"falls back to oauth-authorization-server when oidc is 404\", async () => {\n const base = await serve({\n \"/.well-known/oauth-authorization-server\": DOC,\n });\n const meta = await discoverAuthorizationServer(base);\n expect(meta.registration_endpoint).toBe(\"https://idp.test/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(\"falls through to oauth-authorization-server when oidc doc is schema-invalid\", async () => {\n const invalidDoc = { ...DOC };\n delete (invalidDoc as Partial<typeof DOC>).response_types_supported;\n const base = await serve({\n \"/.well-known/openid-configuration\": invalidDoc,\n \"/.well-known/oauth-authorization-server\": DOC,\n });\n const meta = await discoverAuthorizationServer(base);\n expect(meta.registration_endpoint).toBe(\"https://idp.test/register\");\n });\n\n it(\"treats a 200 non-JSON body as unreachable and throws discovery failed\", async () => {\n const base = await serve({\n \"/.well-known/openid-configuration\": \"<html>nope</html>\",\n \"/.well-known/oauth-authorization-server\": \"<html>nope</html>\",\n });\n await expect(discoverAuthorizationServer(base)).rejects.toThrow(\n /discovery failed/i,\n );\n });\n});\n"]}
@@ -0,0 +1,16 @@
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
+ /** Public URL of this server; sets `resourceServerUrl` and the `resource_metadata` URL. */
6
+ baseUrl: string;
7
+ /** AS metadata served at `/.well-known/oauth-authorization-server`. */
8
+ oauthMetadata: OAuthMetadata;
9
+ verify: JwksVerifyConfig;
10
+ /** Scopes advertised in protected-resource metadata. */
11
+ scopesSupported?: string[];
12
+ /** Server-wide required-scope floor. */
13
+ requiredScopes?: string[];
14
+ /** `required` (default) gates `/mcp`; `optional` allows anonymous for per-tool checks. */
15
+ enforcement?: "required" | "optional";
16
+ };
@@ -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 /** Public URL of this server; sets `resourceServerUrl` and the `resource_metadata` URL. */\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 /** `required` (default) gates `/mcp`; `optional` allows anonymous for per-tool checks. */\n enforcement?: \"required\" | \"optional\";\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
+ baseUrl: string;
8
+ scopes?: string[];
9
+ requiredScopes?: string[];
10
+ enforcement?: "required" | "optional";
11
+ metadataOverrides?: Partial<OAuthMetadata>;
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
+ const oauthMetadata = {
6
+ ...discovered,
7
+ ...opts.metadataOverrides,
8
+ };
9
+ if (!oauthMetadata.registration_endpoint) {
10
+ throw new Error(`${opts.issuer} is not DCR-compatible (no registration_endpoint); customProvider only supports DCR IdPs.`);
11
+ }
12
+ if (!oauthMetadata.jwks_uri) {
13
+ throw new Error(`${opts.issuer} discovery has no jwks_uri; JWKS verification requires it.`);
14
+ }
15
+ return {
16
+ baseUrl: opts.baseUrl,
17
+ oauthMetadata,
18
+ verify: {
19
+ issuer: oauthMetadata.issuer,
20
+ audience: opts.audience,
21
+ jwksUri: oauthMetadata.jwks_uri,
22
+ },
23
+ scopesSupported: opts.scopes ?? oauthMetadata.scopes_supported,
24
+ requiredScopes: opts.requiredScopes,
25
+ enforcement: opts.enforcement,
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;IAClE,MAAM,aAAa,GAAuB;QACxC,GAAG,UAAU;QACb,GAAG,IAAI,CAAC,iBAAiB;KAC1B,CAAC;IAEF,IAAI,CAAC,aAAa,CAAC,qBAAqB,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,CAAC,MAAM,2FAA2F,CAC1G,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,CAAC,MAAM,4DAA4D,CAC3E,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,aAAa;QACb,MAAM,EAAE;YACN,MAAM,EAAE,aAAa,CAAC,MAAM;YAC5B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,OAAO,EAAE,aAAa,CAAC,QAAQ;SAChC;QACD,eAAe,EAAE,IAAI,CAAC,MAAM,IAAI,aAAa,CAAC,gBAAgB;QAC9D,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,WAAW,EAAE,IAAI,CAAC,WAAW;KAC9B,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 baseUrl: string;\n scopes?: string[];\n requiredScopes?: string[];\n enforcement?: \"required\" | \"optional\";\n metadataOverrides?: Partial<OAuthMetadata>;\n}): Promise<OAuthConfig> {\n const discovered = await discoverAuthorizationServer(opts.issuer);\n const oauthMetadata: DiscoveredMetadata = {\n ...discovered,\n ...opts.metadataOverrides,\n };\n\n if (!oauthMetadata.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 (!oauthMetadata.jwks_uri) {\n throw new Error(\n `${opts.issuer} discovery has no jwks_uri; JWKS verification requires it.`,\n );\n }\n\n return {\n baseUrl: opts.baseUrl,\n oauthMetadata,\n verify: {\n issuer: oauthMetadata.issuer,\n audience: opts.audience,\n jwksUri: oauthMetadata.jwks_uri,\n },\n scopesSupported: opts.scopes ?? oauthMetadata.scopes_supported,\n requiredScopes: opts.requiredScopes,\n enforcement: opts.enforcement,\n };\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,82 @@
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
+ async function serveDiscovery(doc) {
8
+ const srv = http.createServer((req, res) => {
9
+ if (req.url !== "/.well-known/openid-configuration") {
10
+ res.writeHead(404).end();
11
+ return;
12
+ }
13
+ res.setHeader("content-type", "application/json");
14
+ res.end(JSON.stringify(doc));
15
+ });
16
+ await new Promise((resolve) => srv.listen(0, resolve));
17
+ server = srv;
18
+ const port = srv.address().port;
19
+ return `http://localhost:${port}`;
20
+ }
21
+ const ISSUER = "https://idp.test";
22
+ function doc(extra = {}) {
23
+ return {
24
+ issuer: ISSUER,
25
+ authorization_endpoint: `${ISSUER}/authorize`,
26
+ token_endpoint: `${ISSUER}/token`,
27
+ registration_endpoint: `${ISSUER}/register`,
28
+ response_types_supported: ["code"],
29
+ scopes_supported: ["openid", "email", "profile"],
30
+ jwks_uri: `${ISSUER}/jwks`,
31
+ ...extra,
32
+ };
33
+ }
34
+ describe("customProvider", () => {
35
+ it("builds an OAuthConfig from discovery", async () => {
36
+ const base = await serveDiscovery(doc());
37
+ const config = await customProvider({
38
+ issuer: base,
39
+ audience: "my-api",
40
+ baseUrl: "https://app.example.test",
41
+ scopes: ["openid"],
42
+ enforcement: "optional",
43
+ });
44
+ expect(config.baseUrl).toBe("https://app.example.test");
45
+ expect(config.verify).toEqual({
46
+ issuer: ISSUER,
47
+ audience: "my-api",
48
+ jwksUri: `${ISSUER}/jwks`,
49
+ });
50
+ expect(config.scopesSupported).toEqual(["openid"]);
51
+ expect(config.enforcement).toBe("optional");
52
+ expect(config.oauthMetadata.registration_endpoint).toBe(`${ISSUER}/register`);
53
+ });
54
+ it("defaults scopesSupported to the discovered scopes", async () => {
55
+ const base = await serveDiscovery(doc());
56
+ const config = await customProvider({
57
+ issuer: base,
58
+ audience: "a",
59
+ baseUrl: "https://app.example.test",
60
+ });
61
+ expect(config.scopesSupported).toEqual(["openid", "email", "profile"]);
62
+ });
63
+ it("rejects a non-DCR IdP (no registration_endpoint)", async () => {
64
+ const base = await serveDiscovery(doc({ registration_endpoint: undefined }));
65
+ await expect(customProvider({
66
+ issuer: base,
67
+ audience: "a",
68
+ baseUrl: "https://app.example.test",
69
+ })).rejects.toThrow(/not DCR-compatible/);
70
+ });
71
+ it("applies metadataOverrides over discovered values", async () => {
72
+ const base = await serveDiscovery(doc());
73
+ const config = await customProvider({
74
+ issuer: base,
75
+ audience: "a",
76
+ baseUrl: "https://app.example.test",
77
+ metadataOverrides: { token_endpoint: "https://override/token" },
78
+ });
79
+ expect(config.oauthMetadata.token_endpoint).toBe("https://override/token");
80
+ });
81
+ });
82
+ //# 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\nasync function serveDiscovery(doc: Record<string, unknown>) {\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));\n });\n await new Promise<void>((resolve) => srv.listen(0, resolve));\n server = srv;\n const port = (srv.address() as { port: number }).port;\n return `http://localhost:${port}`;\n}\n\nconst ISSUER = \"https://idp.test\";\nfunction doc(extra: Record<string, unknown> = {}) {\n return {\n issuer: ISSUER,\n authorization_endpoint: `${ISSUER}/authorize`,\n token_endpoint: `${ISSUER}/token`,\n registration_endpoint: `${ISSUER}/register`,\n response_types_supported: [\"code\"],\n scopes_supported: [\"openid\", \"email\", \"profile\"],\n jwks_uri: `${ISSUER}/jwks`,\n ...extra,\n };\n}\n\ndescribe(\"customProvider\", () => {\n it(\"builds an OAuthConfig from discovery\", async () => {\n const base = await serveDiscovery(doc());\n\n const config = await customProvider({\n issuer: base,\n audience: \"my-api\",\n baseUrl: \"https://app.example.test\",\n scopes: [\"openid\"],\n enforcement: \"optional\",\n });\n\n expect(config.baseUrl).toBe(\"https://app.example.test\");\n expect(config.verify).toEqual({\n issuer: ISSUER,\n audience: \"my-api\",\n jwksUri: `${ISSUER}/jwks`,\n });\n expect(config.scopesSupported).toEqual([\"openid\"]);\n expect(config.enforcement).toBe(\"optional\");\n expect(config.oauthMetadata.registration_endpoint).toBe(\n `${ISSUER}/register`,\n );\n });\n\n it(\"defaults scopesSupported to the discovered scopes\", async () => {\n const base = await serveDiscovery(doc());\n const config = await customProvider({\n issuer: base,\n audience: \"a\",\n baseUrl: \"https://app.example.test\",\n });\n expect(config.scopesSupported).toEqual([\"openid\", \"email\", \"profile\"]);\n });\n\n it(\"rejects a non-DCR IdP (no registration_endpoint)\", async () => {\n const base = await serveDiscovery(\n doc({ registration_endpoint: undefined }),\n );\n await expect(\n customProvider({\n issuer: base,\n audience: \"a\",\n baseUrl: \"https://app.example.test\",\n }),\n ).rejects.toThrow(/not DCR-compatible/);\n });\n\n it(\"applies metadataOverrides over discovered values\", async () => {\n const base = await serveDiscovery(doc());\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,28 @@
1
+ import { getOAuthProtectedResourceMetadataUrl, mcpAuthMetadataRouter, } from "@modelcontextprotocol/sdk/server/auth/router.js";
2
+ import { optionalBearerAuth, requireBearerAuth } from "../auth.js";
3
+ import { createJwksVerifier } from "./verify.js";
4
+ /** Mounts the well-known OAuth metadata and bearer auth on `/mcp`. */
5
+ export function setupOAuth(app, config) {
6
+ let baseUrl;
7
+ try {
8
+ baseUrl = new URL(config.baseUrl);
9
+ }
10
+ catch {
11
+ throw new Error(`oauth.baseUrl must be a valid absolute URL, got: ${JSON.stringify(config.baseUrl)}`);
12
+ }
13
+ if (!config.verify?.issuer || !config.verify?.audience) {
14
+ throw new Error("oauth.verify requires both `issuer` and `audience`");
15
+ }
16
+ app.use(mcpAuthMetadataRouter({
17
+ oauthMetadata: config.oauthMetadata,
18
+ resourceServerUrl: baseUrl,
19
+ scopesSupported: config.scopesSupported,
20
+ }));
21
+ const bearer = config.enforcement === "optional" ? optionalBearerAuth : requireBearerAuth;
22
+ app.use("/mcp", bearer({
23
+ verifier: createJwksVerifier(config.verify),
24
+ requiredScopes: config.requiredScopes,
25
+ resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(baseUrl),
26
+ }));
27
+ }
28
+ //# 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;AAEzD,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,sEAAsE;AACtE,MAAM,UAAU,UAAU,CAAC,GAAY,EAAE,MAAmB;IAC1D,IAAI,OAAY,CAAC;IACjB,IAAI,CAAC;QACH,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,oDAAoD,IAAI,CAAC,SAAS,CAChE,MAAM,CAAC,OAAO,CACf,EAAE,CACJ,CAAC;IACJ,CAAC;IACD,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,GAAG,CAAC,GAAG,CACL,qBAAqB,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,iBAAiB,EAAE,OAAO;QAC1B,eAAe,EAAE,MAAM,CAAC,eAAe;KACxC,CAAC,CACH,CAAC;IAEF,MAAM,MAAM,GACV,MAAM,CAAC,WAAW,KAAK,UAAU,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,iBAAiB,CAAC;IAC7E,GAAG,CAAC,GAAG,CACL,MAAM,EACN,MAAM,CAAC;QACL,QAAQ,EAAE,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC;QAC3C,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,mBAAmB,EAAE,oCAAoC,CAAC,OAAO,CAAC;KACnE,CAAC,CACH,CAAC;AACJ,CAAC","sourcesContent":["import {\n getOAuthProtectedResourceMetadataUrl,\n mcpAuthMetadataRouter,\n} from \"@modelcontextprotocol/sdk/server/auth/router.js\";\nimport type { Express } from \"express\";\nimport { optionalBearerAuth, requireBearerAuth } from \"../auth.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 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 if (!config.verify?.issuer || !config.verify?.audience) {\n throw new Error(\"oauth.verify requires both `issuer` and `audience`\");\n }\n\n app.use(\n mcpAuthMetadataRouter({\n oauthMetadata: config.oauthMetadata,\n resourceServerUrl: baseUrl,\n scopesSupported: config.scopesSupported,\n }),\n );\n\n const bearer =\n config.enforcement === \"optional\" ? optionalBearerAuth : requireBearerAuth;\n app.use(\n \"/mcp\",\n bearer({\n verifier: createJwksVerifier(config.verify),\n requiredScopes: config.requiredScopes,\n resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(baseUrl),\n }),\n );\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};