skybridge 0.0.0-dev.d39c413 → 0.0.0-dev.d3b5bba

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 (386) hide show
  1. package/README.md +125 -124
  2. package/dist/cli/build-helpers.d.ts +11 -0
  3. package/dist/cli/build-helpers.js +107 -0
  4. package/dist/cli/build-helpers.js.map +1 -0
  5. package/dist/cli/build-helpers.test.d.ts +1 -0
  6. package/dist/cli/build-helpers.test.js +130 -0
  7. package/dist/cli/build-helpers.test.js.map +1 -0
  8. package/dist/cli/build-steps.d.ts +2 -0
  9. package/dist/cli/build-steps.js +73 -0
  10. package/dist/cli/build-steps.js.map +1 -0
  11. package/dist/cli/build-steps.test.d.ts +1 -0
  12. package/dist/cli/build-steps.test.js +52 -0
  13. package/dist/cli/build-steps.test.js.map +1 -0
  14. package/dist/cli/detect-port.d.ts +2 -2
  15. package/dist/cli/detect-port.js +9 -20
  16. package/dist/cli/detect-port.js.map +1 -1
  17. package/dist/cli/header.d.ts +1 -1
  18. package/dist/cli/header.js.map +1 -1
  19. package/dist/cli/resolve-skybridge-config.d.ts +5 -0
  20. package/dist/cli/resolve-skybridge-config.js +18 -0
  21. package/dist/cli/resolve-skybridge-config.js.map +1 -0
  22. package/dist/cli/run-command.js.map +1 -1
  23. package/dist/cli/run-plain.d.ts +18 -0
  24. package/dist/cli/run-plain.js +89 -0
  25. package/dist/cli/run-plain.js.map +1 -0
  26. package/dist/cli/telemetry.js.map +1 -1
  27. package/dist/cli/tunnel-control-server.js.map +1 -1
  28. package/dist/cli/tunnel-control-server.test.js.map +1 -1
  29. package/dist/cli/tunnel-handler.js.map +1 -1
  30. package/dist/cli/tunnel-handler.test.js.map +1 -1
  31. package/dist/cli/tunnel.js +2 -2
  32. package/dist/cli/tunnel.js.map +1 -1
  33. package/dist/cli/tunnel.test.js.map +1 -1
  34. package/dist/cli/types.js.map +1 -1
  35. package/dist/cli/use-execute-steps.js.map +1 -1
  36. package/dist/cli/use-messages.js.map +1 -1
  37. package/dist/cli/use-nodemon.d.ts +14 -0
  38. package/dist/cli/use-nodemon.js +72 -52
  39. package/dist/cli/use-nodemon.js.map +1 -1
  40. package/dist/cli/use-open-browser.d.ts +1 -0
  41. package/dist/cli/use-open-browser.js +44 -0
  42. package/dist/cli/use-open-browser.js.map +1 -0
  43. package/dist/cli/use-tunnel.js.map +1 -1
  44. package/dist/cli/use-typescript-check.d.ts +8 -2
  45. package/dist/cli/use-typescript-check.js +71 -68
  46. package/dist/cli/use-typescript-check.js.map +1 -1
  47. package/dist/commands/build.d.ts +0 -3
  48. package/dist/commands/build.js +2 -79
  49. package/dist/commands/build.js.map +1 -1
  50. package/dist/commands/create.d.ts +9 -0
  51. package/dist/commands/create.js +30 -0
  52. package/dist/commands/create.js.map +1 -0
  53. package/dist/commands/dev.d.ts +2 -0
  54. package/dist/commands/dev.js +55 -0
  55. package/dist/commands/dev.js.map +1 -1
  56. package/dist/commands/start.js +7 -1
  57. package/dist/commands/start.js.map +1 -1
  58. package/dist/commands/telemetry/disable.js.map +1 -1
  59. package/dist/commands/telemetry/enable.js.map +1 -1
  60. package/dist/commands/telemetry/status.js.map +1 -1
  61. package/dist/server/asset-base-url-transform-plugin.d.ts +1 -0
  62. package/dist/server/asset-base-url-transform-plugin.js +16 -1
  63. package/dist/server/asset-base-url-transform-plugin.js.map +1 -1
  64. package/dist/server/asset-base-url-transform-plugin.test.js +51 -1
  65. package/dist/server/asset-base-url-transform-plugin.test.js.map +1 -1
  66. package/dist/server/auth/discovery.d.ts +32 -0
  67. package/dist/server/auth/discovery.js +56 -0
  68. package/dist/server/auth/discovery.js.map +1 -0
  69. package/dist/server/auth/discovery.test.d.ts +1 -0
  70. package/dist/server/auth/discovery.test.js +93 -0
  71. package/dist/server/auth/discovery.test.js.map +1 -0
  72. package/dist/server/auth/index.d.ts +18 -0
  73. package/dist/server/auth/index.js +2 -0
  74. package/dist/server/auth/index.js.map +1 -0
  75. package/dist/server/auth/providers/auth0.d.ts +18 -0
  76. package/dist/server/auth/providers/auth0.js +31 -0
  77. package/dist/server/auth/providers/auth0.js.map +1 -0
  78. package/dist/server/auth/providers/auth0.test.d.ts +1 -0
  79. package/dist/server/auth/providers/auth0.test.js +48 -0
  80. package/dist/server/auth/providers/auth0.test.js.map +1 -0
  81. package/dist/server/auth/providers/authplane.d.ts +47 -0
  82. package/dist/server/auth/providers/authplane.js +57 -0
  83. package/dist/server/auth/providers/authplane.js.map +1 -0
  84. package/dist/server/auth/providers/authplane.test.d.ts +1 -0
  85. package/dist/server/auth/providers/authplane.test.js +138 -0
  86. package/dist/server/auth/providers/authplane.test.js.map +1 -0
  87. package/dist/server/auth/providers/clerk.d.ts +14 -0
  88. package/dist/server/auth/providers/clerk.js +16 -0
  89. package/dist/server/auth/providers/clerk.js.map +1 -0
  90. package/dist/server/auth/providers/clerk.test.d.ts +1 -0
  91. package/dist/server/auth/providers/clerk.test.js +28 -0
  92. package/dist/server/auth/providers/clerk.test.js.map +1 -0
  93. package/dist/server/auth/providers/custom.d.ts +32 -0
  94. package/dist/server/auth/providers/custom.js +49 -0
  95. package/dist/server/auth/providers/custom.js.map +1 -0
  96. package/dist/server/auth/providers/custom.test.d.ts +1 -0
  97. package/dist/server/auth/providers/custom.test.js +156 -0
  98. package/dist/server/auth/providers/custom.test.js.map +1 -0
  99. package/dist/server/auth/providers/descope.d.ts +15 -0
  100. package/dist/server/auth/providers/descope.js +44 -0
  101. package/dist/server/auth/providers/descope.js.map +1 -0
  102. package/dist/server/auth/providers/descope.test.d.ts +1 -0
  103. package/dist/server/auth/providers/descope.test.js +60 -0
  104. package/dist/server/auth/providers/descope.test.js.map +1 -0
  105. package/dist/server/auth/providers/shared.d.ts +2 -0
  106. package/dist/server/auth/providers/shared.js +6 -0
  107. package/dist/server/auth/providers/shared.js.map +1 -0
  108. package/dist/server/auth/providers/shared.test.d.ts +1 -0
  109. package/dist/server/auth/providers/shared.test.js +10 -0
  110. package/dist/server/auth/providers/shared.test.js.map +1 -0
  111. package/dist/server/auth/providers/stytch.d.ts +12 -0
  112. package/dist/server/auth/providers/stytch.js +13 -0
  113. package/dist/server/auth/providers/stytch.js.map +1 -0
  114. package/dist/server/auth/providers/workos.d.ts +11 -0
  115. package/dist/server/auth/providers/workos.js +12 -0
  116. package/dist/server/auth/providers/workos.js.map +1 -0
  117. package/dist/server/auth/security-schemes.d.ts +23 -0
  118. package/dist/server/auth/security-schemes.js +64 -0
  119. package/dist/server/auth/security-schemes.js.map +1 -0
  120. package/dist/server/auth/security-schemes.test.d.ts +1 -0
  121. package/dist/server/auth/security-schemes.test.js +99 -0
  122. package/dist/server/auth/security-schemes.test.js.map +1 -0
  123. package/dist/server/auth/setup.d.ts +6 -0
  124. package/dist/server/auth/setup.js +92 -0
  125. package/dist/server/auth/setup.js.map +1 -0
  126. package/dist/server/auth/setup.test.d.ts +1 -0
  127. package/dist/server/auth/setup.test.js +474 -0
  128. package/dist/server/auth/setup.test.js.map +1 -0
  129. package/dist/server/auth/verify.d.ts +12 -0
  130. package/dist/server/auth/verify.js +38 -0
  131. package/dist/server/auth/verify.js.map +1 -0
  132. package/dist/server/auth/verify.test.d.ts +1 -0
  133. package/dist/server/auth/verify.test.js +100 -0
  134. package/dist/server/auth/verify.test.js.map +1 -0
  135. package/dist/server/auth.d.ts +20 -0
  136. package/dist/server/auth.js +28 -0
  137. package/dist/server/auth.js.map +1 -0
  138. package/dist/server/build-manifest.test.d.ts +1 -0
  139. package/dist/server/build-manifest.test.js +27 -0
  140. package/dist/server/build-manifest.test.js.map +1 -0
  141. package/dist/server/content-helpers.d.ts +40 -0
  142. package/dist/server/content-helpers.js +33 -0
  143. package/dist/server/content-helpers.js.map +1 -1
  144. package/dist/server/content-helpers.test.js.map +1 -1
  145. package/dist/server/express.d.ts +1 -5
  146. package/dist/server/express.js +2 -4
  147. package/dist/server/express.js.map +1 -1
  148. package/dist/server/express.test.js +214 -71
  149. package/dist/server/express.test.js.map +1 -1
  150. package/dist/server/file-ref.d.ts +28 -0
  151. package/dist/server/file-ref.js +27 -0
  152. package/dist/server/file-ref.js.map +1 -0
  153. package/dist/server/host.d.ts +2 -0
  154. package/dist/server/host.js +11 -0
  155. package/dist/server/host.js.map +1 -0
  156. package/dist/server/host.test.d.ts +1 -0
  157. package/dist/server/host.test.js +13 -0
  158. package/dist/server/host.test.js.map +1 -0
  159. package/dist/server/index.d.ts +13 -2
  160. package/dist/server/index.js +11 -1
  161. package/dist/server/index.js.map +1 -1
  162. package/dist/server/inferUtilityTypes.js.map +1 -1
  163. package/dist/server/metric.js.map +1 -1
  164. package/dist/server/middleware.d.ts +16 -3
  165. package/dist/server/middleware.js.map +1 -1
  166. package/dist/server/middleware.test-d.js.map +1 -1
  167. package/dist/server/middleware.test.js.map +1 -1
  168. package/dist/server/requestOrigin.d.ts +7 -0
  169. package/dist/server/requestOrigin.js +25 -0
  170. package/dist/server/requestOrigin.js.map +1 -0
  171. package/dist/server/server.d.ts +280 -10
  172. package/dist/server/server.js +457 -186
  173. package/dist/server/server.js.map +1 -1
  174. package/dist/server/skills-integration.test.d.ts +1 -0
  175. package/dist/server/skills-integration.test.js +63 -0
  176. package/dist/server/skills-integration.test.js.map +1 -0
  177. package/dist/server/skills.d.ts +23 -0
  178. package/dist/server/skills.js +178 -0
  179. package/dist/server/skills.js.map +1 -0
  180. package/dist/server/skills.test.d.ts +1 -0
  181. package/dist/server/skills.test.js +210 -0
  182. package/dist/server/skills.test.js.map +1 -0
  183. package/dist/server/templateHelper.js.map +1 -1
  184. package/dist/server/templates.generated.d.ts +4 -2
  185. package/dist/server/templates.generated.js +2 -1
  186. package/dist/server/templates.generated.js.map +1 -1
  187. package/dist/server/tunnel-proxy-router.js.map +1 -1
  188. package/dist/server/tunnel-proxy-router.test.js.map +1 -1
  189. package/dist/server/view-name.test-d.d.ts +1 -0
  190. package/dist/server/view-name.test-d.js +8 -0
  191. package/dist/server/view-name.test-d.js.map +1 -0
  192. package/dist/server/view-resource-resolution.test.d.ts +6 -0
  193. package/dist/server/view-resource-resolution.test.js +171 -0
  194. package/dist/server/view-resource-resolution.test.js.map +1 -0
  195. package/dist/server/viewsDevServer.js +4 -1
  196. package/dist/server/viewsDevServer.js.map +1 -1
  197. package/dist/test/utils.js.map +1 -1
  198. package/dist/test/view.test.js +109 -96
  199. package/dist/test/view.test.js.map +1 -1
  200. package/dist/version.js.map +1 -1
  201. package/dist/web/bridges/adaptor.d.ts +51 -0
  202. package/dist/web/bridges/adaptor.js +341 -0
  203. package/dist/web/bridges/adaptor.js.map +1 -0
  204. package/dist/web/bridges/adaptor.test.d.ts +1 -0
  205. package/dist/web/bridges/adaptor.test.js +224 -0
  206. package/dist/web/bridges/adaptor.test.js.map +1 -0
  207. package/dist/web/bridges/apps-sdk/bridge.d.ts +6 -1
  208. package/dist/web/bridges/apps-sdk/bridge.js +15 -3
  209. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
  210. package/dist/web/bridges/apps-sdk/index.d.ts +0 -1
  211. package/dist/web/bridges/apps-sdk/index.js +0 -1
  212. package/dist/web/bridges/apps-sdk/index.js.map +1 -1
  213. package/dist/web/bridges/apps-sdk/types.d.ts +8 -1
  214. package/dist/web/bridges/apps-sdk/types.js.map +1 -1
  215. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.d.ts +11 -0
  216. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js +11 -0
  217. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
  218. package/dist/web/bridges/get-adaptor.d.ts +7 -0
  219. package/dist/web/bridges/get-adaptor.js +9 -7
  220. package/dist/web/bridges/get-adaptor.js.map +1 -1
  221. package/dist/web/bridges/get-adaptor.test.d.ts +1 -0
  222. package/dist/web/bridges/get-adaptor.test.js +32 -0
  223. package/dist/web/bridges/get-adaptor.test.js.map +1 -0
  224. package/dist/web/bridges/index.js.map +1 -1
  225. package/dist/web/bridges/mcp-app/bridge.d.ts +15 -2
  226. package/dist/web/bridges/mcp-app/bridge.js +73 -7
  227. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  228. package/dist/web/bridges/mcp-app/bridge.test.d.ts +1 -0
  229. package/dist/web/bridges/mcp-app/bridge.test.js +17 -0
  230. package/dist/web/bridges/mcp-app/bridge.test.js.map +1 -0
  231. package/dist/web/bridges/mcp-app/index.d.ts +0 -1
  232. package/dist/web/bridges/mcp-app/index.js +0 -1
  233. package/dist/web/bridges/mcp-app/index.js.map +1 -1
  234. package/dist/web/bridges/mcp-app/types.d.ts +2 -0
  235. package/dist/web/bridges/mcp-app/types.js.map +1 -1
  236. package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +12 -0
  237. package/dist/web/bridges/mcp-app/use-mcp-app-context.js +12 -0
  238. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
  239. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -1
  240. package/dist/web/bridges/mcp-app/view-tools.test.d.ts +1 -0
  241. package/dist/web/bridges/mcp-app/view-tools.test.js +143 -0
  242. package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -0
  243. package/dist/web/bridges/types.d.ts +108 -3
  244. package/dist/web/bridges/types.js +14 -1
  245. package/dist/web/bridges/types.js.map +1 -1
  246. package/dist/web/bridges/types.test.d.ts +1 -0
  247. package/dist/web/bridges/types.test.js +19 -0
  248. package/dist/web/bridges/types.test.js.map +1 -0
  249. package/dist/web/bridges/use-host-context.d.ts +5 -0
  250. package/dist/web/bridges/use-host-context.js +5 -0
  251. package/dist/web/bridges/use-host-context.js.map +1 -1
  252. package/dist/web/components/modal-provider.d.ts +1 -1
  253. package/dist/web/components/modal-provider.js +4 -3
  254. package/dist/web/components/modal-provider.js.map +1 -1
  255. package/dist/web/create-store.d.ts +26 -0
  256. package/dist/web/create-store.js +26 -0
  257. package/dist/web/create-store.js.map +1 -1
  258. package/dist/web/create-store.test.js +8 -5
  259. package/dist/web/create-store.test.js.map +1 -1
  260. package/dist/web/data-llm.d.ts +34 -1
  261. package/dist/web/data-llm.js +28 -0
  262. package/dist/web/data-llm.js.map +1 -1
  263. package/dist/web/data-llm.test.js +17 -5
  264. package/dist/web/data-llm.test.js.map +1 -1
  265. package/dist/web/generate-helpers.d.ts +2 -0
  266. package/dist/web/generate-helpers.js +2 -0
  267. package/dist/web/generate-helpers.js.map +1 -1
  268. package/dist/web/generate-helpers.test-d.js +4 -2
  269. package/dist/web/generate-helpers.test-d.js.map +1 -1
  270. package/dist/web/generate-helpers.test.js.map +1 -1
  271. package/dist/web/helpers/state.js.map +1 -1
  272. package/dist/web/helpers/state.test.js.map +1 -1
  273. package/dist/web/hooks/index.d.ts +5 -0
  274. package/dist/web/hooks/index.js +5 -0
  275. package/dist/web/hooks/index.js.map +1 -1
  276. package/dist/web/hooks/test/utils.d.ts +7 -2
  277. package/dist/web/hooks/test/utils.js +14 -3
  278. package/dist/web/hooks/test/utils.js.map +1 -1
  279. package/dist/web/hooks/use-call-tool.d.ts +45 -0
  280. package/dist/web/hooks/use-call-tool.js +28 -0
  281. package/dist/web/hooks/use-call-tool.js.map +1 -1
  282. package/dist/web/hooks/use-call-tool.test-d.js.map +1 -1
  283. package/dist/web/hooks/use-call-tool.test.js +62 -27
  284. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  285. package/dist/web/hooks/use-display-mode.d.ts +20 -0
  286. package/dist/web/hooks/use-display-mode.js +20 -0
  287. package/dist/web/hooks/use-display-mode.js.map +1 -1
  288. package/dist/web/hooks/use-display-mode.test-d.js.map +1 -1
  289. package/dist/web/hooks/use-display-mode.test.js +56 -20
  290. package/dist/web/hooks/use-display-mode.test.js.map +1 -1
  291. package/dist/web/hooks/use-download.d.ts +5 -0
  292. package/dist/web/hooks/use-download.js +8 -0
  293. package/dist/web/hooks/use-download.js.map +1 -0
  294. package/dist/web/hooks/use-download.test.d.ts +1 -0
  295. package/dist/web/hooks/use-download.test.js +103 -0
  296. package/dist/web/hooks/use-download.test.js.map +1 -0
  297. package/dist/web/hooks/use-files.d.ts +32 -0
  298. package/dist/web/hooks/use-files.js +32 -0
  299. package/dist/web/hooks/use-files.js.map +1 -1
  300. package/dist/web/hooks/use-files.test.js +10 -2
  301. package/dist/web/hooks/use-files.test.js.map +1 -1
  302. package/dist/web/hooks/use-host-info.d.ts +22 -0
  303. package/dist/web/hooks/use-host-info.js +30 -0
  304. package/dist/web/hooks/use-host-info.js.map +1 -0
  305. package/dist/web/hooks/use-host-info.test.d.ts +1 -0
  306. package/dist/web/hooks/use-host-info.test.js +59 -0
  307. package/dist/web/hooks/use-host-info.test.js.map +1 -0
  308. package/dist/web/hooks/use-layout.d.ts +2 -0
  309. package/dist/web/hooks/use-layout.js +2 -0
  310. package/dist/web/hooks/use-layout.js.map +1 -1
  311. package/dist/web/hooks/use-layout.test.js +59 -26
  312. package/dist/web/hooks/use-layout.test.js.map +1 -1
  313. package/dist/web/hooks/use-open-external.d.ts +17 -0
  314. package/dist/web/hooks/use-open-external.js +16 -0
  315. package/dist/web/hooks/use-open-external.js.map +1 -1
  316. package/dist/web/hooks/use-open-external.test.js +13 -3
  317. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  318. package/dist/web/hooks/use-register-view-tool.d.ts +38 -0
  319. package/dist/web/hooks/use-register-view-tool.js +50 -0
  320. package/dist/web/hooks/use-register-view-tool.js.map +1 -0
  321. package/dist/web/hooks/use-request-close.d.ts +16 -0
  322. package/dist/web/hooks/use-request-close.js +21 -0
  323. package/dist/web/hooks/use-request-close.js.map +1 -0
  324. package/dist/web/hooks/use-request-close.test.d.ts +1 -0
  325. package/dist/web/hooks/use-request-close.test.js +47 -0
  326. package/dist/web/hooks/use-request-close.test.js.map +1 -0
  327. package/dist/web/hooks/use-request-modal.d.ts +16 -1
  328. package/dist/web/hooks/use-request-modal.js +16 -1
  329. package/dist/web/hooks/use-request-modal.js.map +1 -1
  330. package/dist/web/hooks/use-request-modal.test.js +10 -0
  331. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  332. package/dist/web/hooks/use-request-size.d.ts +20 -0
  333. package/dist/web/hooks/use-request-size.js +24 -0
  334. package/dist/web/hooks/use-request-size.js.map +1 -0
  335. package/dist/web/hooks/use-request-size.test.d.ts +1 -0
  336. package/dist/web/hooks/use-request-size.test.js +47 -0
  337. package/dist/web/hooks/use-request-size.test.js.map +1 -0
  338. package/dist/web/hooks/use-send-follow-up-message.d.ts +19 -1
  339. package/dist/web/hooks/use-send-follow-up-message.js +19 -2
  340. package/dist/web/hooks/use-send-follow-up-message.js.map +1 -1
  341. package/dist/web/hooks/use-set-open-in-app-url.d.ts +17 -0
  342. package/dist/web/hooks/use-set-open-in-app-url.js +17 -0
  343. package/dist/web/hooks/use-set-open-in-app-url.js.map +1 -1
  344. package/dist/web/hooks/use-set-open-in-app-url.test.js +28 -8
  345. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  346. package/dist/web/hooks/use-tool-info.d.ts +53 -2
  347. package/dist/web/hooks/use-tool-info.js +30 -7
  348. package/dist/web/hooks/use-tool-info.js.map +1 -1
  349. package/dist/web/hooks/use-tool-info.test-d.js +11 -29
  350. package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
  351. package/dist/web/hooks/use-tool-info.test.js +42 -32
  352. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  353. package/dist/web/hooks/use-user.d.ts +2 -0
  354. package/dist/web/hooks/use-user.js +2 -0
  355. package/dist/web/hooks/use-user.js.map +1 -1
  356. package/dist/web/hooks/use-user.test.js +81 -35
  357. package/dist/web/hooks/use-user.test.js.map +1 -1
  358. package/dist/web/hooks/use-view-state.d.ts +21 -0
  359. package/dist/web/hooks/use-view-state.js +19 -9
  360. package/dist/web/hooks/use-view-state.js.map +1 -1
  361. package/dist/web/hooks/use-view-state.test.js +37 -2
  362. package/dist/web/hooks/use-view-state.test.js.map +1 -1
  363. package/dist/web/index.js.map +1 -1
  364. package/dist/web/mount-view.d.ts +19 -0
  365. package/dist/web/mount-view.js +19 -0
  366. package/dist/web/mount-view.js.map +1 -1
  367. package/dist/web/plugin/data-llm.test.js.map +1 -1
  368. package/dist/web/plugin/plugin.d.ts +34 -0
  369. package/dist/web/plugin/plugin.js +37 -11
  370. package/dist/web/plugin/plugin.js.map +1 -1
  371. package/dist/web/plugin/scan-views.js.map +1 -1
  372. package/dist/web/plugin/scan-views.test.js.map +1 -1
  373. package/dist/web/plugin/transform-data-llm.js.map +1 -1
  374. package/dist/web/plugin/transform-data-llm.test.js.map +1 -1
  375. package/dist/web/plugin/validate-view.js.map +1 -1
  376. package/dist/web/plugin/validate-view.test.js.map +1 -1
  377. package/dist/web/proxy.js.map +1 -1
  378. package/dist/web/types.d.ts +4 -0
  379. package/dist/web/types.js.map +1 -1
  380. package/package.json +30 -19
  381. package/dist/web/bridges/apps-sdk/adaptor.d.ts +0 -24
  382. package/dist/web/bridges/apps-sdk/adaptor.js +0 -93
  383. package/dist/web/bridges/apps-sdk/adaptor.js.map +0 -1
  384. package/dist/web/bridges/mcp-app/adaptor.d.ts +0 -48
  385. package/dist/web/bridges/mcp-app/adaptor.js +0 -268
  386. package/dist/web/bridges/mcp-app/adaptor.js.map +0 -1
@@ -0,0 +1,56 @@
1
+ import { OAuthMetadataSchema } from "@modelcontextprotocol/sdk/shared/auth.js";
2
+ import { z } from "zod";
3
+ /**
4
+ * Discovery doc validated as OAuth AS metadata + `jwks_uri`. The SDK's
5
+ * `OAuthMetadataSchema` omits `jwks_uri` (its client never verifies tokens), but
6
+ * we need it for JWKS token verification, so we extend the schema to keep it.
7
+ */
8
+ const DiscoverySchema = OAuthMetadataSchema.extend({
9
+ jwks_uri: z.string().optional(),
10
+ });
11
+ const WELL_KNOWN = [
12
+ "/.well-known/openid-configuration",
13
+ "/.well-known/oauth-authorization-server",
14
+ ];
15
+ const DISCOVERY_TIMEOUT_MS = 5000;
16
+ /** Fetches and validates an authorization server's discovery document. */
17
+ export async function discoverAuthorizationServer(issuer) {
18
+ const base = issuer.replace(/\/$/, "");
19
+ const errors = [];
20
+ // First valid doc lacking a registration_endpoint, kept as a fallback so a
21
+ // genuinely non-DCR IdP still resolves (and is rejected later by the caller).
22
+ let fallback;
23
+ for (const path of WELL_KNOWN) {
24
+ const url = `${base}${path}`;
25
+ // Any failure (network/timeout, non-2xx, non-JSON, invalid metadata, issuer
26
+ // mismatch) records a per-URL reason and falls through to the next path.
27
+ try {
28
+ const res = await fetch(url, {
29
+ signal: AbortSignal.timeout(DISCOVERY_TIMEOUT_MS),
30
+ });
31
+ if (!res.ok) {
32
+ throw new Error(`HTTP ${res.status}`);
33
+ }
34
+ const meta = DiscoverySchema.parse(await res.json());
35
+ // RFC 8414 §3.3: issuer must match the fetch origin (slash-insensitive).
36
+ if (meta.issuer.replace(/\/$/, "") !== base) {
37
+ throw new Error(`issuer mismatch: ${meta.issuer}`);
38
+ }
39
+ // Prefer the document that advertises DCR: some IdPs (e.g. Clerk) serve a
40
+ // valid openid-configuration without `registration_endpoint` yet expose
41
+ // it at oauth-authorization-server. Keep looking past a doc that omits it.
42
+ if (meta.registration_endpoint) {
43
+ return meta;
44
+ }
45
+ fallback ??= meta;
46
+ }
47
+ catch (err) {
48
+ errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
49
+ }
50
+ }
51
+ if (fallback) {
52
+ return fallback;
53
+ }
54
+ throw new Error(`OAuth discovery failed for ${issuer}: ${errors.join("; ")}`);
55
+ }
56
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../src/server/auth/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAC/E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;GAIG;AACH,MAAM,eAAe,GAAG,mBAAmB,CAAC,MAAM,CAAC;IACjD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAChC,CAAC,CAAC;AAGH,MAAM,UAAU,GAAG;IACjB,mCAAmC;IACnC,yCAAyC;CAC1C,CAAC;AAEF,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAElC,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;IAC5B,2EAA2E;IAC3E,8EAA8E;IAC9E,IAAI,QAAwC,CAAC;IAE7C,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;QAC7B,4EAA4E;QAC5E,yEAAyE;QACzE,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAC3B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,oBAAoB,CAAC;aAClD,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YACxC,CAAC;YACD,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YACrD,yEAAyE;YACzE,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC;gBAC5C,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;YACrD,CAAC;YACD,0EAA0E;YAC1E,wEAAwE;YACxE,2EAA2E;YAC3E,IAAI,IAAI,CAAC,qBAAqB,EAAE,CAAC;gBAC/B,OAAO,IAAI,CAAC;YACd,CAAC;YACD,QAAQ,KAAK,IAAI,CAAC;QACpB,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;QACJ,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,8BAA8B,MAAM,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAChF,CAAC","sourcesContent":["import { OAuthMetadataSchema } from \"@modelcontextprotocol/sdk/shared/auth.js\";\nimport { z } from \"zod\";\n\n/**\n * Discovery doc validated as OAuth AS metadata + `jwks_uri`. The SDK's\n * `OAuthMetadataSchema` omits `jwks_uri` (its client never verifies tokens), but\n * we need it for JWKS token verification, so we extend the schema to keep it.\n */\nconst DiscoverySchema = OAuthMetadataSchema.extend({\n jwks_uri: z.string().optional(),\n});\nexport type DiscoveredMetadata = z.infer<typeof DiscoverySchema>;\n\nconst WELL_KNOWN = [\n \"/.well-known/openid-configuration\",\n \"/.well-known/oauth-authorization-server\",\n];\n\nconst DISCOVERY_TIMEOUT_MS = 5000;\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 // First valid doc lacking a registration_endpoint, kept as a fallback so a\n // genuinely non-DCR IdP still resolves (and is rejected later by the caller).\n let fallback: DiscoveredMetadata | undefined;\n\n for (const path of WELL_KNOWN) {\n const url = `${base}${path}`;\n // Any failure (network/timeout, non-2xx, non-JSON, invalid metadata, issuer\n // mismatch) records a per-URL reason and falls through to the next path.\n try {\n const res = await fetch(url, {\n signal: AbortSignal.timeout(DISCOVERY_TIMEOUT_MS),\n });\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}`);\n }\n const meta = DiscoverySchema.parse(await res.json());\n // RFC 8414 §3.3: issuer must match the fetch origin (slash-insensitive).\n if (meta.issuer.replace(/\\/$/, \"\") !== base) {\n throw new Error(`issuer mismatch: ${meta.issuer}`);\n }\n // Prefer the document that advertises DCR: some IdPs (e.g. Clerk) serve a\n // valid openid-configuration without `registration_endpoint` yet expose\n // it at oauth-authorization-server. Keep looking past a doc that omits it.\n if (meta.registration_endpoint) {\n return meta;\n }\n fallback ??= meta;\n } catch (err) {\n errors.push(\n `${url}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n if (fallback) {\n return fallback;\n }\n throw new Error(`OAuth discovery failed for ${issuer}: ${errors.join(\"; \")}`);\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,93 @@
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
+ function doc(origin, extra = {}) {
8
+ return {
9
+ issuer: origin,
10
+ authorization_endpoint: `${origin}/authorize`,
11
+ token_endpoint: `${origin}/token`,
12
+ registration_endpoint: `${origin}/register`,
13
+ response_types_supported: ["code"],
14
+ scopes_supported: ["openid", "email"],
15
+ jwks_uri: `${origin}/jwks`,
16
+ ...extra,
17
+ };
18
+ }
19
+ // Serves JSON bodies built from the live origin at their well-known paths;
20
+ // returns the origin URL.
21
+ async function serve(build) {
22
+ let origin = "";
23
+ const srv = http.createServer((req, res) => {
24
+ const body = build(origin)[req.url ?? ""];
25
+ if (body === undefined) {
26
+ res.writeHead(404).end();
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
+ origin = `http://localhost:${srv.address().port}`;
35
+ return origin;
36
+ }
37
+ describe("discoverAuthorizationServer", () => {
38
+ it("fetches and validates openid-configuration", async () => {
39
+ const base = await serve((o) => ({
40
+ "/.well-known/openid-configuration": doc(o),
41
+ }));
42
+ const meta = await discoverAuthorizationServer(base);
43
+ expect(meta.registration_endpoint).toBe(`${base}/register`);
44
+ expect(meta.jwks_uri).toBe(`${base}/jwks`);
45
+ });
46
+ it("accepts a canonically slash-terminated issuer", async () => {
47
+ const base = await serve((o) => ({
48
+ "/.well-known/openid-configuration": doc(o, { issuer: `${o}/` }),
49
+ }));
50
+ const meta = await discoverAuthorizationServer(base);
51
+ expect(meta.registration_endpoint).toBe(`${base}/register`);
52
+ });
53
+ it("falls back to oauth-authorization-server when oidc is 404", async () => {
54
+ const base = await serve((o) => ({
55
+ "/.well-known/oauth-authorization-server": doc(o),
56
+ }));
57
+ const meta = await discoverAuthorizationServer(base);
58
+ expect(meta.registration_endpoint).toBe(`${base}/register`);
59
+ });
60
+ it("throws when no well-known doc is reachable", async () => {
61
+ const base = await serve(() => ({}));
62
+ await expect(discoverAuthorizationServer(base)).rejects.toThrow(/discovery failed/i);
63
+ });
64
+ it("falls through to oauth-authorization-server when oidc doc is schema-invalid", async () => {
65
+ const base = await serve((o) => ({
66
+ "/.well-known/openid-configuration": doc(o, {
67
+ response_types_supported: undefined,
68
+ }),
69
+ "/.well-known/oauth-authorization-server": doc(o),
70
+ }));
71
+ const meta = await discoverAuthorizationServer(base);
72
+ expect(meta.registration_endpoint).toBe(`${base}/register`);
73
+ });
74
+ it("prefers the doc advertising DCR when oidc omits registration_endpoint", async () => {
75
+ // Clerk-shaped: valid openid-configuration without a registration_endpoint,
76
+ // DCR exposed only at oauth-authorization-server.
77
+ const base = await serve((o) => ({
78
+ "/.well-known/openid-configuration": doc(o, {
79
+ registration_endpoint: undefined,
80
+ }),
81
+ "/.well-known/oauth-authorization-server": doc(o),
82
+ }));
83
+ const meta = await discoverAuthorizationServer(base);
84
+ expect(meta.registration_endpoint).toBe(`${base}/register`);
85
+ });
86
+ it("rejects a doc whose issuer does not match the fetch origin", async () => {
87
+ const base = await serve(() => ({
88
+ "/.well-known/openid-configuration": doc("https://evil.test"),
89
+ }));
90
+ await expect(discoverAuthorizationServer(base)).rejects.toThrow(/discovery failed/i);
91
+ });
92
+ });
93
+ //# 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\nfunction doc(origin: string, extra: Record<string, unknown> = {}) {\n return {\n issuer: origin,\n authorization_endpoint: `${origin}/authorize`,\n token_endpoint: `${origin}/token`,\n registration_endpoint: `${origin}/register`,\n response_types_supported: [\"code\"],\n scopes_supported: [\"openid\", \"email\"],\n jwks_uri: `${origin}/jwks`,\n ...extra,\n };\n}\n\n// Serves JSON bodies built from the live origin at their well-known paths;\n// returns the origin URL.\nasync function serve(build: (origin: string) => Record<string, unknown>) {\n let origin = \"\";\n const srv = http.createServer((req, res) => {\n const body = build(origin)[req.url ?? \"\"];\n if (body === undefined) {\n res.writeHead(404).end();\n return;\n }\n res.setHeader(\"content-type\", \"application/json\");\n res.end(JSON.stringify(body));\n });\n await new Promise<void>((resolve) => srv.listen(0, resolve));\n server = srv;\n origin = `http://localhost:${(srv.address() as { port: number }).port}`;\n return origin;\n}\n\ndescribe(\"discoverAuthorizationServer\", () => {\n it(\"fetches and validates openid-configuration\", async () => {\n const base = await serve((o) => ({\n \"/.well-known/openid-configuration\": doc(o),\n }));\n const meta = await discoverAuthorizationServer(base);\n expect(meta.registration_endpoint).toBe(`${base}/register`);\n expect(meta.jwks_uri).toBe(`${base}/jwks`);\n });\n\n it(\"accepts a canonically slash-terminated issuer\", async () => {\n const base = await serve((o) => ({\n \"/.well-known/openid-configuration\": doc(o, { issuer: `${o}/` }),\n }));\n const meta = await discoverAuthorizationServer(base);\n expect(meta.registration_endpoint).toBe(`${base}/register`);\n });\n\n it(\"falls back to oauth-authorization-server when oidc is 404\", async () => {\n const base = await serve((o) => ({\n \"/.well-known/oauth-authorization-server\": doc(o),\n }));\n const meta = await discoverAuthorizationServer(base);\n expect(meta.registration_endpoint).toBe(`${base}/register`);\n });\n\n it(\"throws when no well-known doc is reachable\", async () => {\n const base = await serve(() => ({}));\n await expect(discoverAuthorizationServer(base)).rejects.toThrow(\n /discovery failed/i,\n );\n });\n\n it(\"falls through to oauth-authorization-server when oidc doc is schema-invalid\", async () => {\n const base = await serve((o) => ({\n \"/.well-known/openid-configuration\": doc(o, {\n response_types_supported: undefined,\n }),\n \"/.well-known/oauth-authorization-server\": doc(o),\n }));\n const meta = await discoverAuthorizationServer(base);\n expect(meta.registration_endpoint).toBe(`${base}/register`);\n });\n\n it(\"prefers the doc advertising DCR when oidc omits registration_endpoint\", async () => {\n // Clerk-shaped: valid openid-configuration without a registration_endpoint,\n // DCR exposed only at oauth-authorization-server.\n const base = await serve((o) => ({\n \"/.well-known/openid-configuration\": doc(o, {\n registration_endpoint: undefined,\n }),\n \"/.well-known/oauth-authorization-server\": doc(o),\n }));\n const meta = await discoverAuthorizationServer(base);\n expect(meta.registration_endpoint).toBe(`${base}/register`);\n });\n\n it(\"rejects a doc whose issuer does not match the fetch origin\", async () => {\n const base = await serve(() => ({\n \"/.well-known/openid-configuration\": doc(\"https://evil.test\"),\n }));\n await expect(discoverAuthorizationServer(base)).rejects.toThrow(\n /discovery failed/i,\n );\n });\n});\n"]}
@@ -0,0 +1,18 @@
1
+ import type { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
2
+ import type { JwksVerifyConfig } from "./verify.js";
3
+ /** Resource-server OAuth config for `SkybridgeServerOptions.oauth`. */
4
+ export type OAuthConfig = {
5
+ /**
6
+ * Public URL of this server; sets `resourceServerUrl` and the
7
+ * `resource_metadata` URL. When omitted, it is inferred per request from
8
+ * `x-forwarded-host`/`origin`/`host` headers.
9
+ */
10
+ baseUrl?: string;
11
+ /** AS metadata served at `/.well-known/oauth-authorization-server`. */
12
+ oauthMetadata: OAuthMetadata;
13
+ verify: JwksVerifyConfig;
14
+ /** Scopes advertised in protected-resource metadata. */
15
+ scopesSupported?: string[];
16
+ /** Server-wide required-scope floor. */
17
+ requiredScopes?: string[];
18
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/server/auth/index.ts"],"names":[],"mappings":"","sourcesContent":["import type { OAuthMetadata } from \"@modelcontextprotocol/sdk/shared/auth.js\";\nimport type { JwksVerifyConfig } from \"./verify.js\";\n\n/** Resource-server OAuth config for `SkybridgeServerOptions.oauth`. */\nexport type OAuthConfig = {\n /**\n * Public URL of this server; sets `resourceServerUrl` and the\n * `resource_metadata` URL. When omitted, it is inferred per request from\n * `x-forwarded-host`/`origin`/`host` headers.\n */\n baseUrl?: string;\n /** AS metadata served at `/.well-known/oauth-authorization-server`. */\n oauthMetadata: OAuthMetadata;\n verify: JwksVerifyConfig;\n /** Scopes advertised in protected-resource metadata. */\n scopesSupported?: string[];\n /** Server-wide required-scope floor. */\n requiredScopes?: string[];\n};\n"]}
@@ -0,0 +1,18 @@
1
+ import type { OAuthConfig } from "../index.js";
2
+ import { type CustomProviderOptions } from "./custom.js";
3
+ /**
4
+ * OAuth provider for Auth0. `domain` is the tenant domain (e.g.
5
+ * `acme.us.auth0.com`); `audience` is the API Identifier; `serverUrl` is this
6
+ * server's public URL. Requires DCR enabled on the tenant.
7
+ *
8
+ * Auth0 can't use the client's resource indicator — `audience` must be *in* the
9
+ * authorize request. So it runs as skybridge-as-AS (`serverUrl`, see
10
+ * {@link customProvider}) and bakes `?audience=<id>` into the advertised
11
+ * `authorization_endpoint`. The token's `aud` is the API; `verify.issuer` stays
12
+ * Auth0 (the token's real `iss`).
13
+ */
14
+ export declare function auth0Provider(opts: {
15
+ domain: string;
16
+ audience: string;
17
+ serverUrl: string;
18
+ } & Omit<CustomProviderOptions, "issuer" | "audience" | "baseUrl" | "serverUrl">): Promise<OAuthConfig>;
@@ -0,0 +1,31 @@
1
+ import { customProvider } from "./custom.js";
2
+ import { toIssuerUrl } from "./shared.js";
3
+ /**
4
+ * OAuth provider for Auth0. `domain` is the tenant domain (e.g.
5
+ * `acme.us.auth0.com`); `audience` is the API Identifier; `serverUrl` is this
6
+ * server's public URL. Requires DCR enabled on the tenant.
7
+ *
8
+ * Auth0 can't use the client's resource indicator — `audience` must be *in* the
9
+ * authorize request. So it runs as skybridge-as-AS (`serverUrl`, see
10
+ * {@link customProvider}) and bakes `?audience=<id>` into the advertised
11
+ * `authorization_endpoint`. The token's `aud` is the API; `verify.issuer` stays
12
+ * Auth0 (the token's real `iss`).
13
+ */
14
+ export async function auth0Provider(opts) {
15
+ const { domain, audience, ...rest } = opts;
16
+ const config = await customProvider({
17
+ issuer: toIssuerUrl(domain),
18
+ audience,
19
+ ...rest,
20
+ });
21
+ const authUrl = new URL(config.oauthMetadata.authorization_endpoint);
22
+ authUrl.searchParams.set("audience", audience);
23
+ return {
24
+ ...config,
25
+ oauthMetadata: {
26
+ ...config.oauthMetadata,
27
+ authorization_endpoint: authUrl.href,
28
+ },
29
+ };
30
+ }
31
+ //# sourceMappingURL=auth0.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth0.js","sourceRoot":"","sources":["../../../../src/server/auth/providers/auth0.ts"],"names":[],"mappings":"AACA,OAAO,EAA8B,cAAc,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAGC;IAED,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;IAC3C,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC;QAClC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC;QAC3B,QAAQ;QACR,GAAG,IAAI;KACR,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;IACrE,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC/C,OAAO;QACL,GAAG,MAAM;QACT,aAAa,EAAE;YACb,GAAG,MAAM,CAAC,aAAa;YACvB,sBAAsB,EAAE,OAAO,CAAC,IAAI;SACrC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { OAuthConfig } from \"../index.js\";\nimport { type CustomProviderOptions, customProvider } from \"./custom.js\";\nimport { toIssuerUrl } from \"./shared.js\";\n\n/**\n * OAuth provider for Auth0. `domain` is the tenant domain (e.g.\n * `acme.us.auth0.com`); `audience` is the API Identifier; `serverUrl` is this\n * server's public URL. Requires DCR enabled on the tenant.\n *\n * Auth0 can't use the client's resource indicator — `audience` must be *in* the\n * authorize request. So it runs as skybridge-as-AS (`serverUrl`, see\n * {@link customProvider}) and bakes `?audience=<id>` into the advertised\n * `authorization_endpoint`. The token's `aud` is the API; `verify.issuer` stays\n * Auth0 (the token's real `iss`).\n */\nexport async function auth0Provider(\n opts: { domain: string; audience: string; serverUrl: string } & Omit<\n CustomProviderOptions,\n \"issuer\" | \"audience\" | \"baseUrl\" | \"serverUrl\"\n >,\n): Promise<OAuthConfig> {\n const { domain, audience, ...rest } = opts;\n const config = await customProvider({\n issuer: toIssuerUrl(domain),\n audience,\n ...rest,\n });\n const authUrl = new URL(config.oauthMetadata.authorization_endpoint);\n authUrl.searchParams.set(\"audience\", audience);\n return {\n ...config,\n oauthMetadata: {\n ...config.oauthMetadata,\n authorization_endpoint: authUrl.href,\n },\n };\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,48 @@
1
+ // @vitest-environment node
2
+ import { afterEach, describe, expect, it, vi } from "vitest";
3
+ import { auth0Provider } from "./auth0.js";
4
+ afterEach(() => vi.restoreAllMocks());
5
+ function doc(issuer) {
6
+ return {
7
+ issuer,
8
+ authorization_endpoint: `${issuer}/authorize`,
9
+ token_endpoint: `${issuer}/oauth/token`,
10
+ registration_endpoint: `${issuer}/oidc/register`,
11
+ response_types_supported: ["code"],
12
+ scopes_supported: ["openid", "profile", "email"],
13
+ jwks_uri: `${issuer}/.well-known/jwks.json`,
14
+ };
15
+ }
16
+ describe("auth0Provider", () => {
17
+ it("advertises skybridge as the AS (static issuer) and bakes audience; verifies against Auth0", async () => {
18
+ const issuer = "https://acme.us.auth0.com";
19
+ vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify(doc(issuer)), {
20
+ headers: { "content-type": "application/json" },
21
+ }));
22
+ const config = await auth0Provider({
23
+ domain: "acme.us.auth0.com",
24
+ audience: "https://api.example.com/",
25
+ serverUrl: "https://app.example.com/",
26
+ scopes: ["openid", "profile", "email"],
27
+ });
28
+ // skybridge is the advertised AS: static issuer = serverUrl (trailing slash stripped).
29
+ expect(config.oauthMetadata.issuer).toBe("https://app.example.com");
30
+ // AS metadata scopes match the advertised set (clients read scopes from here).
31
+ expect(config.oauthMetadata.scopes_supported).toEqual([
32
+ "openid",
33
+ "profile",
34
+ "email",
35
+ ]);
36
+ expect(config.scopesSupported).toEqual(["openid", "profile", "email"]);
37
+ // audience baked into the advertised authorize endpoint (still pointing at Auth0).
38
+ const url = new URL(config.oauthMetadata.authorization_endpoint);
39
+ expect(url.origin + url.pathname).toBe(`${issuer}/authorize`);
40
+ expect(url.searchParams.get("audience")).toBe("https://api.example.com/");
41
+ // baseUrl left unset so the PRM resource resolves per request (Alpic-safe).
42
+ expect(config.baseUrl).toBeUndefined();
43
+ // token verification still uses Auth0 as issuer + the API audience.
44
+ expect(config.verify.issuer).toBe(issuer);
45
+ expect(config.verify.audience).toBe("https://api.example.com/");
46
+ });
47
+ });
48
+ //# sourceMappingURL=auth0.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth0.test.js","sourceRoot":"","sources":["../../../../src/server/auth/providers/auth0.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;IACzB,OAAO;QACL,MAAM;QACN,sBAAsB,EAAE,GAAG,MAAM,YAAY;QAC7C,cAAc,EAAE,GAAG,MAAM,cAAc;QACvC,qBAAqB,EAAE,GAAG,MAAM,gBAAgB;QAChD,wBAAwB,EAAE,CAAC,MAAM,CAAC;QAClC,gBAAgB,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC;QAChD,QAAQ,EAAE,GAAG,MAAM,wBAAwB;KAC5C,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,2FAA2F,EAAE,KAAK,IAAI,EAAE;QACzG,MAAM,MAAM,GAAG,2BAA2B,CAAC;QAC3C,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,iBAAiB,CAC7C,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,mBAAmB;YAC3B,QAAQ,EAAE,0BAA0B;YACpC,SAAS,EAAE,0BAA0B;YACrC,MAAM,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC;SACvC,CAAC,CAAC;QAEH,uFAAuF;QACvF,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACpE,+EAA+E;QAC/E,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,OAAO,CAAC;YACpD,QAAQ;YACR,SAAS;YACT,OAAO;SACR,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;QACvE,mFAAmF;QACnF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;QACjE,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,MAAM,YAAY,CAAC,CAAC;QAC9D,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QAC1E,4EAA4E;QAC5E,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,aAAa,EAAE,CAAC;QACvC,oEAAoE;QACpE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["// @vitest-environment node\nimport { afterEach, describe, expect, it, vi } from \"vitest\";\nimport { auth0Provider } from \"./auth0.js\";\n\nafterEach(() => vi.restoreAllMocks());\n\nfunction doc(issuer: string) {\n return {\n issuer,\n authorization_endpoint: `${issuer}/authorize`,\n token_endpoint: `${issuer}/oauth/token`,\n registration_endpoint: `${issuer}/oidc/register`,\n response_types_supported: [\"code\"],\n scopes_supported: [\"openid\", \"profile\", \"email\"],\n jwks_uri: `${issuer}/.well-known/jwks.json`,\n };\n}\n\ndescribe(\"auth0Provider\", () => {\n it(\"advertises skybridge as the AS (static issuer) and bakes audience; verifies against Auth0\", async () => {\n const issuer = \"https://acme.us.auth0.com\";\n 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 auth0Provider({\n domain: \"acme.us.auth0.com\",\n audience: \"https://api.example.com/\",\n serverUrl: \"https://app.example.com/\",\n scopes: [\"openid\", \"profile\", \"email\"],\n });\n\n // skybridge is the advertised AS: static issuer = serverUrl (trailing slash stripped).\n expect(config.oauthMetadata.issuer).toBe(\"https://app.example.com\");\n // AS metadata scopes match the advertised set (clients read scopes from here).\n expect(config.oauthMetadata.scopes_supported).toEqual([\n \"openid\",\n \"profile\",\n \"email\",\n ]);\n expect(config.scopesSupported).toEqual([\"openid\", \"profile\", \"email\"]);\n // audience baked into the advertised authorize endpoint (still pointing at Auth0).\n const url = new URL(config.oauthMetadata.authorization_endpoint);\n expect(url.origin + url.pathname).toBe(`${issuer}/authorize`);\n expect(url.searchParams.get(\"audience\")).toBe(\"https://api.example.com/\");\n // baseUrl left unset so the PRM resource resolves per request (Alpic-safe).\n expect(config.baseUrl).toBeUndefined();\n // token verification still uses Auth0 as issuer + the API audience.\n expect(config.verify.issuer).toBe(issuer);\n expect(config.verify.audience).toBe(\"https://api.example.com/\");\n });\n});\n"]}
@@ -0,0 +1,47 @@
1
+ import type { OAuthConfig } from "../index.js";
2
+ import { type CustomProviderOptions } from "./custom.js";
3
+ /** Options accepted by {@link authplaneProvider}. */
4
+ export type AuthplaneProviderOptions = {
5
+ /**
6
+ * The authorization server's issuer identifier (RFC 8414 §2) — your
7
+ * Authplane deployment, e.g. `https://auth.acme.com` (or
8
+ * `http://localhost:9000` in local development).
9
+ */
10
+ issuer: string;
11
+ /**
12
+ * This server's resource identifier (RFC 9728 §1.2): the public URL clients
13
+ * reach, advertised as the `resource` field of its protected-resource
14
+ * metadata. Required, unlike the other providers.
15
+ *
16
+ * Authplane binds the access token's `aud` to the RFC 8707 `resource`
17
+ * parameter the client sends, and the client takes that value from the
18
+ * advertised metadata. Setting it explicitly gives the deployment one fixed
19
+ * identifier, which is what the audience is checked against.
20
+ *
21
+ * Resource identifiers are compared by exact string match, so give it in the
22
+ * form it will be advertised and register that same string in Authplane. RFC
23
+ * 8707 §2 asks for the most specific URI available, e.g.
24
+ * `https://acme.example.com/mcp`.
25
+ */
26
+ resource: string;
27
+ /**
28
+ * Expected token `aud`. Defaults to `resource`.
29
+ *
30
+ * RFC 8707 §2 lets an authorization server use the resource identifier
31
+ * verbatim as the audience or map it to another value; set this only for a
32
+ * resource configured in Authplane with such an override, and pass that
33
+ * value verbatim.
34
+ */
35
+ audience?: string;
36
+ } & Omit<CustomProviderOptions, "issuer" | "audience" | "baseUrl">;
37
+ /**
38
+ * OAuth provider for Authplane. `issuer` is the authorization server's issuer
39
+ * identifier and `resource` is this server's resource identifier, which also
40
+ * supplies the expected token audience.
41
+ *
42
+ * Dynamic Client Registration is supported natively, so no registration proxy
43
+ * is needed: clients register with Authplane directly and this server stays out
44
+ * of the authorization path. Pass `serverUrl` to advertise this server as the
45
+ * authorization server instead (see {@link customProvider}).
46
+ */
47
+ export declare function authplaneProvider(opts: AuthplaneProviderOptions): Promise<OAuthConfig>;
@@ -0,0 +1,57 @@
1
+ import { customProvider } from "./custom.js";
2
+ /**
3
+ * Rejects anything that cannot serve as an OAuth identifier: RFC 8707 §2
4
+ * requires an absolute URI and forbids a fragment, and the discovery and
5
+ * protected-resource metadata URLs are both built from the scheme and host.
6
+ */
7
+ function parseIdentifier(value, option) {
8
+ let parsed;
9
+ try {
10
+ parsed = new URL(value);
11
+ }
12
+ catch {
13
+ throw new Error(`authplaneProvider: \`${option}\` must be an absolute URL, got ${JSON.stringify(value)}`);
14
+ }
15
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
16
+ throw new Error(`authplaneProvider: \`${option}\` must use the http or https scheme, got ${JSON.stringify(value)}`);
17
+ }
18
+ if (!parsed.host) {
19
+ throw new Error(`authplaneProvider: \`${option}\` must include a host, got ${JSON.stringify(value)}`);
20
+ }
21
+ if (parsed.hash) {
22
+ throw new Error(`authplaneProvider: \`${option}\` must not include a fragment, got ${JSON.stringify(value)}`);
23
+ }
24
+ return parsed;
25
+ }
26
+ /**
27
+ * OAuth provider for Authplane. `issuer` is the authorization server's issuer
28
+ * identifier and `resource` is this server's resource identifier, which also
29
+ * supplies the expected token audience.
30
+ *
31
+ * Dynamic Client Registration is supported natively, so no registration proxy
32
+ * is needed: clients register with Authplane directly and this server stays out
33
+ * of the authorization path. Pass `serverUrl` to advertise this server as the
34
+ * authorization server instead (see {@link customProvider}).
35
+ */
36
+ export function authplaneProvider(opts) {
37
+ const { issuer, resource, audience, ...rest } = opts;
38
+ parseIdentifier(issuer, "issuer");
39
+ const parsedResource = parseIdentifier(resource, "resource");
40
+ // The advertised resource is the URL-normalised form of this value. Where
41
+ // normalisation would change the string — a bare origin gaining a root path,
42
+ // an uppercase host, an explicit default port — the configured and advertised
43
+ // identifiers would differ, and the audience check compares them exactly.
44
+ // Require the advertised form up front so the two always match.
45
+ if (parsedResource.href !== resource) {
46
+ throw new Error(`authplaneProvider: \`resource\` must be given in the form it will be advertised. ` +
47
+ `${JSON.stringify(resource)} is advertised as ${JSON.stringify(parsedResource.href)}. ` +
48
+ `Use ${JSON.stringify(parsedResource.href)}, or a path-qualified URL such as "https://acme.example.com/mcp", and register the same value in Authplane.`);
49
+ }
50
+ return customProvider({
51
+ issuer,
52
+ audience: audience ?? resource,
53
+ baseUrl: resource,
54
+ ...rest,
55
+ });
56
+ }
57
+ //# sourceMappingURL=authplane.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authplane.js","sourceRoot":"","sources":["../../../../src/server/auth/providers/authplane.ts"],"names":[],"mappings":"AACA,OAAO,EAA8B,cAAc,EAAE,MAAM,aAAa,CAAC;AAqCzE;;;;GAIG;AACH,SAAS,eAAe,CAAC,KAAa,EAAE,MAAc;IACpD,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,wBAAwB,MAAM,mCAAmC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CACzF,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CACb,wBAAwB,MAAM,6CAA6C,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CACnG,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,wBAAwB,MAAM,+BAA+B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CACrF,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CACb,wBAAwB,MAAM,uCAAuC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAC7F,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAA8B;IAE9B,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;IAErD,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAClC,MAAM,cAAc,GAAG,eAAe,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAE7D,0EAA0E;IAC1E,6EAA6E;IAC7E,8EAA8E;IAC9E,0EAA0E;IAC1E,gEAAgE;IAChE,IAAI,cAAc,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CACb,mFAAmF;YACjF,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,qBAAqB,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI;YACvF,OAAO,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,6GAA6G,CAC1J,CAAC;IACJ,CAAC;IAED,OAAO,cAAc,CAAC;QACpB,MAAM;QACN,QAAQ,EAAE,QAAQ,IAAI,QAAQ;QAC9B,OAAO,EAAE,QAAQ;QACjB,GAAG,IAAI;KACR,CAAC,CAAC;AACL,CAAC","sourcesContent":["import type { OAuthConfig } from \"../index.js\";\nimport { type CustomProviderOptions, customProvider } from \"./custom.js\";\n\n/** Options accepted by {@link authplaneProvider}. */\nexport type AuthplaneProviderOptions = {\n /**\n * The authorization server's issuer identifier (RFC 8414 §2) — your\n * Authplane deployment, e.g. `https://auth.acme.com` (or\n * `http://localhost:9000` in local development).\n */\n issuer: string;\n /**\n * This server's resource identifier (RFC 9728 §1.2): the public URL clients\n * reach, advertised as the `resource` field of its protected-resource\n * metadata. Required, unlike the other providers.\n *\n * Authplane binds the access token's `aud` to the RFC 8707 `resource`\n * parameter the client sends, and the client takes that value from the\n * advertised metadata. Setting it explicitly gives the deployment one fixed\n * identifier, which is what the audience is checked against.\n *\n * Resource identifiers are compared by exact string match, so give it in the\n * form it will be advertised and register that same string in Authplane. RFC\n * 8707 §2 asks for the most specific URI available, e.g.\n * `https://acme.example.com/mcp`.\n */\n resource: string;\n /**\n * Expected token `aud`. Defaults to `resource`.\n *\n * RFC 8707 §2 lets an authorization server use the resource identifier\n * verbatim as the audience or map it to another value; set this only for a\n * resource configured in Authplane with such an override, and pass that\n * value verbatim.\n */\n audience?: string;\n} & Omit<CustomProviderOptions, \"issuer\" | \"audience\" | \"baseUrl\">;\n\n/**\n * Rejects anything that cannot serve as an OAuth identifier: RFC 8707 §2\n * requires an absolute URI and forbids a fragment, and the discovery and\n * protected-resource metadata URLs are both built from the scheme and host.\n */\nfunction parseIdentifier(value: string, option: string): URL {\n let parsed: URL;\n try {\n parsed = new URL(value);\n } catch {\n throw new Error(\n `authplaneProvider: \\`${option}\\` must be an absolute URL, got ${JSON.stringify(value)}`,\n );\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") {\n throw new Error(\n `authplaneProvider: \\`${option}\\` must use the http or https scheme, got ${JSON.stringify(value)}`,\n );\n }\n if (!parsed.host) {\n throw new Error(\n `authplaneProvider: \\`${option}\\` must include a host, got ${JSON.stringify(value)}`,\n );\n }\n if (parsed.hash) {\n throw new Error(\n `authplaneProvider: \\`${option}\\` must not include a fragment, got ${JSON.stringify(value)}`,\n );\n }\n return parsed;\n}\n\n/**\n * OAuth provider for Authplane. `issuer` is the authorization server's issuer\n * identifier and `resource` is this server's resource identifier, which also\n * supplies the expected token audience.\n *\n * Dynamic Client Registration is supported natively, so no registration proxy\n * is needed: clients register with Authplane directly and this server stays out\n * of the authorization path. Pass `serverUrl` to advertise this server as the\n * authorization server instead (see {@link customProvider}).\n */\nexport function authplaneProvider(\n opts: AuthplaneProviderOptions,\n): Promise<OAuthConfig> {\n const { issuer, resource, audience, ...rest } = opts;\n\n parseIdentifier(issuer, \"issuer\");\n const parsedResource = parseIdentifier(resource, \"resource\");\n\n // The advertised resource is the URL-normalised form of this value. Where\n // normalisation would change the string — a bare origin gaining a root path,\n // an uppercase host, an explicit default port — the configured and advertised\n // identifiers would differ, and the audience check compares them exactly.\n // Require the advertised form up front so the two always match.\n if (parsedResource.href !== resource) {\n throw new Error(\n `authplaneProvider: \\`resource\\` must be given in the form it will be advertised. ` +\n `${JSON.stringify(resource)} is advertised as ${JSON.stringify(parsedResource.href)}. ` +\n `Use ${JSON.stringify(parsedResource.href)}, or a path-qualified URL such as \"https://acme.example.com/mcp\", and register the same value in Authplane.`,\n );\n }\n\n return customProvider({\n issuer,\n audience: audience ?? resource,\n baseUrl: resource,\n ...rest,\n });\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,138 @@
1
+ // @vitest-environment node
2
+ import { afterEach, describe, expect, it, vi } from "vitest";
3
+ import { authplaneProvider } from "./authplane.js";
4
+ afterEach(() => vi.restoreAllMocks());
5
+ const ISSUER = "https://auth.acme.com";
6
+ function discoveryDoc(issuer, scopes) {
7
+ return {
8
+ issuer,
9
+ authorization_endpoint: `${issuer}/authorize`,
10
+ token_endpoint: `${issuer}/token`,
11
+ registration_endpoint: `${issuer}/register`,
12
+ response_types_supported: ["code"],
13
+ scopes_supported: scopes,
14
+ jwks_uri: `${issuer}/.well-known/jwks.json`,
15
+ };
16
+ }
17
+ function mockDiscovery(docs) {
18
+ return vi
19
+ .spyOn(globalThis, "fetch")
20
+ .mockImplementation(async (input) => {
21
+ const url = String(input);
22
+ const entry = Object.entries(docs).find(([issuer]) => url === `${issuer}/.well-known/openid-configuration`);
23
+ if (entry === undefined) {
24
+ return new Response(null, { status: 404 });
25
+ }
26
+ return new Response(JSON.stringify(discoveryDoc(...entry)), {
27
+ headers: { "content-type": "application/json" },
28
+ });
29
+ });
30
+ }
31
+ describe("authplaneProvider", () => {
32
+ it("derives the audience from the resource the server advertises", async () => {
33
+ mockDiscovery({ [ISSUER]: ["checkout"] });
34
+ const config = await authplaneProvider({
35
+ issuer: ISSUER,
36
+ resource: "https://coffee.example.com/mcp",
37
+ });
38
+ expect(config.verify.audience).toBe("https://coffee.example.com/mcp");
39
+ expect(config.verify.issuer).toBe(ISSUER);
40
+ expect(config.verify.jwksUri).toBe(`${ISSUER}/.well-known/jwks.json`);
41
+ expect(config.baseUrl).toBe("https://coffee.example.com/mcp");
42
+ });
43
+ it.each([
44
+ "https://coffee.example.com/mcp",
45
+ "https://coffee.example.com/v2/mcp",
46
+ "https://coffee.example.com/",
47
+ "http://localhost:3000/mcp",
48
+ "https://coffee.example.com:8443/mcp",
49
+ ])("passes %s through as both resource and audience", async (resource) => {
50
+ mockDiscovery({ [ISSUER]: ["checkout"] });
51
+ const config = await authplaneProvider({ issuer: ISSUER, resource });
52
+ expect(config.baseUrl).toBe(resource);
53
+ expect(config.verify.audience).toBe(resource);
54
+ });
55
+ // The metadata router serialises the resource identifier through `URL` before
56
+ // advertising it. Anything that serialisation would rewrite is refused here,
57
+ // so the advertised identifier is always the configured one.
58
+ it.each([
59
+ // Pathless origin — gains a root path.
60
+ ["https://coffee.example.com", "https://coffee.example.com/"],
61
+ ["http://localhost:3000", "http://localhost:3000/"],
62
+ // Uppercase host — lowercased.
63
+ ["https://COFFEE.EXAMPLE.COM/mcp", "https://coffee.example.com/mcp"],
64
+ // Explicit default port — dropped.
65
+ ["https://coffee.example.com:443/mcp", "https://coffee.example.com/mcp"],
66
+ ])("rejects %s and names %s instead", async (resource, published) => {
67
+ const fetchSpy = mockDiscovery({ [ISSUER]: ["checkout"] });
68
+ expect(() => authplaneProvider({ issuer: ISSUER, resource })).toThrow(`is advertised as ${JSON.stringify(published)}`);
69
+ expect(fetchSpy).not.toHaveBeenCalled();
70
+ });
71
+ it("lets an explicit audience override the default", async () => {
72
+ mockDiscovery({ [ISSUER]: ["checkout"] });
73
+ const config = await authplaneProvider({
74
+ issuer: ISSUER,
75
+ resource: "https://coffee.example.com/mcp",
76
+ audience: "urn:acme:coffee",
77
+ });
78
+ expect(config.verify.audience).toBe("urn:acme:coffee");
79
+ });
80
+ it.each([
81
+ ["coffee.example.com/mcp", /must be an absolute URL/],
82
+ ["ftp://coffee.example.com/mcp", /must use the http or https scheme/],
83
+ ["https://coffee.example.com/mcp#frag", /must not include a fragment/],
84
+ ])("rejects the malformed resource %s", async (resource, message) => {
85
+ const fetchSpy = mockDiscovery({ [ISSUER]: ["checkout"] });
86
+ expect(() => authplaneProvider({ issuer: ISSUER, resource })).toThrow(message);
87
+ expect(fetchSpy).not.toHaveBeenCalled();
88
+ });
89
+ it("rejects a malformed issuer before any discovery", async () => {
90
+ const fetchSpy = mockDiscovery({ [ISSUER]: ["checkout"] });
91
+ expect(() => authplaneProvider({
92
+ issuer: "auth.acme.com",
93
+ resource: "https://coffee.example.com/mcp",
94
+ })).toThrow(/`issuer` must be an absolute URL/);
95
+ expect(fetchSpy).not.toHaveBeenCalled();
96
+ });
97
+ it("accepts an issuer carrying a trailing slash", async () => {
98
+ // Discovery resolves the issuer itself; the provider passes it through
99
+ // rather than rewriting the operator's value.
100
+ mockDiscovery({ [ISSUER]: ["checkout"] });
101
+ const config = await authplaneProvider({
102
+ issuer: `${ISSUER}/`,
103
+ resource: "https://coffee.example.com/mcp",
104
+ });
105
+ expect(config.verify.issuer).toBe(ISSUER);
106
+ });
107
+ it("accepts a local http issuer for development", async () => {
108
+ const local = "http://localhost:9000";
109
+ mockDiscovery({ [local]: ["checkout"] });
110
+ const config = await authplaneProvider({
111
+ issuer: local,
112
+ resource: "http://localhost:3000/mcp",
113
+ });
114
+ expect(config.verify.issuer).toBe(local);
115
+ expect(config.verify.audience).toBe("http://localhost:3000/mcp");
116
+ });
117
+ it("forwards scopes and the required-scope floor", async () => {
118
+ mockDiscovery({ [ISSUER]: ["checkout", "profile"] });
119
+ const config = await authplaneProvider({
120
+ issuer: ISSUER,
121
+ resource: "https://coffee.example.com/mcp",
122
+ scopes: ["checkout"],
123
+ requiredScopes: ["checkout"],
124
+ });
125
+ expect(config.scopesSupported).toEqual(["checkout"]);
126
+ expect(config.requiredScopes).toEqual(["checkout"]);
127
+ });
128
+ it("advertises the registration endpoint for dynamic client registration", async () => {
129
+ mockDiscovery({ [ISSUER]: ["checkout"] });
130
+ const config = await authplaneProvider({
131
+ issuer: ISSUER,
132
+ resource: "https://coffee.example.com/mcp",
133
+ });
134
+ expect(config.oauthMetadata.issuer).toBe(ISSUER);
135
+ expect(config.oauthMetadata.registration_endpoint).toBe(`${ISSUER}/register`);
136
+ });
137
+ });
138
+ //# 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":"AAAA,2BAA2B;AAC3B,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD,SAAS,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC,CAAC;AAEtC,MAAM,MAAM,GAAG,uBAAuB,CAAC;AAEvC,SAAS,YAAY,CAAC,MAAc,EAAE,MAAgB;IACpD,OAAO;QACL,MAAM;QACN,sBAAsB,EAAE,GAAG,MAAM,YAAY;QAC7C,cAAc,EAAE,GAAG,MAAM,QAAQ;QACjC,qBAAqB,EAAE,GAAG,MAAM,WAAW;QAC3C,wBAAwB,EAAE,CAAC,MAAM,CAAC;QAClC,gBAAgB,EAAE,MAAM;QACxB,QAAQ,EAAE,GAAG,MAAM,wBAAwB;KAC5C,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,IAA8B;IACnD,OAAO,EAAE;SACN,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC;SAC1B,kBAAkB,CAAC,KAAK,EAAE,KAAwB,EAAE,EAAE;QACrD,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1B,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CACrC,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,GAAG,MAAM,mCAAmC,CACnE,CAAC;QACF,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE;YAC1D,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAChD,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC;AAED,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC5E,aAAa,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAE1C,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC;YACrC,MAAM,EAAE,MAAM;YACd,QAAQ,EAAE,gCAAgC;SAC3C,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;QACtE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,MAAM,wBAAwB,CAAC,CAAC;QACtE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,IAAI,CAAC;QACN,gCAAgC;QAChC,mCAAmC;QACnC,6BAA6B;QAC7B,2BAA2B;QAC3B,qCAAqC;KACtC,CAAC,CAAC,iDAAiD,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;QACvE,aAAa,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAE1C,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QAErE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,8EAA8E;IAC9E,6EAA6E;IAC7E,6DAA6D;IAC7D,EAAE,CAAC,IAAI,CAAC;QACN,uCAAuC;QACvC,CAAC,4BAA4B,EAAE,6BAA6B,CAAC;QAC7D,CAAC,uBAAuB,EAAE,wBAAwB,CAAC;QACnD,+BAA+B;QAC/B,CAAC,gCAAgC,EAAE,gCAAgC,CAAC;QACpE,mCAAmC;QACnC,CAAC,oCAAoC,EAAE,gCAAgC,CAAC;KACzE,CAAC,CAAC,iCAAiC,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE;QAClE,MAAM,QAAQ,GAAG,aAAa,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAE3D,MAAM,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,OAAO,CACnE,oBAAoB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,CAChD,CAAC;QACF,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC9D,aAAa,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAE1C,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC;YACrC,MAAM,EAAE,MAAM;YACd,QAAQ,EAAE,gCAAgC;YAC1C,QAAQ,EAAE,iBAAiB;SAC5B,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,IAAI,CAAC;QACN,CAAC,wBAAwB,EAAE,yBAAyB,CAAC;QACrD,CAAC,8BAA8B,EAAE,mCAAmC,CAAC;QACrE,CAAC,qCAAqC,EAAE,6BAA6B,CAAC;KACvE,CAAC,CAAC,mCAAmC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;QAClE,MAAM,QAAQ,GAAG,aAAa,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAE3D,MAAM,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,OAAO,CACnE,OAAO,CACR,CAAC;QACF,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,QAAQ,GAAG,aAAa,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAE3D,MAAM,CAAC,GAAG,EAAE,CACV,iBAAiB,CAAC;YAChB,MAAM,EAAE,eAAe;YACvB,QAAQ,EAAE,gCAAgC;SAC3C,CAAC,CACH,CAAC,OAAO,CAAC,kCAAkC,CAAC,CAAC;QAC9C,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,uEAAuE;QACvE,8CAA8C;QAC9C,aAAa,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAE1C,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC;YACrC,MAAM,EAAE,GAAG,MAAM,GAAG;YACpB,QAAQ,EAAE,gCAAgC;SAC3C,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,KAAK,GAAG,uBAAuB,CAAC;QACtC,aAAa,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAEzC,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC;YACrC,MAAM,EAAE,KAAK;YACb,QAAQ,EAAE,2BAA2B;SACtC,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,aAAa,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC;QAErD,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC;YACrC,MAAM,EAAE,MAAM;YACd,QAAQ,EAAE,gCAAgC;YAC1C,MAAM,EAAE,CAAC,UAAU,CAAC;YACpB,cAAc,EAAE,CAAC,UAAU,CAAC;SAC7B,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;QACrD,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sEAAsE,EAAE,KAAK,IAAI,EAAE;QACpF,aAAa,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAE1C,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC;YACrC,MAAM,EAAE,MAAM;YACd,QAAQ,EAAE,gCAAgC;SAC3C,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACjD,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC,IAAI,CACrD,GAAG,MAAM,WAAW,CACrB,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["// @vitest-environment node\nimport { afterEach, describe, expect, it, vi } from \"vitest\";\nimport { authplaneProvider } from \"./authplane.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 });\n\n expect(config.verify.audience).toBe(\"https://coffee.example.com/mcp\");\n expect(config.verify.issuer).toBe(ISSUER);\n expect(config.verify.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({ issuer: ISSUER, resource });\n\n expect(config.baseUrl).toBe(resource);\n expect(config.verify.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 const config = await authplaneProvider({\n issuer: ISSUER,\n resource: \"https://coffee.example.com/mcp\",\n audience: \"urn:acme:coffee\",\n });\n\n expect(config.verify.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 const config = await authplaneProvider({\n issuer: `${ISSUER}/`,\n resource: \"https://coffee.example.com/mcp\",\n });\n\n expect(config.verify.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 const config = await authplaneProvider({\n issuer: local,\n resource: \"http://localhost:3000/mcp\",\n });\n\n expect(config.verify.issuer).toBe(local);\n expect(config.verify.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 });\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 });\n\n expect(config.oauthMetadata.issuer).toBe(ISSUER);\n expect(config.oauthMetadata.registration_endpoint).toBe(\n `${ISSUER}/register`,\n );\n });\n});\n"]}