skybridge 0.0.0-dev.fa77314 → 0.0.0-dev.fa95a7b

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 (355) hide show
  1. package/README.md +10 -7
  2. package/dist/cli/build-helpers.d.ts +6 -2
  3. package/dist/cli/build-helpers.js +39 -14
  4. package/dist/cli/build-helpers.js.map +1 -1
  5. package/dist/cli/build-helpers.test.js +73 -5
  6. package/dist/cli/build-helpers.test.js.map +1 -1
  7. package/dist/cli/build-steps.d.ts +2 -0
  8. package/dist/cli/build-steps.js +73 -0
  9. package/dist/cli/build-steps.js.map +1 -0
  10. package/dist/cli/build-steps.test.js +52 -0
  11. package/dist/cli/build-steps.test.js.map +1 -0
  12. package/dist/cli/detect-port.js +0 -1
  13. package/dist/cli/detect-port.js.map +1 -1
  14. package/dist/cli/header.d.ts +1 -1
  15. package/dist/cli/resolve-skybridge-config.d.ts +5 -0
  16. package/dist/cli/{resolve-views-dir.js → resolve-skybridge-config.js} +4 -3
  17. package/dist/cli/resolve-skybridge-config.js.map +1 -0
  18. package/dist/cli/run-plain.d.ts +18 -0
  19. package/dist/cli/run-plain.js +90 -0
  20. package/dist/cli/run-plain.js.map +1 -0
  21. package/dist/cli/use-execute-steps.d.ts +1 -1
  22. package/dist/cli/use-nodemon.d.ts +25 -1
  23. package/dist/cli/use-nodemon.js +96 -63
  24. package/dist/cli/use-nodemon.js.map +1 -1
  25. package/dist/cli/use-typescript-check.d.ts +8 -2
  26. package/dist/cli/use-typescript-check.js +70 -67
  27. package/dist/cli/use-typescript-check.js.map +1 -1
  28. package/dist/commands/build.d.ts +0 -2
  29. package/dist/commands/build.js +2 -61
  30. package/dist/commands/build.js.map +1 -1
  31. package/dist/commands/dev.d.ts +1 -0
  32. package/dist/commands/dev.js +38 -6
  33. package/dist/commands/dev.js.map +1 -1
  34. package/dist/commands/start.js +2 -2
  35. package/dist/commands/start.js.map +1 -1
  36. package/dist/context-warnings.d.ts +5 -0
  37. package/dist/context-warnings.js +31 -0
  38. package/dist/context-warnings.js.map +1 -0
  39. package/dist/context-warnings.test.js +28 -0
  40. package/dist/context-warnings.test.js.map +1 -0
  41. package/dist/server/app.d.ts +194 -0
  42. package/dist/server/app.js +266 -0
  43. package/dist/server/app.js.map +1 -0
  44. package/dist/server/app.test.js +97 -0
  45. package/dist/server/app.test.js.map +1 -0
  46. package/dist/server/auth/discovery.d.ts +29 -7
  47. package/dist/server/auth/discovery.js +16 -11
  48. package/dist/server/auth/discovery.js.map +1 -1
  49. package/dist/server/auth/index.d.ts +35 -5
  50. package/dist/server/auth/index.js.map +1 -1
  51. package/dist/server/auth/providers/auth0.d.ts +34 -7
  52. package/dist/server/auth/providers/auth0.js +29 -11
  53. package/dist/server/auth/providers/auth0.js.map +1 -1
  54. package/dist/server/auth/providers/auth0.test.js +31 -14
  55. package/dist/server/auth/providers/auth0.test.js.map +1 -1
  56. package/dist/server/auth/providers/authplane.d.ts +49 -0
  57. package/dist/server/auth/providers/authplane.js +57 -0
  58. package/dist/server/auth/providers/authplane.js.map +1 -0
  59. package/dist/server/auth/providers/authplane.test.js +143 -0
  60. package/dist/server/auth/providers/authplane.test.js.map +1 -0
  61. package/dist/server/auth/providers/clerk.d.ts +40 -3
  62. package/dist/server/auth/providers/clerk.js +5 -6
  63. package/dist/server/auth/providers/clerk.js.map +1 -1
  64. package/dist/server/auth/providers/clerk.test.js +7 -25
  65. package/dist/server/auth/providers/clerk.test.js.map +1 -1
  66. package/dist/server/auth/providers/custom.d.ts +27 -4
  67. package/dist/server/auth/providers/custom.js +46 -15
  68. package/dist/server/auth/providers/custom.js.map +1 -1
  69. package/dist/server/auth/providers/custom.test.js +86 -16
  70. package/dist/server/auth/providers/custom.test.js.map +1 -1
  71. package/dist/server/auth/providers/descope.d.ts +41 -11
  72. package/dist/server/auth/providers/descope.js +38 -8
  73. package/dist/server/auth/providers/descope.js.map +1 -1
  74. package/dist/server/auth/providers/descope.test.js +43 -16
  75. package/dist/server/auth/providers/descope.test.js.map +1 -1
  76. package/dist/server/auth/providers/shared.d.ts +2 -0
  77. package/dist/server/auth/providers/shared.js +6 -0
  78. package/dist/server/auth/providers/shared.js.map +1 -0
  79. package/dist/server/auth/providers/shared.test.js +10 -0
  80. package/dist/server/auth/providers/shared.test.js.map +1 -0
  81. package/dist/server/auth/providers/stytch.d.ts +16 -3
  82. package/dist/server/auth/providers/stytch.js +5 -6
  83. package/dist/server/auth/providers/stytch.js.map +1 -1
  84. package/dist/server/auth/providers/verify-spy.d.ts +12 -0
  85. package/dist/server/auth/providers/verify-spy.js +21 -0
  86. package/dist/server/auth/providers/verify-spy.js.map +1 -0
  87. package/dist/server/auth/providers/workos.d.ts +25 -3
  88. package/dist/server/auth/providers/workos.js +5 -6
  89. package/dist/server/auth/providers/workos.js.map +1 -1
  90. package/dist/server/auth/security-schemes.d.ts +23 -0
  91. package/dist/server/auth/security-schemes.js +64 -0
  92. package/dist/server/auth/security-schemes.js.map +1 -0
  93. package/dist/server/auth/security-schemes.test.js +99 -0
  94. package/dist/server/auth/security-schemes.test.js.map +1 -0
  95. package/dist/server/auth/setup.d.ts +3 -1
  96. package/dist/server/auth/setup.js +66 -26
  97. package/dist/server/auth/setup.js.map +1 -1
  98. package/dist/server/auth/setup.test.js +349 -20
  99. package/dist/server/auth/setup.test.js.map +1 -1
  100. package/dist/server/auth/verify.d.ts +37 -3
  101. package/dist/server/auth/verify.js +27 -3
  102. package/dist/server/auth/verify.js.map +1 -1
  103. package/dist/server/auth/verify.test.js +49 -0
  104. package/dist/server/auth/verify.test.js.map +1 -1
  105. package/dist/server/auth-extra.test-d.js +130 -0
  106. package/dist/server/auth-extra.test-d.js.map +1 -0
  107. package/dist/server/auth.d.ts +43 -6
  108. package/dist/server/auth.js +4 -5
  109. package/dist/server/auth.js.map +1 -1
  110. package/dist/server/build-manifest.test.js +7 -6
  111. package/dist/server/build-manifest.test.js.map +1 -1
  112. package/dist/server/content-helpers.d.ts +1 -1
  113. package/dist/server/content-helpers.js.map +1 -1
  114. package/dist/server/express.d.ts +23 -3
  115. package/dist/server/express.js +38 -32
  116. package/dist/server/express.js.map +1 -1
  117. package/dist/server/express.test.js +220 -103
  118. package/dist/server/express.test.js.map +1 -1
  119. package/dist/server/file-ref.d.ts +1 -1
  120. package/dist/server/file-ref.js +1 -1
  121. package/dist/server/file-ref.js.map +1 -1
  122. package/dist/server/host.d.ts +2 -0
  123. package/dist/server/host.js +11 -0
  124. package/dist/server/host.js.map +1 -0
  125. package/dist/server/host.test.d.ts +1 -0
  126. package/dist/server/host.test.js +13 -0
  127. package/dist/server/host.test.js.map +1 -0
  128. package/dist/server/index.d.ts +16 -9
  129. package/dist/server/index.js +9 -3
  130. package/dist/server/index.js.map +1 -1
  131. package/dist/server/mcp-errors.test.d.ts +1 -0
  132. package/dist/server/mcp-errors.test.js +41 -0
  133. package/dist/server/mcp-errors.test.js.map +1 -0
  134. package/dist/server/middleware.d.ts +35 -10
  135. package/dist/server/middleware.js +33 -0
  136. package/dist/server/middleware.js.map +1 -1
  137. package/dist/server/middleware.test-d.js +1 -1
  138. package/dist/server/middleware.test-d.js.map +1 -1
  139. package/dist/server/middleware.test.js +242 -134
  140. package/dist/server/middleware.test.js.map +1 -1
  141. package/dist/server/register-tool.test.d.ts +1 -0
  142. package/dist/server/register-tool.test.js +27 -0
  143. package/dist/server/register-tool.test.js.map +1 -0
  144. package/dist/server/server.d.ts +133 -116
  145. package/dist/server/server.js +251 -346
  146. package/dist/server/server.js.map +1 -1
  147. package/dist/server/skills-integration.test.d.ts +1 -0
  148. package/dist/server/skills-integration.test.js +86 -0
  149. package/dist/server/skills-integration.test.js.map +1 -0
  150. package/dist/server/skills.d.ts +27 -0
  151. package/dist/server/skills.js +188 -0
  152. package/dist/server/skills.js.map +1 -0
  153. package/dist/server/skills.test.d.ts +1 -0
  154. package/dist/server/skills.test.js +243 -0
  155. package/dist/server/skills.test.js.map +1 -0
  156. package/dist/server/templateHelper.d.ts +0 -3
  157. package/dist/server/templateHelper.js.map +1 -1
  158. package/dist/server/templates.generated.js +4 -8
  159. package/dist/server/templates.generated.js.map +1 -1
  160. package/dist/server/tool-extra.test.d.ts +1 -0
  161. package/dist/server/tool-extra.test.js +88 -0
  162. package/dist/server/tool-extra.test.js.map +1 -0
  163. package/dist/server/view-name.test-d.d.ts +1 -0
  164. package/dist/server/view-name.test-d.js +8 -0
  165. package/dist/server/view-name.test-d.js.map +1 -0
  166. package/dist/server/view-resource-resolution.test.js +105 -17
  167. package/dist/server/view-resource-resolution.test.js.map +1 -1
  168. package/dist/server/viewsDevServer.js +4 -1
  169. package/dist/server/viewsDevServer.js.map +1 -1
  170. package/dist/test/utils.d.ts +9 -11
  171. package/dist/test/utils.js +220 -201
  172. package/dist/test/utils.js.map +1 -1
  173. package/dist/test/view.test.js +105 -133
  174. package/dist/test/view.test.js.map +1 -1
  175. package/dist/views/index.d.ts +10 -0
  176. package/dist/views/index.js +3 -0
  177. package/dist/views/index.js.map +1 -0
  178. package/dist/{web/plugin → views}/scan-views.js +1 -1
  179. package/dist/views/scan-views.js.map +1 -0
  180. package/dist/views/scan-views.test.d.ts +1 -0
  181. package/dist/views/scan-views.test.js.map +1 -0
  182. package/dist/views/validate-view.d.ts +1 -0
  183. package/dist/{web/plugin → views}/validate-view.js +2 -2
  184. package/dist/views/validate-view.js.map +1 -0
  185. package/dist/views/validate-view.test.d.ts +1 -0
  186. package/dist/views/validate-view.test.js.map +1 -0
  187. package/dist/web/bridges/adaptor.d.ts +51 -0
  188. package/dist/web/bridges/adaptor.js +327 -0
  189. package/dist/web/bridges/adaptor.js.map +1 -0
  190. package/dist/web/bridges/adaptor.test.d.ts +1 -0
  191. package/dist/web/bridges/adaptor.test.js +225 -0
  192. package/dist/web/bridges/adaptor.test.js.map +1 -0
  193. package/dist/web/bridges/apps-sdk/bridge.d.ts +6 -2
  194. package/dist/web/bridges/apps-sdk/bridge.js +15 -4
  195. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
  196. package/dist/web/bridges/apps-sdk/index.d.ts +0 -1
  197. package/dist/web/bridges/apps-sdk/index.js +0 -1
  198. package/dist/web/bridges/apps-sdk/index.js.map +1 -1
  199. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.d.ts +1 -1
  200. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js +1 -1
  201. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
  202. package/dist/web/bridges/get-adaptor.d.ts +4 -4
  203. package/dist/web/bridges/get-adaptor.js +6 -11
  204. package/dist/web/bridges/get-adaptor.js.map +1 -1
  205. package/dist/web/bridges/get-adaptor.test.d.ts +1 -0
  206. package/dist/web/bridges/get-adaptor.test.js +32 -0
  207. package/dist/web/bridges/get-adaptor.test.js.map +1 -0
  208. package/dist/web/bridges/mcp-app/bridge.d.ts +13 -2
  209. package/dist/web/bridges/mcp-app/bridge.js +52 -8
  210. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  211. package/dist/web/bridges/mcp-app/bridge.test.d.ts +1 -0
  212. package/dist/web/bridges/mcp-app/bridge.test.js +17 -0
  213. package/dist/web/bridges/mcp-app/bridge.test.js.map +1 -0
  214. package/dist/web/bridges/mcp-app/index.d.ts +0 -1
  215. package/dist/web/bridges/mcp-app/index.js +0 -1
  216. package/dist/web/bridges/mcp-app/index.js.map +1 -1
  217. package/dist/web/bridges/mcp-app/types.d.ts +2 -0
  218. package/dist/web/bridges/mcp-app/types.js.map +1 -1
  219. package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +1 -1
  220. package/dist/web/bridges/mcp-app/use-mcp-app-context.js +1 -1
  221. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
  222. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js +1 -1
  223. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -1
  224. package/dist/web/bridges/mcp-app/view-tools.test.js +6 -7
  225. package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -1
  226. package/dist/web/bridges/types.d.ts +27 -12
  227. package/dist/web/bridges/types.js +14 -1
  228. package/dist/web/bridges/types.js.map +1 -1
  229. package/dist/web/bridges/types.test.d.ts +1 -0
  230. package/dist/web/bridges/types.test.js +19 -0
  231. package/dist/web/bridges/types.test.js.map +1 -0
  232. package/dist/web/components/modal-provider.d.ts +1 -1
  233. package/dist/web/components/modal-provider.js +4 -3
  234. package/dist/web/components/modal-provider.js.map +1 -1
  235. package/dist/web/create-store.test.js +10 -7
  236. package/dist/web/create-store.test.js.map +1 -1
  237. package/dist/web/data-llm.d.ts +1 -1
  238. package/dist/web/data-llm.test.js +20 -7
  239. package/dist/web/data-llm.test.js.map +1 -1
  240. package/dist/web/generate-helpers.d.ts +22 -18
  241. package/dist/web/generate-helpers.js +19 -15
  242. package/dist/web/generate-helpers.js.map +1 -1
  243. package/dist/web/generate-helpers.test-d.js +3 -3
  244. package/dist/web/generate-helpers.test-d.js.map +1 -1
  245. package/dist/web/generate-helpers.test.js +2 -2
  246. package/dist/web/generate-helpers.test.js.map +1 -1
  247. package/dist/web/hooks/index.d.ts +2 -1
  248. package/dist/web/hooks/index.js +2 -1
  249. package/dist/web/hooks/index.js.map +1 -1
  250. package/dist/web/hooks/test/utils.d.ts +2 -1
  251. package/dist/web/hooks/test/utils.js +1 -1
  252. package/dist/web/hooks/test/utils.js.map +1 -1
  253. package/dist/web/hooks/use-call-tool.test.js +39 -25
  254. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  255. package/dist/web/hooks/use-display-mode.d.ts +1 -1
  256. package/dist/web/hooks/use-display-mode.js +1 -1
  257. package/dist/web/hooks/use-display-mode.js.map +1 -1
  258. package/dist/web/hooks/use-display-mode.test.js +57 -21
  259. package/dist/web/hooks/use-display-mode.test.js.map +1 -1
  260. package/dist/web/hooks/use-download.d.ts +1 -3
  261. package/dist/web/hooks/use-download.js +1 -2
  262. package/dist/web/hooks/use-download.js.map +1 -1
  263. package/dist/web/hooks/use-download.test.js +18 -10
  264. package/dist/web/hooks/use-download.test.js.map +1 -1
  265. package/dist/web/hooks/use-files.test.js +11 -3
  266. package/dist/web/hooks/use-files.test.js.map +1 -1
  267. package/dist/web/hooks/use-host.d.ts +22 -0
  268. package/dist/web/hooks/use-host.js +30 -0
  269. package/dist/web/hooks/use-host.js.map +1 -0
  270. package/dist/web/hooks/use-host.test.d.ts +1 -0
  271. package/dist/web/hooks/use-host.test.js +59 -0
  272. package/dist/web/hooks/use-host.test.js.map +1 -0
  273. package/dist/web/hooks/use-open-external.test.js +15 -5
  274. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  275. package/dist/web/hooks/use-register-view-tool.d.ts +2 -3
  276. package/dist/web/hooks/use-register-view-tool.js.map +1 -1
  277. package/dist/web/hooks/use-request-close.test.js +35 -40
  278. package/dist/web/hooks/use-request-close.test.js.map +1 -1
  279. package/dist/web/hooks/use-request-modal.test.js +11 -1
  280. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  281. package/dist/web/hooks/use-request-size.d.ts +1 -1
  282. package/dist/web/hooks/use-request-size.js +1 -1
  283. package/dist/web/hooks/use-request-size.js.map +1 -1
  284. package/dist/web/hooks/use-request-size.test.js +35 -53
  285. package/dist/web/hooks/use-request-size.test.js.map +1 -1
  286. package/dist/web/hooks/use-set-open-in-app-url.test.js +26 -12
  287. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  288. package/dist/web/hooks/use-tool-info.d.ts +1 -20
  289. package/dist/web/hooks/use-tool-info.js +0 -1
  290. package/dist/web/hooks/use-tool-info.js.map +1 -1
  291. package/dist/web/hooks/use-tool-info.test-d.js +0 -5
  292. package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
  293. package/dist/web/hooks/use-tool-info.test.js +38 -33
  294. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  295. package/dist/web/hooks/use-user.d.ts +3 -2
  296. package/dist/web/hooks/use-user.js +4 -3
  297. package/dist/web/hooks/use-user.js.map +1 -1
  298. package/dist/web/hooks/use-user.test.js +85 -37
  299. package/dist/web/hooks/use-user.test.js.map +1 -1
  300. package/dist/web/hooks/use-view-state.js +19 -9
  301. package/dist/web/hooks/use-view-state.js.map +1 -1
  302. package/dist/web/hooks/use-view-state.test.js +39 -4
  303. package/dist/web/hooks/use-view-state.test.js.map +1 -1
  304. package/dist/web/hooks/use-viewport.d.ts +20 -0
  305. package/dist/web/hooks/use-viewport.js +21 -0
  306. package/dist/web/hooks/use-viewport.js.map +1 -0
  307. package/dist/web/hooks/use-viewport.test.d.ts +1 -0
  308. package/dist/web/hooks/use-viewport.test.js +109 -0
  309. package/dist/web/hooks/use-viewport.test.js.map +1 -0
  310. package/dist/web/mount-view.d.ts +3 -2
  311. package/dist/web/mount-view.js +5 -8
  312. package/dist/web/mount-view.js.map +1 -1
  313. package/package.json +31 -28
  314. package/dist/cli/resolve-views-dir.d.ts +0 -1
  315. package/dist/cli/resolve-views-dir.js.map +0 -1
  316. package/dist/server/auth/providers/stytch.test.js +0 -33
  317. package/dist/server/auth/providers/stytch.test.js.map +0 -1
  318. package/dist/server/auth/providers/workos.test.js +0 -41
  319. package/dist/server/auth/providers/workos.test.js.map +0 -1
  320. package/dist/web/bridges/apps-sdk/adaptor.d.ts +0 -29
  321. package/dist/web/bridges/apps-sdk/adaptor.js +0 -117
  322. package/dist/web/bridges/apps-sdk/adaptor.js.map +0 -1
  323. package/dist/web/bridges/mcp-app/adaptor.d.ts +0 -53
  324. package/dist/web/bridges/mcp-app/adaptor.js +0 -283
  325. package/dist/web/bridges/mcp-app/adaptor.js.map +0 -1
  326. package/dist/web/hooks/use-layout.d.ts +0 -24
  327. package/dist/web/hooks/use-layout.js +0 -25
  328. package/dist/web/hooks/use-layout.js.map +0 -1
  329. package/dist/web/hooks/use-layout.test.js +0 -96
  330. package/dist/web/hooks/use-layout.test.js.map +0 -1
  331. package/dist/web/plugin/data-llm.test.js +0 -81
  332. package/dist/web/plugin/data-llm.test.js.map +0 -1
  333. package/dist/web/plugin/plugin.d.ts +0 -33
  334. package/dist/web/plugin/plugin.js +0 -189
  335. package/dist/web/plugin/plugin.js.map +0 -1
  336. package/dist/web/plugin/scan-views.js.map +0 -1
  337. package/dist/web/plugin/scan-views.test.js.map +0 -1
  338. package/dist/web/plugin/transform-data-llm.d.ts +0 -12
  339. package/dist/web/plugin/transform-data-llm.js +0 -96
  340. package/dist/web/plugin/transform-data-llm.js.map +0 -1
  341. package/dist/web/plugin/transform-data-llm.test.js +0 -81
  342. package/dist/web/plugin/transform-data-llm.test.js.map +0 -1
  343. package/dist/web/plugin/validate-view.d.ts +0 -1
  344. package/dist/web/plugin/validate-view.js.map +0 -1
  345. package/dist/web/plugin/validate-view.test.js.map +0 -1
  346. /package/dist/{server/auth/providers/stytch.test.d.ts → cli/build-steps.test.d.ts} +0 -0
  347. /package/dist/{server/auth/providers/workos.test.d.ts → context-warnings.test.d.ts} +0 -0
  348. /package/dist/{web/hooks/use-layout.test.d.ts → server/app.test.d.ts} +0 -0
  349. /package/dist/{web/plugin/scan-views.test.d.ts → server/auth/providers/authplane.test.d.ts} +0 -0
  350. /package/dist/{web/plugin/transform-data-llm.test.d.ts → server/auth/providers/shared.test.d.ts} +0 -0
  351. /package/dist/{web/plugin/validate-view.test.d.ts → server/auth/security-schemes.test.d.ts} +0 -0
  352. /package/dist/{web/plugin/data-llm.test.d.ts → server/auth-extra.test-d.d.ts} +0 -0
  353. /package/dist/{web/plugin → views}/scan-views.d.ts +0 -0
  354. /package/dist/{web/plugin → views}/scan-views.test.js +0 -0
  355. /package/dist/{web/plugin → views}/validate-view.test.js +0 -0
@@ -0,0 +1,143 @@
1
+ // @vitest-environment node
2
+ import { afterEach, describe, expect, it, vi } from "vitest";
3
+ import { authplaneProvider } from "./authplane.js";
4
+ import { lastJwksConfig as jwks } from "./verify-spy.js";
5
+ vi.mock("../verify.js", () => import("./verify-spy.js"));
6
+ afterEach(() => vi.restoreAllMocks());
7
+ const ISSUER = "https://auth.acme.com";
8
+ function discoveryDoc(issuer, scopes) {
9
+ return {
10
+ issuer,
11
+ authorization_endpoint: `${issuer}/authorize`,
12
+ token_endpoint: `${issuer}/token`,
13
+ registration_endpoint: `${issuer}/register`,
14
+ response_types_supported: ["code"],
15
+ scopes_supported: scopes,
16
+ jwks_uri: `${issuer}/.well-known/jwks.json`,
17
+ };
18
+ }
19
+ function mockDiscovery(docs) {
20
+ return vi
21
+ .spyOn(globalThis, "fetch")
22
+ .mockImplementation(async (input) => {
23
+ const url = String(input);
24
+ const entry = Object.entries(docs).find(([issuer]) => url === `${issuer}/.well-known/openid-configuration`);
25
+ if (entry === undefined) {
26
+ return new Response(null, { status: 404 });
27
+ }
28
+ return new Response(JSON.stringify(discoveryDoc(...entry)), {
29
+ headers: { "content-type": "application/json" },
30
+ });
31
+ });
32
+ }
33
+ describe("authplaneProvider", () => {
34
+ it("derives the audience from the resource the server advertises", async () => {
35
+ mockDiscovery({ [ISSUER]: ["checkout"] });
36
+ const config = await authplaneProvider({
37
+ issuer: ISSUER,
38
+ resource: "https://coffee.example.com/mcp",
39
+ }).resolve();
40
+ expect(jwks().audience).toBe("https://coffee.example.com/mcp");
41
+ expect(jwks().issuer).toBe(ISSUER);
42
+ expect(jwks().jwksUri).toBe(`${ISSUER}/.well-known/jwks.json`);
43
+ expect(config.baseUrl).toBe("https://coffee.example.com/mcp");
44
+ });
45
+ it.each([
46
+ "https://coffee.example.com/mcp",
47
+ "https://coffee.example.com/v2/mcp",
48
+ "https://coffee.example.com/",
49
+ "http://localhost:3000/mcp",
50
+ "https://coffee.example.com:8443/mcp",
51
+ ])("passes %s through as both resource and audience", async (resource) => {
52
+ mockDiscovery({ [ISSUER]: ["checkout"] });
53
+ const config = await authplaneProvider({
54
+ issuer: ISSUER,
55
+ resource,
56
+ }).resolve();
57
+ expect(config.baseUrl).toBe(resource);
58
+ expect(jwks().audience).toBe(resource);
59
+ });
60
+ // The metadata router serialises the resource identifier through `URL` before
61
+ // advertising it. Anything that serialisation would rewrite is refused here,
62
+ // so the advertised identifier is always the configured one.
63
+ it.each([
64
+ // Pathless origin — gains a root path.
65
+ ["https://coffee.example.com", "https://coffee.example.com/"],
66
+ ["http://localhost:3000", "http://localhost:3000/"],
67
+ // Uppercase host — lowercased.
68
+ ["https://COFFEE.EXAMPLE.COM/mcp", "https://coffee.example.com/mcp"],
69
+ // Explicit default port — dropped.
70
+ ["https://coffee.example.com:443/mcp", "https://coffee.example.com/mcp"],
71
+ ])("rejects %s and names %s instead", async (resource, published) => {
72
+ const fetchSpy = mockDiscovery({ [ISSUER]: ["checkout"] });
73
+ expect(() => authplaneProvider({ issuer: ISSUER, resource })).toThrow(`is advertised as ${JSON.stringify(published)}`);
74
+ expect(fetchSpy).not.toHaveBeenCalled();
75
+ });
76
+ it("lets an explicit audience override the default", async () => {
77
+ mockDiscovery({ [ISSUER]: ["checkout"] });
78
+ await authplaneProvider({
79
+ issuer: ISSUER,
80
+ resource: "https://coffee.example.com/mcp",
81
+ audience: "urn:acme:coffee",
82
+ }).resolve();
83
+ expect(jwks().audience).toBe("urn:acme:coffee");
84
+ });
85
+ it.each([
86
+ ["coffee.example.com/mcp", /must be an absolute URL/],
87
+ ["ftp://coffee.example.com/mcp", /must use the http or https scheme/],
88
+ ["https://coffee.example.com/mcp#frag", /must not include a fragment/],
89
+ ])("rejects the malformed resource %s", async (resource, message) => {
90
+ const fetchSpy = mockDiscovery({ [ISSUER]: ["checkout"] });
91
+ expect(() => authplaneProvider({ issuer: ISSUER, resource })).toThrow(message);
92
+ expect(fetchSpy).not.toHaveBeenCalled();
93
+ });
94
+ it("rejects a malformed issuer before any discovery", async () => {
95
+ const fetchSpy = mockDiscovery({ [ISSUER]: ["checkout"] });
96
+ expect(() => authplaneProvider({
97
+ issuer: "auth.acme.com",
98
+ resource: "https://coffee.example.com/mcp",
99
+ })).toThrow(/`issuer` must be an absolute URL/);
100
+ expect(fetchSpy).not.toHaveBeenCalled();
101
+ });
102
+ it("accepts an issuer carrying a trailing slash", async () => {
103
+ // Discovery resolves the issuer itself; the provider passes it through
104
+ // rather than rewriting the operator's value.
105
+ mockDiscovery({ [ISSUER]: ["checkout"] });
106
+ await authplaneProvider({
107
+ issuer: `${ISSUER}/`,
108
+ resource: "https://coffee.example.com/mcp",
109
+ }).resolve();
110
+ expect(jwks().issuer).toBe(ISSUER);
111
+ });
112
+ it("accepts a local http issuer for development", async () => {
113
+ const local = "http://localhost:9000";
114
+ mockDiscovery({ [local]: ["checkout"] });
115
+ await authplaneProvider({
116
+ issuer: local,
117
+ resource: "http://localhost:3000/mcp",
118
+ }).resolve();
119
+ expect(jwks().issuer).toBe(local);
120
+ expect(jwks().audience).toBe("http://localhost:3000/mcp");
121
+ });
122
+ it("forwards scopes and the required-scope floor", async () => {
123
+ mockDiscovery({ [ISSUER]: ["checkout", "profile"] });
124
+ const config = await authplaneProvider({
125
+ issuer: ISSUER,
126
+ resource: "https://coffee.example.com/mcp",
127
+ scopes: ["checkout"],
128
+ requiredScopes: ["checkout"],
129
+ }).resolve();
130
+ expect(config.scopesSupported).toEqual(["checkout"]);
131
+ expect(config.requiredScopes).toEqual(["checkout"]);
132
+ });
133
+ it("advertises the registration endpoint for dynamic client registration", async () => {
134
+ mockDiscovery({ [ISSUER]: ["checkout"] });
135
+ const config = await authplaneProvider({
136
+ issuer: ISSUER,
137
+ resource: "https://coffee.example.com/mcp",
138
+ }).resolve();
139
+ expect(config.oauthMetadata.issuer).toBe(ISSUER);
140
+ expect(config.oauthMetadata.registration_endpoint).toBe(`${ISSUER}/register`);
141
+ });
142
+ });
143
+ //# sourceMappingURL=authplane.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authplane.test.js","sourceRoot":"","sources":["../../../../src/server/auth/providers/authplane.test.ts"],"names":[],"mappings":"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","sourcesContent":["// @vitest-environment node\nimport { afterEach, describe, expect, it, vi } from \"vitest\";\nimport { authplaneProvider } from \"./authplane.js\";\nimport { lastJwksConfig as jwks } from \"./verify-spy.js\";\n\nvi.mock(\"../verify.js\", () => import(\"./verify-spy.js\"));\n\nafterEach(() => vi.restoreAllMocks());\n\nconst ISSUER = \"https://auth.acme.com\";\n\nfunction discoveryDoc(issuer: string, scopes: string[]) {\n return {\n issuer,\n authorization_endpoint: `${issuer}/authorize`,\n token_endpoint: `${issuer}/token`,\n registration_endpoint: `${issuer}/register`,\n response_types_supported: [\"code\"],\n scopes_supported: scopes,\n jwks_uri: `${issuer}/.well-known/jwks.json`,\n };\n}\n\nfunction mockDiscovery(docs: Record<string, string[]>) {\n return vi\n .spyOn(globalThis, \"fetch\")\n .mockImplementation(async (input: RequestInfo | URL) => {\n const url = String(input);\n const entry = Object.entries(docs).find(\n ([issuer]) => url === `${issuer}/.well-known/openid-configuration`,\n );\n if (entry === undefined) {\n return new Response(null, { status: 404 });\n }\n return new Response(JSON.stringify(discoveryDoc(...entry)), {\n headers: { \"content-type\": \"application/json\" },\n });\n });\n}\n\ndescribe(\"authplaneProvider\", () => {\n it(\"derives the audience from the resource the server advertises\", async () => {\n mockDiscovery({ [ISSUER]: [\"checkout\"] });\n\n const config = await authplaneProvider({\n issuer: ISSUER,\n resource: \"https://coffee.example.com/mcp\",\n }).resolve();\n\n expect(jwks().audience).toBe(\"https://coffee.example.com/mcp\");\n expect(jwks().issuer).toBe(ISSUER);\n expect(jwks().jwksUri).toBe(`${ISSUER}/.well-known/jwks.json`);\n expect(config.baseUrl).toBe(\"https://coffee.example.com/mcp\");\n });\n\n it.each([\n \"https://coffee.example.com/mcp\",\n \"https://coffee.example.com/v2/mcp\",\n \"https://coffee.example.com/\",\n \"http://localhost:3000/mcp\",\n \"https://coffee.example.com:8443/mcp\",\n ])(\"passes %s through as both resource and audience\", async (resource) => {\n mockDiscovery({ [ISSUER]: [\"checkout\"] });\n\n const config = await authplaneProvider({\n issuer: ISSUER,\n resource,\n }).resolve();\n\n expect(config.baseUrl).toBe(resource);\n expect(jwks().audience).toBe(resource);\n });\n\n // The metadata router serialises the resource identifier through `URL` before\n // advertising it. Anything that serialisation would rewrite is refused here,\n // so the advertised identifier is always the configured one.\n it.each([\n // Pathless origin — gains a root path.\n [\"https://coffee.example.com\", \"https://coffee.example.com/\"],\n [\"http://localhost:3000\", \"http://localhost:3000/\"],\n // Uppercase host — lowercased.\n [\"https://COFFEE.EXAMPLE.COM/mcp\", \"https://coffee.example.com/mcp\"],\n // Explicit default port — dropped.\n [\"https://coffee.example.com:443/mcp\", \"https://coffee.example.com/mcp\"],\n ])(\"rejects %s and names %s instead\", async (resource, published) => {\n const fetchSpy = mockDiscovery({ [ISSUER]: [\"checkout\"] });\n\n expect(() => authplaneProvider({ issuer: ISSUER, resource })).toThrow(\n `is advertised as ${JSON.stringify(published)}`,\n );\n expect(fetchSpy).not.toHaveBeenCalled();\n });\n\n it(\"lets an explicit audience override the default\", async () => {\n mockDiscovery({ [ISSUER]: [\"checkout\"] });\n\n await authplaneProvider({\n issuer: ISSUER,\n resource: \"https://coffee.example.com/mcp\",\n audience: \"urn:acme:coffee\",\n }).resolve();\n\n expect(jwks().audience).toBe(\"urn:acme:coffee\");\n });\n\n it.each([\n [\"coffee.example.com/mcp\", /must be an absolute URL/],\n [\"ftp://coffee.example.com/mcp\", /must use the http or https scheme/],\n [\"https://coffee.example.com/mcp#frag\", /must not include a fragment/],\n ])(\"rejects the malformed resource %s\", async (resource, message) => {\n const fetchSpy = mockDiscovery({ [ISSUER]: [\"checkout\"] });\n\n expect(() => authplaneProvider({ issuer: ISSUER, resource })).toThrow(\n message,\n );\n expect(fetchSpy).not.toHaveBeenCalled();\n });\n\n it(\"rejects a malformed issuer before any discovery\", async () => {\n const fetchSpy = mockDiscovery({ [ISSUER]: [\"checkout\"] });\n\n expect(() =>\n authplaneProvider({\n issuer: \"auth.acme.com\",\n resource: \"https://coffee.example.com/mcp\",\n }),\n ).toThrow(/`issuer` must be an absolute URL/);\n expect(fetchSpy).not.toHaveBeenCalled();\n });\n\n it(\"accepts an issuer carrying a trailing slash\", async () => {\n // Discovery resolves the issuer itself; the provider passes it through\n // rather than rewriting the operator's value.\n mockDiscovery({ [ISSUER]: [\"checkout\"] });\n\n await authplaneProvider({\n issuer: `${ISSUER}/`,\n resource: \"https://coffee.example.com/mcp\",\n }).resolve();\n\n expect(jwks().issuer).toBe(ISSUER);\n });\n\n it(\"accepts a local http issuer for development\", async () => {\n const local = \"http://localhost:9000\";\n mockDiscovery({ [local]: [\"checkout\"] });\n\n await authplaneProvider({\n issuer: local,\n resource: \"http://localhost:3000/mcp\",\n }).resolve();\n\n expect(jwks().issuer).toBe(local);\n expect(jwks().audience).toBe(\"http://localhost:3000/mcp\");\n });\n\n it(\"forwards scopes and the required-scope floor\", async () => {\n mockDiscovery({ [ISSUER]: [\"checkout\", \"profile\"] });\n\n const config = await authplaneProvider({\n issuer: ISSUER,\n resource: \"https://coffee.example.com/mcp\",\n scopes: [\"checkout\"],\n requiredScopes: [\"checkout\"],\n }).resolve();\n\n expect(config.scopesSupported).toEqual([\"checkout\"]);\n expect(config.requiredScopes).toEqual([\"checkout\"]);\n });\n\n it(\"advertises the registration endpoint for dynamic client registration\", async () => {\n mockDiscovery({ [ISSUER]: [\"checkout\"] });\n\n const config = await authplaneProvider({\n issuer: ISSUER,\n resource: \"https://coffee.example.com/mcp\",\n }).resolve();\n\n expect(config.oauthMetadata.issuer).toBe(ISSUER);\n expect(config.oauthMetadata.registration_endpoint).toBe(\n `${ISSUER}/register`,\n );\n });\n});\n"]}
@@ -1,5 +1,42 @@
1
- import type { OAuthConfig } from "../index.js";
1
+ import type { ExtraClaims } from "../../auth.js";
2
+ import type { OAuthProvider } from "../index.js";
3
+ import type { RegisteredClaims } from "../verify.js";
2
4
  import { type CustomProviderOptions } from "./custom.js";
5
+ /**
6
+ * Claims a Clerk session token (v2) carries. All optional: `o` and `act` appear
7
+ * only with organizations and impersonation, and `email` only when a JWT
8
+ * template adds it.
9
+ *
10
+ * @see https://clerk.com/docs/backend-requests/resources/session-tokens
11
+ * @see https://clerk.com/docs/backend-requests/jwt-templates
12
+ */
13
+ export type ClerkClaims = {
14
+ /** Session id. */
15
+ sid?: string;
16
+ /** Authorized party: the origin the token was issued to. */
17
+ azp?: string;
18
+ /** Session token version. */
19
+ v?: number;
20
+ /** Factor verification age, `[since-first-factor, since-second-factor]`. */
21
+ fva?: number[];
22
+ /** Active organization, when the user has one. */
23
+ o?: {
24
+ /** Organization id. */
25
+ id?: string;
26
+ /** Organization slug. */
27
+ slg?: string;
28
+ /** Role within the organization. */
29
+ rol?: string;
30
+ /** Permissions within the organization. */
31
+ per?: string;
32
+ /** Feature-permission map backing `per`. */
33
+ fpm?: string;
34
+ };
35
+ /** Actor, present while a user is being impersonated. */
36
+ act?: Record<string, unknown>;
37
+ /** Only when a JWT template adds it; not a default Clerk claim. */
38
+ email?: string;
39
+ };
3
40
  /**
4
41
  * OAuth provider for Clerk. `domain` is the Frontend API URL (e.g.
5
42
  * `acme.clerk.accounts.dev`, or a production custom domain). Requires Dynamic
@@ -9,6 +46,6 @@ import { type CustomProviderOptions } from "./custom.js";
9
46
  * Clerk access tokens carry no `aud` claim, so there is no `audience` option —
10
47
  * verification is issuer + JWKS only (matching Clerk's own `mcpAuthClerk`).
11
48
  */
12
- export declare function clerkProvider(opts: {
49
+ export declare function clerkProvider<TCustom extends ExtraClaims = Record<never, never>>(opts: {
13
50
  domain: string;
14
- } & Omit<CustomProviderOptions, "issuer" | "audience">): Promise<OAuthConfig>;
51
+ } & Omit<CustomProviderOptions, "issuer" | "audience">): OAuthProvider<ClerkClaims & TCustom & RegisteredClaims>;
@@ -1,9 +1,5 @@
1
1
  import { customProvider } from "./custom.js";
2
- /** Normalises a bare host to an https issuer URL and strips a trailing slash. */
3
- function toIssuerUrl(domain) {
4
- const withScheme = /^https?:\/\//.test(domain) ? domain : `https://${domain}`;
5
- return withScheme.replace(/\/$/, "");
6
- }
2
+ import { toIssuerUrl } from "./shared.js";
7
3
  /**
8
4
  * OAuth provider for Clerk. `domain` is the Frontend API URL (e.g.
9
5
  * `acme.clerk.accounts.dev`, or a production custom domain). Requires Dynamic
@@ -15,6 +11,9 @@ function toIssuerUrl(domain) {
15
11
  */
16
12
  export function clerkProvider(opts) {
17
13
  const { domain, ...rest } = opts;
18
- return customProvider({ issuer: toIssuerUrl(domain), ...rest });
14
+ return customProvider({
15
+ issuer: toIssuerUrl(domain),
16
+ ...rest,
17
+ });
19
18
  }
20
19
  //# sourceMappingURL=clerk.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"clerk.js","sourceRoot":"","sources":["../../../../src/server/auth/providers/clerk.ts"],"names":[],"mappings":"AACA,OAAO,EAA8B,cAAc,EAAE,MAAM,aAAa,CAAC;AAEzE,iFAAiF;AACjF,SAAS,WAAW,CAAC,MAAc;IACjC,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,MAAM,EAAE,CAAC;IAC9E,OAAO,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAC3B,IAA6E;IAE7E,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;IACjC,OAAO,cAAc,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;AAClE,CAAC","sourcesContent":["import type { OAuthConfig } from \"../index.js\";\nimport { type CustomProviderOptions, customProvider } from \"./custom.js\";\n\n/** Normalises a bare host to an https issuer URL and strips a trailing slash. */\nfunction toIssuerUrl(domain: string): string {\n const withScheme = /^https?:\\/\\//.test(domain) ? domain : `https://${domain}`;\n return withScheme.replace(/\\/$/, \"\");\n}\n\n/**\n * OAuth provider for Clerk. `domain` is the Frontend API URL (e.g.\n * `acme.clerk.accounts.dev`, or a production custom domain). Requires Dynamic\n * Client Registration enabled on the instance, and the OAuth application set to\n * issue JWT access tokens (opaque tokens can't be JWKS-verified).\n *\n * Clerk access tokens carry no `aud` claim, so there is no `audience` option —\n * verification is issuer + JWKS only (matching Clerk's own `mcpAuthClerk`).\n */\nexport function clerkProvider(\n opts: { domain: string } & Omit<CustomProviderOptions, \"issuer\" | \"audience\">,\n): Promise<OAuthConfig> {\n const { domain, ...rest } = opts;\n return customProvider({ issuer: toIssuerUrl(domain), ...rest });\n}\n"]}
1
+ {"version":3,"file":"clerk.js","sourceRoot":"","sources":["../../../../src/server/auth/providers/clerk.ts"],"names":[],"mappings":"AAGA,OAAO,EAA8B,cAAc,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAsC1C;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAG3B,IAA6E;IAE7E,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;IACjC,OAAO,cAAc,CAAwB;QAC3C,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC;QAC3B,GAAG,IAAI;KACR,CAAC,CAAC;AACL,CAAC","sourcesContent":["import type { ExtraClaims } from \"../../auth.js\";\nimport type { OAuthProvider } from \"../index.js\";\nimport type { RegisteredClaims } from \"../verify.js\";\nimport { type CustomProviderOptions, customProvider } from \"./custom.js\";\nimport { toIssuerUrl } from \"./shared.js\";\n\n/**\n * Claims a Clerk session token (v2) carries. All optional: `o` and `act` appear\n * only with organizations and impersonation, and `email` only when a JWT\n * template adds it.\n *\n * @see https://clerk.com/docs/backend-requests/resources/session-tokens\n * @see https://clerk.com/docs/backend-requests/jwt-templates\n */\nexport type ClerkClaims = {\n /** Session id. */\n sid?: string;\n /** Authorized party: the origin the token was issued to. */\n azp?: string;\n /** Session token version. */\n v?: number;\n /** Factor verification age, `[since-first-factor, since-second-factor]`. */\n fva?: number[];\n /** Active organization, when the user has one. */\n o?: {\n /** Organization id. */\n id?: string;\n /** Organization slug. */\n slg?: string;\n /** Role within the organization. */\n rol?: string;\n /** Permissions within the organization. */\n per?: string;\n /** Feature-permission map backing `per`. */\n fpm?: string;\n };\n /** Actor, present while a user is being impersonated. */\n act?: Record<string, unknown>;\n /** Only when a JWT template adds it; not a default Clerk claim. */\n email?: string;\n};\n\n/**\n * OAuth provider for Clerk. `domain` is the Frontend API URL (e.g.\n * `acme.clerk.accounts.dev`, or a production custom domain). Requires Dynamic\n * Client Registration enabled on the instance, and the OAuth application set to\n * issue JWT access tokens (opaque tokens can't be JWKS-verified).\n *\n * Clerk access tokens carry no `aud` claim, so there is no `audience` option —\n * verification is issuer + JWKS only (matching Clerk's own `mcpAuthClerk`).\n */\nexport function clerkProvider<\n TCustom extends ExtraClaims = Record<never, never>,\n>(\n opts: { domain: string } & Omit<CustomProviderOptions, \"issuer\" | \"audience\">,\n): OAuthProvider<ClerkClaims & TCustom & RegisteredClaims> {\n const { domain, ...rest } = opts;\n return customProvider<ClerkClaims & TCustom>({\n issuer: toIssuerUrl(domain),\n ...rest,\n });\n}\n"]}
@@ -1,8 +1,10 @@
1
1
  // @vitest-environment node
2
2
  import { afterEach, describe, expect, it, vi } from "vitest";
3
3
  import { clerkProvider } from "./clerk.js";
4
+ import { lastJwksConfig as jwks } from "./verify-spy.js";
5
+ vi.mock("../verify.js", () => import("./verify-spy.js"));
4
6
  afterEach(() => vi.restoreAllMocks());
5
- function doc(issuer, extra = {}) {
7
+ function doc(issuer) {
6
8
  return {
7
9
  issuer,
8
10
  authorization_endpoint: `${issuer}/oauth/authorize`,
@@ -11,38 +13,18 @@ function doc(issuer, extra = {}) {
11
13
  response_types_supported: ["code"],
12
14
  scopes_supported: ["profile", "email"],
13
15
  jwks_uri: `${issuer}/.well-known/jwks.json`,
14
- ...extra,
15
16
  };
16
17
  }
17
18
  describe("clerkProvider", () => {
18
- it("builds the issuer from the Frontend API domain", async () => {
19
+ it("verifies issuer + JWKS with no audience (Clerk tokens carry no aud)", async () => {
19
20
  const issuer = "https://acme.clerk.accounts.dev";
20
21
  const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify(doc(issuer)), {
21
22
  headers: { "content-type": "application/json" },
22
23
  }));
23
- const config = await clerkProvider({
24
- domain: "acme.clerk.accounts.dev",
25
- });
24
+ await clerkProvider({ domain: "acme.clerk.accounts.dev" }).resolve();
26
25
  expect(fetchSpy).toHaveBeenCalledWith(`${issuer}/.well-known/openid-configuration`, expect.anything());
27
- expect(config.verify.issuer).toBe(issuer);
28
- // Clerk tokens carry no aud — the provider configures no audience check.
29
- expect(config.verify.audience).toBeUndefined();
30
- });
31
- it("uses oauth-authorization-server when oidc omits the registration_endpoint", async () => {
32
- const issuer = "https://acme.clerk.accounts.dev";
33
- vi.spyOn(globalThis, "fetch").mockImplementation(async (input) => {
34
- const url = String(input);
35
- if (url.endsWith("/.well-known/openid-configuration")) {
36
- return new Response(JSON.stringify(doc(issuer, { registration_endpoint: undefined })), { headers: { "content-type": "application/json" } });
37
- }
38
- return new Response(JSON.stringify(doc(issuer)), {
39
- headers: { "content-type": "application/json" },
40
- });
41
- });
42
- const config = await clerkProvider({
43
- domain: "acme.clerk.accounts.dev",
44
- });
45
- expect(config.oauthMetadata.registration_endpoint).toBe(`${issuer}/oauth/register`);
26
+ expect(jwks().issuer).toBe(issuer);
27
+ expect(jwks().audience).toBeUndefined();
46
28
  });
47
29
  });
48
30
  //# sourceMappingURL=clerk.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"clerk.test.js","sourceRoot":"","sources":["../../../../src/server/auth/providers/clerk.test.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,SAAS,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC,CAAC;AAEtC,SAAS,GAAG,CAAC,MAAc,EAAE,QAAiC,EAAE;IAC9D,OAAO;QACL,MAAM;QACN,sBAAsB,EAAE,GAAG,MAAM,kBAAkB;QACnD,cAAc,EAAE,GAAG,MAAM,cAAc;QACvC,qBAAqB,EAAE,GAAG,MAAM,iBAAiB;QACjD,wBAAwB,EAAE,CAAC,MAAM,CAAC;QAClC,gBAAgB,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC;QACtC,QAAQ,EAAE,GAAG,MAAM,wBAAwB;QAC3C,GAAG,KAAK;KACT,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,MAAM,GAAG,iCAAiC,CAAC;QACjD,MAAM,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,iBAAiB,CAC9D,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE;YACxC,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAChD,CAAC,CACH,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC;YACjC,MAAM,EAAE,yBAAyB;SAClC,CAAC,CAAC;QAEH,MAAM,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CACnC,GAAG,MAAM,mCAAmC,EAC5C,MAAM,CAAC,QAAQ,EAAE,CAClB,CAAC;QACF,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1C,yEAAyE;QACzE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,aAAa,EAAE,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2EAA2E,EAAE,KAAK,IAAI,EAAE;QACzF,MAAM,MAAM,GAAG,iCAAiC,CAAC;QACjD,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;YAC/D,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,GAAG,CAAC,QAAQ,CAAC,mCAAmC,CAAC,EAAE,CAAC;gBACtD,OAAO,IAAI,QAAQ,CACjB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,qBAAqB,EAAE,SAAS,EAAE,CAAC,CAAC,EACjE,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CACpD,CAAC;YACJ,CAAC;YACD,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE;gBAC/C,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;aAChD,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC;YACjC,MAAM,EAAE,yBAAyB;SAClC,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC,IAAI,CACrD,GAAG,MAAM,iBAAiB,CAC3B,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["// @vitest-environment node\nimport { afterEach, describe, expect, it, vi } from \"vitest\";\nimport { clerkProvider } from \"./clerk.js\";\n\nafterEach(() => vi.restoreAllMocks());\n\nfunction doc(issuer: string, extra: Record<string, unknown> = {}) {\n return {\n issuer,\n authorization_endpoint: `${issuer}/oauth/authorize`,\n token_endpoint: `${issuer}/oauth/token`,\n registration_endpoint: `${issuer}/oauth/register`,\n response_types_supported: [\"code\"],\n scopes_supported: [\"profile\", \"email\"],\n jwks_uri: `${issuer}/.well-known/jwks.json`,\n ...extra,\n };\n}\n\ndescribe(\"clerkProvider\", () => {\n it(\"builds the issuer from the Frontend API domain\", async () => {\n const issuer = \"https://acme.clerk.accounts.dev\";\n const fetchSpy = vi.spyOn(globalThis, \"fetch\").mockResolvedValue(\n new Response(JSON.stringify(doc(issuer)), {\n headers: { \"content-type\": \"application/json\" },\n }),\n );\n\n const config = await clerkProvider({\n domain: \"acme.clerk.accounts.dev\",\n });\n\n expect(fetchSpy).toHaveBeenCalledWith(\n `${issuer}/.well-known/openid-configuration`,\n expect.anything(),\n );\n expect(config.verify.issuer).toBe(issuer);\n // Clerk tokens carry no aud — the provider configures no audience check.\n expect(config.verify.audience).toBeUndefined();\n });\n\n it(\"uses oauth-authorization-server when oidc omits the registration_endpoint\", async () => {\n const issuer = \"https://acme.clerk.accounts.dev\";\n vi.spyOn(globalThis, \"fetch\").mockImplementation(async (input) => {\n const url = String(input);\n if (url.endsWith(\"/.well-known/openid-configuration\")) {\n return new Response(\n JSON.stringify(doc(issuer, { registration_endpoint: undefined })),\n { headers: { \"content-type\": \"application/json\" } },\n );\n }\n return new Response(JSON.stringify(doc(issuer)), {\n headers: { \"content-type\": \"application/json\" },\n });\n });\n\n const config = await clerkProvider({\n domain: \"acme.clerk.accounts.dev\",\n });\n\n expect(config.oauthMetadata.registration_endpoint).toBe(\n `${issuer}/oauth/register`,\n );\n });\n});\n"]}
1
+ {"version":3,"file":"clerk.test.js","sourceRoot":"","sources":["../../../../src/server/auth/providers/clerk.test.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,cAAc,IAAI,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAEzD,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC;AAEzD,SAAS,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC,CAAC;AAEtC,SAAS,GAAG,CAAC,MAAc;IACzB,OAAO;QACL,MAAM;QACN,sBAAsB,EAAE,GAAG,MAAM,kBAAkB;QACnD,cAAc,EAAE,GAAG,MAAM,cAAc;QACvC,qBAAqB,EAAE,GAAG,MAAM,iBAAiB;QACjD,wBAAwB,EAAE,CAAC,MAAM,CAAC;QAClC,gBAAgB,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC;QACtC,QAAQ,EAAE,GAAG,MAAM,wBAAwB;KAC5C,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,qEAAqE,EAAE,KAAK,IAAI,EAAE;QACnF,MAAM,MAAM,GAAG,iCAAiC,CAAC;QACjD,MAAM,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,iBAAiB,CAC9D,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE;YACxC,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAChD,CAAC,CACH,CAAC;QAEF,MAAM,aAAa,CAAC,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;QAErE,MAAM,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CACnC,GAAG,MAAM,mCAAmC,EAC5C,MAAM,CAAC,QAAQ,EAAE,CAClB,CAAC;QACF,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,CAAC,aAAa,EAAE,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["// @vitest-environment node\nimport { afterEach, describe, expect, it, vi } from \"vitest\";\nimport { clerkProvider } from \"./clerk.js\";\nimport { lastJwksConfig as jwks } from \"./verify-spy.js\";\n\nvi.mock(\"../verify.js\", () => import(\"./verify-spy.js\"));\n\nafterEach(() => vi.restoreAllMocks());\n\nfunction doc(issuer: string) {\n return {\n issuer,\n authorization_endpoint: `${issuer}/oauth/authorize`,\n token_endpoint: `${issuer}/oauth/token`,\n registration_endpoint: `${issuer}/oauth/register`,\n response_types_supported: [\"code\"],\n scopes_supported: [\"profile\", \"email\"],\n jwks_uri: `${issuer}/.well-known/jwks.json`,\n };\n}\n\ndescribe(\"clerkProvider\", () => {\n it(\"verifies issuer + JWKS with no audience (Clerk tokens carry no aud)\", async () => {\n const issuer = \"https://acme.clerk.accounts.dev\";\n const fetchSpy = vi.spyOn(globalThis, \"fetch\").mockResolvedValue(\n new Response(JSON.stringify(doc(issuer)), {\n headers: { \"content-type\": \"application/json\" },\n }),\n );\n\n await clerkProvider({ domain: \"acme.clerk.accounts.dev\" }).resolve();\n\n expect(fetchSpy).toHaveBeenCalledWith(\n `${issuer}/.well-known/openid-configuration`,\n expect.anything(),\n );\n expect(jwks().issuer).toBe(issuer);\n expect(jwks().audience).toBeUndefined();\n });\n});\n"]}
@@ -1,5 +1,7 @@
1
- import type { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
2
- import type { OAuthConfig } from "../index.js";
1
+ import type { OAuthMetadata } from "@modelcontextprotocol/server";
2
+ import type { ExtraClaims } from "../../auth.js";
3
+ import type { OAuthProvider } from "../index.js";
4
+ import { type RegisteredClaims } from "../verify.js";
3
5
  /** Options accepted by {@link customProvider} and the branded providers. */
4
6
  export type CustomProviderOptions = {
5
7
  issuer: string;
@@ -9,9 +11,30 @@ export type CustomProviderOptions = {
9
11
  audience?: string;
10
12
  /** Omit to let the server infer the resource origin from request headers. */
11
13
  baseUrl?: string;
14
+ /** Advertise THIS server as the authorization server (skybridge-as-AS): the
15
+ * served AS metadata `issuer` and the PRM `authorization_servers` use this URL
16
+ * instead of the IdP's. Needed when skybridge must sit in the auth path — Auth0
17
+ * (the `audience` must be baked into `/authorize`) or the Alpic DCR proxy (Alpic
18
+ * injects the registration endpoint). Use the static public URL; `verify.issuer`
19
+ * stays the IdP (the token's real `iss`). */
20
+ serverUrl?: string;
21
+ /** Advertise this URL as the authorization server (served AS metadata `issuer`
22
+ * and PRM `authorization_servers`). Its own discovery document supplies the
23
+ * advertised endpoints, DCR registration, scopes and the `iss` tokens are
24
+ * verified against; only `jwks_uri` still comes from `issuer`'s document. Use
25
+ * when DCR/authorize/token live at a different URL than the discovery issuer
26
+ * (e.g. Descope's agentic URL vs. its base-project URL). `serverUrl` wins if
27
+ * both are set. */
28
+ authorizationServer?: string;
12
29
  scopes?: string[];
13
30
  requiredScopes?: string[];
14
31
  metadataOverrides?: Omit<Partial<OAuthMetadata>, "issuer">;
15
32
  };
16
- /** Builds a complete {@link OAuthConfig} for a DCR-compatible IdP via discovery. */
17
- export declare function customProvider(opts: CustomProviderOptions): Promise<OAuthConfig>;
33
+ /**
34
+ * Builds a complete {@link OAuthConfig} from an IdP's OAuth discovery document.
35
+ *
36
+ * @typeParam TExtra - Claims the IdP puts in the access token, reaching handlers
37
+ * as `extra.http?.authInfo.extra`. The branded providers pass their documented claims;
38
+ * pass your own when wiring an IdP by hand.
39
+ */
40
+ export declare function customProvider<TExtra extends ExtraClaims = ExtraClaims>(opts: CustomProviderOptions): OAuthProvider<TExtra & RegisteredClaims>;
@@ -1,27 +1,58 @@
1
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) {
2
+ import { createJwksVerifier } from "../verify.js";
3
+ /**
4
+ * Builds a complete {@link OAuthConfig} from an IdP's OAuth discovery document.
5
+ *
6
+ * @typeParam TExtra - Claims the IdP puts in the access token, reaching handlers
7
+ * as `extra.http?.authInfo.extra`. The branded providers pass their documented claims;
8
+ * pass your own when wiring an IdP by hand.
9
+ */
10
+ export function customProvider(opts) {
11
+ return { resolve: () => resolveCustomProvider(opts) };
12
+ }
13
+ async function resolveCustomProvider(opts) {
4
14
  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
- }
15
+ // JWKS verification needs a signing-key URL; without it the server can't verify
16
+ // tokens. (`registration_endpoint` is optional per RFC 8414 — a no-DCR IdP simply
17
+ // omits it; a proxy like Alpic, or pre-registered clients, supply registration.)
9
18
  if (!discovered.jwks_uri) {
10
19
  throw new Error(`${opts.issuer} discovery has no jwks_uri; JWKS verification requires it.`);
11
20
  }
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 };
21
+ // Overrides adjust only advertised metadata; the trust anchor (issuer, jwks_uri)
22
+ // always comes from validated discovery.
23
+ const { issuer: _issuer, jwks_uri: _jwks, ...overrides } = opts.metadataOverrides ?? {};
24
+ let advertised = discovered;
25
+ if (opts.authorizationServer) {
26
+ try {
27
+ advertised = await discoverAuthorizationServer(opts.authorizationServer);
28
+ }
29
+ catch (err) {
30
+ console.warn(`OAuth discovery failed for authorizationServer ${opts.authorizationServer}; advertising ${opts.issuer} metadata instead: ${err instanceof Error ? err.message : String(err)}`);
31
+ }
32
+ }
33
+ const base = { ...advertised, ...overrides };
34
+ const scopesSupported = opts.scopes ?? base.scopes_supported;
35
+ // serverUrl (skybridge-as-AS) overrides the advertised issuer, keeping the
36
+ // IdP as the token issuer. authorizationServer re-asserts its own URL so a
37
+ // failed AS discovery still points clients at the AS. serverUrl wins.
38
+ const advertisedIssuer = opts.serverUrl?.replace(/\/$/, "") ??
39
+ opts.authorizationServer?.replace(/\/$/, "");
40
+ const oauthMetadata = advertisedIssuer
41
+ ? {
42
+ ...base,
43
+ issuer: advertisedIssuer,
44
+ scopes_supported: scopesSupported,
45
+ }
46
+ : base;
16
47
  return {
17
48
  baseUrl: opts.baseUrl,
18
49
  oauthMetadata,
19
- verify: {
20
- issuer: discovered.issuer,
50
+ verifier: createJwksVerifier({
51
+ issuer: advertised.issuer,
21
52
  audience: opts.audience,
22
- jwksUri: discovered.jwks_uri,
23
- },
24
- scopesSupported: opts.scopes ?? oauthMetadata.scopes_supported,
53
+ jwksUri: advertised.jwks_uri ?? discovered.jwks_uri,
54
+ }),
55
+ scopesSupported,
25
56
  requiredScopes: opts.requiredScopes,
26
57
  };
27
58
  }
@@ -1 +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;AAiBzB,oFAAoF;AACpF,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAA2B;IAE3B,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,GAAG,CAAC,IAAI,CAAC,iBAAiB,IAAI,EAAE,CAAgC,CAAC;IAClE,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/** Options accepted by {@link customProvider} and the branded providers. */\nexport type CustomProviderOptions = {\n issuer: string;\n /** Expected token `aud`. Omit to skip audience verification — only for IdPs\n * that don't bind an audience (e.g. Clerk). Branded providers whose IdP does\n * bind an audience re-require it in their own options. */\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};\n\n/** Builds a complete {@link OAuthConfig} for a DCR-compatible IdP via discovery. */\nexport async function customProvider(\n opts: CustomProviderOptions,\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 } = (opts.metadataOverrides ?? {}) as Partial<DiscoveredMetadata>;\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"]}
1
+ {"version":3,"file":"custom.js","sourceRoot":"","sources":["../../../../src/server/auth/providers/custom.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,2BAA2B,GAC5B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,kBAAkB,EAAyB,MAAM,cAAc,CAAC;AA+BzE;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,IAA2B;IAE3B,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,qBAAqB,CAAS,IAAI,CAAC,EAAE,CAAC;AAChE,CAAC;AAED,KAAK,UAAU,qBAAqB,CAClC,IAA2B;IAE3B,MAAM,UAAU,GAAG,MAAM,2BAA2B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAElE,gFAAgF;IAChF,kFAAkF;IAClF,iFAAiF;IACjF,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,CAAC,MAAM,4DAA4D,CAC3E,CAAC;IACJ,CAAC;IAED,iFAAiF;IACjF,yCAAyC;IACzC,MAAM,EACJ,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,KAAK,EACf,GAAG,SAAS,EACb,GAAgC,IAAI,CAAC,iBAAiB,IAAI,EAAE,CAAC;IAC9D,IAAI,UAAU,GAAG,UAAU,CAAC;IAC5B,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,UAAU,GAAG,MAAM,2BAA2B,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QAC3E,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CACV,kDAAkD,IAAI,CAAC,mBAAmB,iBAAiB,IAAI,CAAC,MAAM,sBAAsB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC/K,CAAC;QACJ,CAAC;IACH,CAAC;IACD,MAAM,IAAI,GAAuB,EAAE,GAAG,UAAU,EAAE,GAAG,SAAS,EAAE,CAAC;IACjE,MAAM,eAAe,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,gBAAgB,CAAC;IAE7D,2EAA2E;IAC3E,2EAA2E;IAC3E,sEAAsE;IACtE,MAAM,gBAAgB,GACpB,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC,mBAAmB,EAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC/C,MAAM,aAAa,GAAuB,gBAAgB;QACxD,CAAC,CAAC;YACE,GAAG,IAAI;YACP,MAAM,EAAE,gBAAgB;YACxB,gBAAgB,EAAE,eAAe;SAClC;QACH,CAAC,CAAC,IAAI,CAAC;IAET,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,aAAa;QACb,QAAQ,EAAE,kBAAkB,CAAS;YACnC,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,OAAO,EAAE,UAAU,CAAC,QAAQ,IAAI,UAAU,CAAC,QAAQ;SACpD,CAAC;QACF,eAAe;QACf,cAAc,EAAE,IAAI,CAAC,cAAc;KACpC,CAAC;AACJ,CAAC","sourcesContent":["import type { OAuthMetadata } from \"@modelcontextprotocol/server\";\nimport type { ExtraClaims } from \"../../auth.js\";\nimport {\n type DiscoveredMetadata,\n discoverAuthorizationServer,\n} from \"../discovery.js\";\nimport type { OAuthConfig, OAuthProvider } from \"../index.js\";\nimport { createJwksVerifier, type RegisteredClaims } from \"../verify.js\";\n\n/** Options accepted by {@link customProvider} and the branded providers. */\nexport type CustomProviderOptions = {\n issuer: string;\n /** Expected token `aud`. Omit to skip audience verification — only for IdPs\n * that don't bind an audience (e.g. Clerk). Branded providers whose IdP does\n * bind an audience re-require it in their own options. */\n audience?: string;\n /** Omit to let the server infer the resource origin from request headers. */\n baseUrl?: string;\n /** Advertise THIS server as the authorization server (skybridge-as-AS): the\n * served AS metadata `issuer` and the PRM `authorization_servers` use this URL\n * instead of the IdP's. Needed when skybridge must sit in the auth path — Auth0\n * (the `audience` must be baked into `/authorize`) or the Alpic DCR proxy (Alpic\n * injects the registration endpoint). Use the static public URL; `verify.issuer`\n * stays the IdP (the token's real `iss`). */\n serverUrl?: string;\n /** Advertise this URL as the authorization server (served AS metadata `issuer`\n * and PRM `authorization_servers`). Its own discovery document supplies the\n * advertised endpoints, DCR registration, scopes and the `iss` tokens are\n * verified against; only `jwks_uri` still comes from `issuer`'s document. Use\n * when DCR/authorize/token live at a different URL than the discovery issuer\n * (e.g. Descope's agentic URL vs. its base-project URL). `serverUrl` wins if\n * both are set. */\n authorizationServer?: string;\n scopes?: string[];\n requiredScopes?: string[];\n metadataOverrides?: Omit<Partial<OAuthMetadata>, \"issuer\">;\n};\n\n/**\n * Builds a complete {@link OAuthConfig} from an IdP's OAuth discovery document.\n *\n * @typeParam TExtra - Claims the IdP puts in the access token, reaching handlers\n * as `extra.http?.authInfo.extra`. The branded providers pass their documented claims;\n * pass your own when wiring an IdP by hand.\n */\nexport function customProvider<TExtra extends ExtraClaims = ExtraClaims>(\n opts: CustomProviderOptions,\n): OAuthProvider<TExtra & RegisteredClaims> {\n return { resolve: () => resolveCustomProvider<TExtra>(opts) };\n}\n\nasync function resolveCustomProvider<TExtra extends ExtraClaims>(\n opts: CustomProviderOptions,\n): Promise<OAuthConfig<TExtra & RegisteredClaims>> {\n const discovered = await discoverAuthorizationServer(opts.issuer);\n\n // JWKS verification needs a signing-key URL; without it the server can't verify\n // tokens. (`registration_endpoint` is optional per RFC 8414 — a no-DCR IdP simply\n // omits it; a proxy like Alpic, or pre-registered clients, supply registration.)\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; the trust anchor (issuer, jwks_uri)\n // always comes from validated discovery.\n const {\n issuer: _issuer,\n jwks_uri: _jwks,\n ...overrides\n }: Partial<DiscoveredMetadata> = opts.metadataOverrides ?? {};\n let advertised = discovered;\n if (opts.authorizationServer) {\n try {\n advertised = await discoverAuthorizationServer(opts.authorizationServer);\n } catch (err) {\n console.warn(\n `OAuth discovery failed for authorizationServer ${opts.authorizationServer}; advertising ${opts.issuer} metadata instead: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n const base: DiscoveredMetadata = { ...advertised, ...overrides };\n const scopesSupported = opts.scopes ?? base.scopes_supported;\n\n // serverUrl (skybridge-as-AS) overrides the advertised issuer, keeping the\n // IdP as the token issuer. authorizationServer re-asserts its own URL so a\n // failed AS discovery still points clients at the AS. serverUrl wins.\n const advertisedIssuer =\n opts.serverUrl?.replace(/\\/$/, \"\") ??\n opts.authorizationServer?.replace(/\\/$/, \"\");\n const oauthMetadata: DiscoveredMetadata = advertisedIssuer\n ? {\n ...base,\n issuer: advertisedIssuer,\n scopes_supported: scopesSupported,\n }\n : base;\n\n return {\n baseUrl: opts.baseUrl,\n oauthMetadata,\n verifier: createJwksVerifier<TExtra>({\n issuer: advertised.issuer,\n audience: opts.audience,\n jwksUri: advertised.jwks_uri ?? discovered.jwks_uri,\n }),\n scopesSupported,\n requiredScopes: opts.requiredScopes,\n };\n}\n"]}