skybridge 0.0.0-dev.c2b3cec → 0.0.0-dev.c2c0f98

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 +3 -0
  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/header.d.ts +1 -1
  13. package/dist/cli/resolve-skybridge-config.d.ts +5 -0
  14. package/dist/cli/{resolve-views-dir.js → resolve-skybridge-config.js} +4 -3
  15. package/dist/cli/resolve-skybridge-config.js.map +1 -0
  16. package/dist/cli/run-plain.d.ts +18 -0
  17. package/dist/cli/run-plain.js +89 -0
  18. package/dist/cli/run-plain.js.map +1 -0
  19. package/dist/cli/use-execute-steps.d.ts +1 -1
  20. package/dist/cli/use-nodemon.d.ts +14 -0
  21. package/dist/cli/use-nodemon.js +71 -60
  22. package/dist/cli/use-nodemon.js.map +1 -1
  23. package/dist/cli/use-typescript-check.d.ts +8 -2
  24. package/dist/cli/use-typescript-check.js +70 -67
  25. package/dist/cli/use-typescript-check.js.map +1 -1
  26. package/dist/commands/build.d.ts +0 -2
  27. package/dist/commands/build.js +2 -61
  28. package/dist/commands/build.js.map +1 -1
  29. package/dist/commands/dev.d.ts +1 -0
  30. package/dist/commands/dev.js +35 -3
  31. package/dist/commands/dev.js.map +1 -1
  32. package/dist/commands/start.js +2 -2
  33. package/dist/commands/start.js.map +1 -1
  34. package/dist/context-warnings.d.ts +5 -0
  35. package/dist/context-warnings.js +31 -0
  36. package/dist/context-warnings.js.map +1 -0
  37. package/dist/context-warnings.test.js +28 -0
  38. package/dist/context-warnings.test.js.map +1 -0
  39. package/dist/server/app.d.ts +143 -0
  40. package/dist/server/app.js +229 -0
  41. package/dist/server/app.js.map +1 -0
  42. package/dist/server/auth/discovery.d.ts +33 -0
  43. package/dist/server/auth/discovery.js +56 -0
  44. package/dist/server/auth/discovery.js.map +1 -0
  45. package/dist/server/auth/discovery.test.js +93 -0
  46. package/dist/server/auth/discovery.test.js.map +1 -0
  47. package/dist/server/auth/index.d.ts +30 -0
  48. package/dist/server/auth/index.js +2 -0
  49. package/dist/server/auth/index.js.map +1 -0
  50. package/dist/server/auth/providers/auth0.d.ts +40 -0
  51. package/dist/server/auth/providers/auth0.js +31 -0
  52. package/dist/server/auth/providers/auth0.js.map +1 -0
  53. package/dist/server/auth/providers/auth0.test.js +50 -0
  54. package/dist/server/auth/providers/auth0.test.js.map +1 -0
  55. package/dist/server/auth/providers/authplane.d.ts +49 -0
  56. package/dist/server/auth/providers/authplane.js +57 -0
  57. package/dist/server/auth/providers/authplane.js.map +1 -0
  58. package/dist/server/auth/providers/authplane.test.d.ts +1 -0
  59. package/dist/server/auth/providers/authplane.test.js +140 -0
  60. package/dist/server/auth/providers/authplane.test.js.map +1 -0
  61. package/dist/server/auth/providers/clerk.d.ts +51 -0
  62. package/dist/server/auth/providers/clerk.js +19 -0
  63. package/dist/server/auth/providers/clerk.js.map +1 -0
  64. package/dist/server/auth/providers/clerk.test.d.ts +1 -0
  65. package/dist/server/auth/providers/clerk.test.js +30 -0
  66. package/dist/server/auth/providers/clerk.test.js.map +1 -0
  67. package/dist/server/auth/providers/custom.d.ts +40 -0
  68. package/dist/server/auth/providers/custom.js +56 -0
  69. package/dist/server/auth/providers/custom.js.map +1 -0
  70. package/dist/server/auth/providers/custom.test.d.ts +1 -0
  71. package/dist/server/auth/providers/custom.test.js +158 -0
  72. package/dist/server/auth/providers/custom.test.js.map +1 -0
  73. package/dist/server/auth/providers/descope.d.ts +44 -0
  74. package/dist/server/auth/providers/descope.js +44 -0
  75. package/dist/server/auth/providers/descope.js.map +1 -0
  76. package/dist/server/auth/providers/descope.test.d.ts +1 -0
  77. package/dist/server/auth/providers/descope.test.js +62 -0
  78. package/dist/server/auth/providers/descope.test.js.map +1 -0
  79. package/dist/server/auth/providers/shared.d.ts +2 -0
  80. package/dist/server/auth/providers/shared.js +6 -0
  81. package/dist/server/auth/providers/shared.js.map +1 -0
  82. package/dist/server/auth/providers/shared.test.d.ts +1 -0
  83. package/dist/server/auth/providers/shared.test.js +10 -0
  84. package/dist/server/auth/providers/shared.test.js.map +1 -0
  85. package/dist/server/auth/providers/stytch.d.ts +25 -0
  86. package/dist/server/auth/providers/stytch.js +16 -0
  87. package/dist/server/auth/providers/stytch.js.map +1 -0
  88. package/dist/server/auth/providers/verify-spy.d.ts +12 -0
  89. package/dist/server/auth/providers/verify-spy.js +21 -0
  90. package/dist/server/auth/providers/verify-spy.js.map +1 -0
  91. package/dist/server/auth/providers/workos.d.ts +33 -0
  92. package/dist/server/auth/providers/workos.js +15 -0
  93. package/dist/server/auth/providers/workos.js.map +1 -0
  94. package/dist/server/auth/security-schemes.d.ts +23 -0
  95. package/dist/server/auth/security-schemes.js +64 -0
  96. package/dist/server/auth/security-schemes.js.map +1 -0
  97. package/dist/server/auth/security-schemes.test.d.ts +1 -0
  98. package/dist/server/auth/security-schemes.test.js +99 -0
  99. package/dist/server/auth/security-schemes.test.js.map +1 -0
  100. package/dist/server/auth/setup.d.ts +6 -0
  101. package/dist/server/auth/setup.js +91 -0
  102. package/dist/server/auth/setup.js.map +1 -0
  103. package/dist/server/auth/setup.test.d.ts +1 -0
  104. package/dist/server/auth/setup.test.js +512 -0
  105. package/dist/server/auth/setup.test.js.map +1 -0
  106. package/dist/server/auth/verify.d.ts +46 -0
  107. package/dist/server/auth/verify.js +62 -0
  108. package/dist/server/auth/verify.js.map +1 -0
  109. package/dist/server/auth/verify.test.d.ts +1 -0
  110. package/dist/server/auth/verify.test.js +149 -0
  111. package/dist/server/auth/verify.test.js.map +1 -0
  112. package/dist/server/auth-extra.test-d.js +71 -0
  113. package/dist/server/auth-extra.test-d.js.map +1 -0
  114. package/dist/server/auth.d.ts +42 -5
  115. package/dist/server/auth.js +3 -4
  116. package/dist/server/auth.js.map +1 -1
  117. package/dist/server/build-manifest.test.js +6 -5
  118. package/dist/server/build-manifest.test.js.map +1 -1
  119. package/dist/server/content-helpers.d.ts +1 -1
  120. package/dist/server/content-helpers.js.map +1 -1
  121. package/dist/server/express.d.ts +14 -3
  122. package/dist/server/express.js +20 -32
  123. package/dist/server/express.js.map +1 -1
  124. package/dist/server/express.test.js +130 -102
  125. package/dist/server/express.test.js.map +1 -1
  126. package/dist/server/host.d.ts +2 -0
  127. package/dist/server/host.js +11 -0
  128. package/dist/server/host.js.map +1 -0
  129. package/dist/server/host.test.d.ts +1 -0
  130. package/dist/server/host.test.js +13 -0
  131. package/dist/server/host.test.js.map +1 -0
  132. package/dist/server/index.d.ts +17 -3
  133. package/dist/server/index.js +13 -2
  134. package/dist/server/index.js.map +1 -1
  135. package/dist/server/mcp-errors.test.d.ts +1 -0
  136. package/dist/server/mcp-errors.test.js +37 -0
  137. package/dist/server/mcp-errors.test.js.map +1 -0
  138. package/dist/server/middleware.d.ts +35 -10
  139. package/dist/server/middleware.js +33 -0
  140. package/dist/server/middleware.js.map +1 -1
  141. package/dist/server/middleware.test-d.js +1 -1
  142. package/dist/server/middleware.test-d.js.map +1 -1
  143. package/dist/server/middleware.test.js +190 -131
  144. package/dist/server/middleware.test.js.map +1 -1
  145. package/dist/server/register-tool.test.d.ts +1 -0
  146. package/dist/server/register-tool.test.js +23 -0
  147. package/dist/server/register-tool.test.js.map +1 -0
  148. package/dist/server/requestOrigin.d.ts +7 -0
  149. package/dist/server/requestOrigin.js +25 -0
  150. package/dist/server/requestOrigin.js.map +1 -0
  151. package/dist/server/server.d.ts +167 -114
  152. package/dist/server/server.js +288 -350
  153. package/dist/server/server.js.map +1 -1
  154. package/dist/server/skills-integration.test.d.ts +1 -0
  155. package/dist/server/skills-integration.test.js +79 -0
  156. package/dist/server/skills-integration.test.js.map +1 -0
  157. package/dist/server/skills.d.ts +27 -0
  158. package/dist/server/skills.js +188 -0
  159. package/dist/server/skills.js.map +1 -0
  160. package/dist/server/skills.test.d.ts +1 -0
  161. package/dist/server/skills.test.js +243 -0
  162. package/dist/server/skills.test.js.map +1 -0
  163. package/dist/server/tool-extra.test.d.ts +1 -0
  164. package/dist/server/tool-extra.test.js +72 -0
  165. package/dist/server/tool-extra.test.js.map +1 -0
  166. package/dist/server/view-name.test-d.d.ts +1 -0
  167. package/dist/server/view-name.test-d.js +8 -0
  168. package/dist/server/view-name.test-d.js.map +1 -0
  169. package/dist/server/view-resource-resolution.test.js +101 -17
  170. package/dist/server/view-resource-resolution.test.js.map +1 -1
  171. package/dist/server/viewsDevServer.js +4 -1
  172. package/dist/server/viewsDevServer.js.map +1 -1
  173. package/dist/standard-schema.d.ts +3 -0
  174. package/dist/standard-schema.js +2 -0
  175. package/dist/standard-schema.js.map +1 -0
  176. package/dist/test/utils.d.ts +9 -11
  177. package/dist/test/utils.js +18 -13
  178. package/dist/test/utils.js.map +1 -1
  179. package/dist/test/view.test.js +88 -120
  180. package/dist/test/view.test.js.map +1 -1
  181. package/dist/views/index.d.ts +3 -0
  182. package/dist/views/index.js +3 -0
  183. package/dist/views/index.js.map +1 -0
  184. package/dist/{web/plugin → views}/scan-views.js +1 -1
  185. package/dist/views/scan-views.js.map +1 -0
  186. package/dist/views/scan-views.test.d.ts +1 -0
  187. package/dist/views/scan-views.test.js.map +1 -0
  188. package/dist/views/validate-view.d.ts +1 -0
  189. package/dist/{web/plugin → views}/validate-view.js +2 -2
  190. package/dist/views/validate-view.js.map +1 -0
  191. package/dist/views/validate-view.test.d.ts +1 -0
  192. package/dist/views/validate-view.test.js.map +1 -0
  193. package/dist/web/bridges/adaptor.d.ts +51 -0
  194. package/dist/web/bridges/adaptor.js +327 -0
  195. package/dist/web/bridges/adaptor.js.map +1 -0
  196. package/dist/web/bridges/adaptor.test.d.ts +1 -0
  197. package/dist/web/bridges/adaptor.test.js +225 -0
  198. package/dist/web/bridges/adaptor.test.js.map +1 -0
  199. package/dist/web/bridges/apps-sdk/bridge.d.ts +6 -2
  200. package/dist/web/bridges/apps-sdk/bridge.js +15 -4
  201. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
  202. package/dist/web/bridges/apps-sdk/index.d.ts +0 -1
  203. package/dist/web/bridges/apps-sdk/index.js +0 -1
  204. package/dist/web/bridges/apps-sdk/index.js.map +1 -1
  205. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.d.ts +1 -1
  206. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js +1 -1
  207. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
  208. package/dist/web/bridges/get-adaptor.d.ts +4 -4
  209. package/dist/web/bridges/get-adaptor.js +6 -11
  210. package/dist/web/bridges/get-adaptor.js.map +1 -1
  211. package/dist/web/bridges/get-adaptor.test.d.ts +1 -0
  212. package/dist/web/bridges/get-adaptor.test.js +32 -0
  213. package/dist/web/bridges/get-adaptor.test.js.map +1 -0
  214. package/dist/web/bridges/mcp-app/bridge.d.ts +13 -2
  215. package/dist/web/bridges/mcp-app/bridge.js +51 -7
  216. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  217. package/dist/web/bridges/mcp-app/bridge.test.d.ts +1 -0
  218. package/dist/web/bridges/mcp-app/bridge.test.js +17 -0
  219. package/dist/web/bridges/mcp-app/bridge.test.js.map +1 -0
  220. package/dist/web/bridges/mcp-app/index.d.ts +0 -1
  221. package/dist/web/bridges/mcp-app/index.js +0 -1
  222. package/dist/web/bridges/mcp-app/index.js.map +1 -1
  223. package/dist/web/bridges/mcp-app/types.d.ts +2 -0
  224. package/dist/web/bridges/mcp-app/types.js.map +1 -1
  225. package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +1 -1
  226. package/dist/web/bridges/mcp-app/use-mcp-app-context.js +1 -1
  227. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
  228. package/dist/web/bridges/mcp-app/view-tools.test.js +5 -6
  229. package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -1
  230. package/dist/web/bridges/types.d.ts +27 -9
  231. package/dist/web/bridges/types.js +14 -1
  232. package/dist/web/bridges/types.js.map +1 -1
  233. package/dist/web/bridges/types.test.d.ts +1 -0
  234. package/dist/web/bridges/types.test.js +19 -0
  235. package/dist/web/bridges/types.test.js.map +1 -0
  236. package/dist/web/components/modal-provider.d.ts +1 -1
  237. package/dist/web/components/modal-provider.js +4 -3
  238. package/dist/web/components/modal-provider.js.map +1 -1
  239. package/dist/web/create-store.test.js +9 -6
  240. package/dist/web/create-store.test.js.map +1 -1
  241. package/dist/web/data-llm.d.ts +1 -1
  242. package/dist/web/data-llm.test.js +19 -6
  243. package/dist/web/data-llm.test.js.map +1 -1
  244. package/dist/web/generate-helpers.d.ts +13 -12
  245. package/dist/web/generate-helpers.js +10 -9
  246. package/dist/web/generate-helpers.js.map +1 -1
  247. package/dist/web/generate-helpers.test-d.js +3 -3
  248. package/dist/web/generate-helpers.test-d.js.map +1 -1
  249. package/dist/web/generate-helpers.test.js +2 -2
  250. package/dist/web/generate-helpers.test.js.map +1 -1
  251. package/dist/web/hooks/index.d.ts +2 -1
  252. package/dist/web/hooks/index.js +2 -1
  253. package/dist/web/hooks/index.js.map +1 -1
  254. package/dist/web/hooks/test/utils.d.ts +2 -1
  255. package/dist/web/hooks/test/utils.js +1 -1
  256. package/dist/web/hooks/test/utils.js.map +1 -1
  257. package/dist/web/hooks/use-call-tool.test.js +39 -25
  258. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  259. package/dist/web/hooks/use-display-mode.d.ts +1 -1
  260. package/dist/web/hooks/use-display-mode.js +1 -1
  261. package/dist/web/hooks/use-display-mode.js.map +1 -1
  262. package/dist/web/hooks/use-display-mode.test.js +57 -21
  263. package/dist/web/hooks/use-display-mode.test.js.map +1 -1
  264. package/dist/web/hooks/use-download.d.ts +1 -3
  265. package/dist/web/hooks/use-download.js +1 -2
  266. package/dist/web/hooks/use-download.js.map +1 -1
  267. package/dist/web/hooks/use-download.test.js +16 -8
  268. package/dist/web/hooks/use-download.test.js.map +1 -1
  269. package/dist/web/hooks/use-files.test.js +11 -3
  270. package/dist/web/hooks/use-files.test.js.map +1 -1
  271. package/dist/web/hooks/use-host.d.ts +22 -0
  272. package/dist/web/hooks/use-host.js +30 -0
  273. package/dist/web/hooks/use-host.js.map +1 -0
  274. package/dist/web/hooks/use-host.test.d.ts +1 -0
  275. package/dist/web/hooks/use-host.test.js +59 -0
  276. package/dist/web/hooks/use-host.test.js.map +1 -0
  277. package/dist/web/hooks/use-open-external.test.js +14 -4
  278. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  279. package/dist/web/hooks/use-register-view-tool.d.ts +2 -3
  280. package/dist/web/hooks/use-register-view-tool.js.map +1 -1
  281. package/dist/web/hooks/use-request-close.test.js +35 -40
  282. package/dist/web/hooks/use-request-close.test.js.map +1 -1
  283. package/dist/web/hooks/use-request-modal.test.js +11 -1
  284. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  285. package/dist/web/hooks/use-request-size.d.ts +1 -1
  286. package/dist/web/hooks/use-request-size.js +1 -1
  287. package/dist/web/hooks/use-request-size.js.map +1 -1
  288. package/dist/web/hooks/use-request-size.test.js +35 -53
  289. package/dist/web/hooks/use-request-size.test.js.map +1 -1
  290. package/dist/web/hooks/use-set-open-in-app-url.test.js +29 -9
  291. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  292. package/dist/web/hooks/use-tool-info.d.ts +1 -20
  293. package/dist/web/hooks/use-tool-info.js +0 -1
  294. package/dist/web/hooks/use-tool-info.js.map +1 -1
  295. package/dist/web/hooks/use-tool-info.test-d.js +0 -5
  296. package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
  297. package/dist/web/hooks/use-tool-info.test.js +37 -32
  298. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  299. package/dist/web/hooks/use-user.d.ts +3 -2
  300. package/dist/web/hooks/use-user.js +4 -3
  301. package/dist/web/hooks/use-user.js.map +1 -1
  302. package/dist/web/hooks/use-user.test.js +84 -36
  303. package/dist/web/hooks/use-user.test.js.map +1 -1
  304. package/dist/web/hooks/use-view-state.js +19 -9
  305. package/dist/web/hooks/use-view-state.js.map +1 -1
  306. package/dist/web/hooks/use-view-state.test.js +38 -3
  307. package/dist/web/hooks/use-view-state.test.js.map +1 -1
  308. package/dist/web/hooks/use-viewport.d.ts +20 -0
  309. package/dist/web/hooks/use-viewport.js +21 -0
  310. package/dist/web/hooks/use-viewport.js.map +1 -0
  311. package/dist/web/hooks/use-viewport.test.d.ts +1 -0
  312. package/dist/web/hooks/use-viewport.test.js +109 -0
  313. package/dist/web/hooks/use-viewport.test.js.map +1 -0
  314. package/dist/web/index.d.ts +1 -0
  315. package/dist/web/index.js.map +1 -1
  316. package/dist/web/mount-view.d.ts +3 -2
  317. package/dist/web/mount-view.js +3 -2
  318. package/dist/web/mount-view.js.map +1 -1
  319. package/package.json +30 -26
  320. package/dist/cli/resolve-views-dir.d.ts +0 -1
  321. package/dist/cli/resolve-views-dir.js.map +0 -1
  322. package/dist/web/bridges/apps-sdk/adaptor.d.ts +0 -29
  323. package/dist/web/bridges/apps-sdk/adaptor.js +0 -117
  324. package/dist/web/bridges/apps-sdk/adaptor.js.map +0 -1
  325. package/dist/web/bridges/mcp-app/adaptor.d.ts +0 -53
  326. package/dist/web/bridges/mcp-app/adaptor.js +0 -283
  327. package/dist/web/bridges/mcp-app/adaptor.js.map +0 -1
  328. package/dist/web/hooks/use-layout.d.ts +0 -24
  329. package/dist/web/hooks/use-layout.js +0 -25
  330. package/dist/web/hooks/use-layout.js.map +0 -1
  331. package/dist/web/hooks/use-layout.test.js +0 -96
  332. package/dist/web/hooks/use-layout.test.js.map +0 -1
  333. package/dist/web/plugin/data-llm.test.js +0 -81
  334. package/dist/web/plugin/data-llm.test.js.map +0 -1
  335. package/dist/web/plugin/plugin.d.ts +0 -33
  336. package/dist/web/plugin/plugin.js +0 -189
  337. package/dist/web/plugin/plugin.js.map +0 -1
  338. package/dist/web/plugin/scan-views.js.map +0 -1
  339. package/dist/web/plugin/scan-views.test.js.map +0 -1
  340. package/dist/web/plugin/transform-data-llm.d.ts +0 -12
  341. package/dist/web/plugin/transform-data-llm.js +0 -96
  342. package/dist/web/plugin/transform-data-llm.js.map +0 -1
  343. package/dist/web/plugin/transform-data-llm.test.js +0 -81
  344. package/dist/web/plugin/transform-data-llm.test.js.map +0 -1
  345. package/dist/web/plugin/validate-view.d.ts +0 -1
  346. package/dist/web/plugin/validate-view.js.map +0 -1
  347. package/dist/web/plugin/validate-view.test.js.map +0 -1
  348. /package/dist/{web/hooks/use-layout.test.d.ts → cli/build-steps.test.d.ts} +0 -0
  349. /package/dist/{web/plugin/data-llm.test.d.ts → context-warnings.test.d.ts} +0 -0
  350. /package/dist/{web/plugin/transform-data-llm.test.d.ts → server/auth/discovery.test.d.ts} +0 -0
  351. /package/dist/{web/plugin/validate-view.test.d.ts → server/auth/providers/auth0.test.d.ts} +0 -0
  352. /package/dist/{web/plugin/scan-views.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,229 @@
1
+ import http from "node:http";
2
+ import { createMcpHandler, } from "@modelcontextprotocol/server";
3
+ import { setupOAuth } from "./auth/setup.js";
4
+ import { createApp, createBaseApp } from "./express.js";
5
+ import { createMiddlewareEntry } from "./metric.js";
6
+ import { buildMiddlewareChain, getHandlerMaps } from "./middleware.js";
7
+ import { McpServer, } from "./server.js";
8
+ /**
9
+ * A Skybridge app: the HTTP surface (Express, OAuth metadata, the `/mcp`
10
+ * route) plus a factory that builds the MCP server for each request.
11
+ *
12
+ * The factory runs once per request, so tools, resources, prompts and views
13
+ * are always registered on the instance that serves the request. It also runs
14
+ * once at construction time, which surfaces registration errors at boot and
15
+ * gives the OAuth layer the set of per-tool security schemes.
16
+ *
17
+ * @typeParam TTools - Accumulated tool registry, inferred from the server the
18
+ * factory returns. You almost never set this manually.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * export const app = new Skybridge(
23
+ * { name: "my-app", version: "1.0.0", capabilities: {} },
24
+ * (server) =>
25
+ * server.registerTool(
26
+ * {
27
+ * name: "search",
28
+ * inputSchema: { query: z.string() },
29
+ * view: { component: "search" },
30
+ * },
31
+ * async ({ query }) => ({ content: `Results for ${query}` }),
32
+ * ),
33
+ * );
34
+ *
35
+ * export type AppType = typeof app;
36
+ * ```
37
+ *
38
+ * @see https://docs.skybridge.tech/api-reference/mcp-server
39
+ */
40
+ export class Skybridge {
41
+ serverInfo;
42
+ serverOptions;
43
+ skybridgeOptions;
44
+ setup;
45
+ expressApp;
46
+ errorMiddleware = [];
47
+ monitoringEntry = createMiddlewareEntry();
48
+ resolveResourceMetadataUrl;
49
+ cachedFetchHandler;
50
+ constructor(config, setup) {
51
+ const { name, title, version, description, icons, websiteUrl, json, oauth, skills, ...serverOptions } = config;
52
+ this.serverInfo = { name, title, version, description, icons, websiteUrl };
53
+ this.serverOptions = serverOptions;
54
+ this.skybridgeOptions = { json, oauth, skills };
55
+ this.setup = setup;
56
+ const sample = this.buildServer();
57
+ this.expressApp = createBaseApp(json);
58
+ if (oauth) {
59
+ this.resolveResourceMetadataUrl = setupOAuth(this.expressApp, oauth, sample.securitySchemesByTool);
60
+ }
61
+ }
62
+ /**
63
+ * The underlying Express app. Use this to extend the HTTP server with
64
+ * custom routes, middleware, or settings — e.g.
65
+ * `app.express.get("/health", ...)`.
66
+ *
67
+ * `express.json()` is pre-applied — tune it via the `json` config field,
68
+ * e.g. `new Skybridge({ name, version, json: { limit: "10mb" } }, setup)`.
69
+ * Register your handlers before `run()`; after `run()`, dev-mode middleware,
70
+ * the `/mcp` route, and the default error handler are appended in that order.
71
+ *
72
+ * Note: Alpic Cloud only routes traffic to `/mcp` — custom routes work
73
+ * locally and on self-hosted deployments.
74
+ */
75
+ get express() {
76
+ return this.expressApp;
77
+ }
78
+ /**
79
+ * The app's fetch handler for `/mcp`: a `Request` → `Response` function that
80
+ * builds a fresh MCP server per request. Memoized, so the same handler is
81
+ * reused across requests.
82
+ */
83
+ get fetchHandler() {
84
+ this.cachedFetchHandler ??= createMcpHandler(() => this.createServerInstance(), {
85
+ onerror: (error) => {
86
+ console.error("Error handling MCP request:", error);
87
+ },
88
+ });
89
+ return this.cachedFetchHandler;
90
+ }
91
+ /**
92
+ * Build a fresh server for one stateless HTTP request, as
93
+ * `createMcpHandler`'s factory contract requires: the factory runs again so
94
+ * the SDK's handler closures belong to the instance whose protocol era it
95
+ * stamps. Sharing one instance's handler maps instead would bind them to an
96
+ * instance that is never marked, pinning every request to the 2025 codec and
97
+ * letting concurrent callers overwrite each other's negotiated version.
98
+ */
99
+ createServerInstance() {
100
+ const server = this.buildServer();
101
+ this.instrumentHandlers(server);
102
+ return server.server;
103
+ }
104
+ /**
105
+ * Connect a Skybridge app to an MCP transport. Use this when you're
106
+ * embedding Skybridge in a host that already manages its own transport
107
+ * (e.g. stdio for desktop apps); for HTTP, prefer {@link Skybridge.run}
108
+ * which sets the transport up for you.
109
+ */
110
+ async connect(transport) {
111
+ await this.createServerInstance().connect(transport);
112
+ }
113
+ use(pathOrHandler, ...handlers) {
114
+ // Branching is load-bearing: Express's `app.use` overloads can't be
115
+ // resolved against a `string | RequestHandler` union, so we narrow.
116
+ if (typeof pathOrHandler === "string") {
117
+ this.expressApp.use(pathOrHandler, ...handlers);
118
+ }
119
+ else {
120
+ this.expressApp.use(pathOrHandler, ...handlers);
121
+ }
122
+ return this;
123
+ }
124
+ useOnError(pathOrHandler, ...handlers) {
125
+ if (typeof pathOrHandler === "string") {
126
+ this.errorMiddleware.push({ path: pathOrHandler, handlers });
127
+ }
128
+ else {
129
+ this.errorMiddleware.push({ handlers: [pathOrHandler, ...handlers] });
130
+ }
131
+ return this;
132
+ }
133
+ /**
134
+ * Start the HTTP server. Listens on `process.env.__PORT` (default `3000`),
135
+ * mounts the `/mcp` route, and applies any custom Express middleware
136
+ * registered via {@link Skybridge.use} / {@link Skybridge.useOnError}.
137
+ *
138
+ * On Cloudflare Workers / workerd, returns an object exposing `fetch` so
139
+ * the runtime can bridge incoming requests to the Node HTTP server. On
140
+ * Vercel (`VERCEL === "1"`), returns the Express app directly so the
141
+ * serverless function entry can call it as a `(req, res)` handler. On
142
+ * Node, returns `undefined` once listening. When the process was spawned
143
+ * with an IPC channel, the bound port is reported to the parent as
144
+ * `{ type: "skybridge:listening", port }`, the readiness signal test runners
145
+ * wait on instead of polling.
146
+ */
147
+ async run() {
148
+ if (process.env.VERCEL === "1") {
149
+ // createApp only reads httpServer inside its dev-only branch
150
+ // (viewsDevServer); under VERCEL=1 + NODE_ENV=production it's a
151
+ // bare object passed to satisfy the required parameter.
152
+ const httpServer = http.createServer();
153
+ await createApp({
154
+ app: this,
155
+ httpServer,
156
+ errorMiddleware: this.errorMiddleware,
157
+ });
158
+ return this.expressApp;
159
+ }
160
+ const httpServer = http.createServer();
161
+ await createApp({
162
+ app: this,
163
+ httpServer,
164
+ errorMiddleware: this.errorMiddleware,
165
+ });
166
+ httpServer.on("request", this.expressApp);
167
+ const intendedPort = parseInt(process.env.__PORT ?? "3000", 10);
168
+ await new Promise((resolve, reject) => {
169
+ httpServer.on("error", (error) => {
170
+ console.error("Failed to start server:", error);
171
+ reject(error);
172
+ });
173
+ httpServer.listen(intendedPort, () => {
174
+ resolve();
175
+ });
176
+ });
177
+ const { port } = httpServer.address();
178
+ process.send?.({ type: "skybridge:listening", port });
179
+ // On workerd, bridge the Node http server to a Workers fetch handler.
180
+ // The specifier is held in a variable to sidestep tsc's module resolution
181
+ // (`cloudflare:node` only exists under wrangler/workerd).
182
+ if (typeof navigator !== "undefined" &&
183
+ navigator.userAgent === "Cloudflare-Workers") {
184
+ const cloudflareNode = "cloudflare:node";
185
+ const { httpServerHandler } = await import(cloudflareNode);
186
+ return httpServerHandler({ port });
187
+ }
188
+ const shutdown = () => {
189
+ // Drop both handlers so a second signal falls through to Node's default
190
+ // (force-quit on a second Ctrl+C while drain is hanging).
191
+ process.off("SIGTERM", shutdown);
192
+ process.off("SIGINT", shutdown);
193
+ httpServer.close(() => process.exit(0));
194
+ // Force exit if connections don't drain in time so the port is still
195
+ // released promptly (e.g. for nodemon restarts).
196
+ setTimeout(() => process.exit(0), 3000).unref();
197
+ };
198
+ process.on("SIGTERM", shutdown);
199
+ process.on("SIGINT", shutdown);
200
+ return undefined;
201
+ }
202
+ buildServer() {
203
+ const server = new McpServer(this.serverInfo, this.serverOptions, this.skybridgeOptions);
204
+ if (this.resolveResourceMetadataUrl) {
205
+ server.setResourceMetadataUrlResolver(this.resolveResourceMetadataUrl);
206
+ }
207
+ return this.setup(server);
208
+ }
209
+ middlewareEntries(server) {
210
+ return [
211
+ ...(this.monitoringEntry ? [this.monitoringEntry] : []),
212
+ ...server.protocolMiddlewareEntries(),
213
+ ];
214
+ }
215
+ instrumentHandlers(server) {
216
+ const entries = this.middlewareEntries(server);
217
+ const { requestHandlers, notificationHandlers } = getHandlerMaps(server.server);
218
+ const instrumentMap = (map, isNotification) => {
219
+ for (const [method, handler] of map) {
220
+ map.set(method, buildMiddlewareChain(method, isNotification, handler, entries));
221
+ }
222
+ const originalSet = map.set.bind(map);
223
+ map.set = (method, handler) => originalSet(method, buildMiddlewareChain(method, isNotification, handler, entries));
224
+ };
225
+ instrumentMap(requestHandlers, false);
226
+ instrumentMap(notificationHandlers, true);
227
+ }
228
+ }
229
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"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","sourcesContent":["import http from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\nimport {\n createMcpHandler,\n type Implementation,\n type McpHttpHandler,\n type Server as SdkServer,\n type ServerOptions,\n} from \"@modelcontextprotocol/server\";\nimport type { ErrorRequestHandler, Express, RequestHandler } from \"express\";\nimport { type ResourceMetadataUrlResolver, setupOAuth } from \"./auth/setup.js\";\nimport type { ExtraClaims } from \"./auth.js\";\nimport { createApp, createBaseApp } from \"./express.js\";\nimport { createMiddlewareEntry } from \"./metric.js\";\nimport type { McpMiddlewareEntry } from \"./middleware.js\";\nimport { buildMiddlewareChain, getHandlerMaps } from \"./middleware.js\";\nimport {\n McpServer,\n type McpServerTypes,\n type SkybridgeServerOptions,\n type ToolDef,\n} from \"./server.js\";\n\ntype ErrorMiddlewareConfig = {\n path?: string;\n handlers: ErrorRequestHandler[];\n};\n\n/**\n * Everything a Skybridge app needs in one bag: the MCP implementation info\n * (`name`, `version`, …), the SDK's {@link ServerOptions} (`capabilities`,\n * `instructions`, …), and the Skybridge-specific options (`oauth`, `json`,\n * `skills`).\n */\nexport type SkybridgeConfig<TAuthExtra extends ExtraClaims = ExtraClaims> =\n Implementation & ServerOptions & SkybridgeServerOptions<TAuthExtra>;\n\n/**\n * The factory that declares an app's MCP surface. Called with a fresh\n * {@link McpServer} for every request, it must **return** the chained server so\n * `typeof app` carries the registered tool types.\n */\nexport type SkybridgeSetup<\n TTools extends Record<string, ToolDef>,\n TAuthExtra extends ExtraClaims,\n> = (\n server: McpServer<Record<never, ToolDef>, TAuthExtra>,\n) => McpServer<TTools, TAuthExtra>;\n\n/**\n * A Skybridge app: the HTTP surface (Express, OAuth metadata, the `/mcp`\n * route) plus a factory that builds the MCP server for each request.\n *\n * The factory runs once per request, so tools, resources, prompts and views\n * are always registered on the instance that serves the request. It also runs\n * once at construction time, which surfaces registration errors at boot and\n * gives the OAuth layer the set of per-tool security schemes.\n *\n * @typeParam TTools - Accumulated tool registry, inferred from the server the\n * factory returns. You almost never set this manually.\n *\n * @example\n * ```ts\n * export const app = new Skybridge(\n * { name: \"my-app\", version: \"1.0.0\", capabilities: {} },\n * (server) =>\n * server.registerTool(\n * {\n * name: \"search\",\n * inputSchema: { query: z.string() },\n * view: { component: \"search\" },\n * },\n * async ({ query }) => ({ content: `Results for ${query}` }),\n * ),\n * );\n *\n * export type AppType = typeof app;\n * ```\n *\n * @see https://docs.skybridge.tech/api-reference/mcp-server\n */\nexport class Skybridge<\n TTools extends Record<string, ToolDef> = Record<never, ToolDef>,\n TAuthExtra extends ExtraClaims = ExtraClaims,\n> {\n declare readonly $types: McpServerTypes<TTools>;\n private readonly serverInfo: Implementation;\n private readonly serverOptions: ServerOptions;\n private readonly skybridgeOptions: SkybridgeServerOptions<TAuthExtra>;\n private readonly setup: SkybridgeSetup<TTools, TAuthExtra>;\n private readonly expressApp: Express;\n private readonly errorMiddleware: ErrorMiddlewareConfig[] = [];\n private readonly monitoringEntry: McpMiddlewareEntry | null =\n createMiddlewareEntry();\n private resolveResourceMetadataUrl?: ResourceMetadataUrlResolver;\n private cachedFetchHandler?: McpHttpHandler;\n\n constructor(\n config: SkybridgeConfig<TAuthExtra>,\n setup: SkybridgeSetup<TTools, TAuthExtra>,\n ) {\n const {\n name,\n title,\n version,\n description,\n icons,\n websiteUrl,\n json,\n oauth,\n skills,\n ...serverOptions\n } = config;\n\n this.serverInfo = { name, title, version, description, icons, websiteUrl };\n this.serverOptions = serverOptions;\n this.skybridgeOptions = { json, oauth, skills };\n this.setup = setup;\n\n const sample = this.buildServer();\n\n this.expressApp = createBaseApp(json);\n if (oauth) {\n this.resolveResourceMetadataUrl = setupOAuth(\n this.expressApp,\n oauth,\n sample.securitySchemesByTool,\n );\n }\n }\n\n /**\n * The underlying Express app. Use this to extend the HTTP server with\n * custom routes, middleware, or settings — e.g.\n * `app.express.get(\"/health\", ...)`.\n *\n * `express.json()` is pre-applied — tune it via the `json` config field,\n * e.g. `new Skybridge({ name, version, json: { limit: \"10mb\" } }, setup)`.\n * Register your handlers before `run()`; after `run()`, dev-mode middleware,\n * the `/mcp` route, and the default error handler are appended in that order.\n *\n * Note: Alpic Cloud only routes traffic to `/mcp` — custom routes work\n * locally and on self-hosted deployments.\n */\n get express(): Express {\n return this.expressApp;\n }\n\n /**\n * The app's fetch handler for `/mcp`: a `Request` → `Response` function that\n * builds a fresh MCP server per request. Memoized, so the same handler is\n * reused across requests.\n */\n get fetchHandler(): McpHttpHandler {\n this.cachedFetchHandler ??= createMcpHandler(\n () => this.createServerInstance(),\n {\n onerror: (error) => {\n console.error(\"Error handling MCP request:\", error);\n },\n },\n );\n return this.cachedFetchHandler;\n }\n\n /**\n * Build a fresh server for one stateless HTTP request, as\n * `createMcpHandler`'s factory contract requires: the factory runs again so\n * the SDK's handler closures belong to the instance whose protocol era it\n * stamps. Sharing one instance's handler maps instead would bind them to an\n * instance that is never marked, pinning every request to the 2025 codec and\n * letting concurrent callers overwrite each other's negotiated version.\n */\n createServerInstance(): SdkServer {\n const server = this.buildServer();\n this.instrumentHandlers(server);\n return server.server;\n }\n\n /**\n * Connect a Skybridge app to an MCP transport. Use this when you're\n * embedding Skybridge in a host that already manages its own transport\n * (e.g. stdio for desktop apps); for HTTP, prefer {@link Skybridge.run}\n * which sets the transport up for you.\n */\n async connect(transport: Parameters<SdkServer[\"connect\"]>[0]): Promise<void> {\n await this.createServerInstance().connect(transport);\n }\n\n /**\n * Register Express middleware on the underlying app. Mirrors `app.use` —\n * pass handlers directly or a path-prefixed handler list. Register before\n * {@link Skybridge.run}; ordering matches Express.\n *\n * Note: Alpic Cloud only routes traffic to `/mcp`. Custom paths work\n * locally and on self-hosted deployments.\n */\n use(...handlers: RequestHandler[]): this;\n use(path: string, ...handlers: RequestHandler[]): this;\n use(\n pathOrHandler: string | RequestHandler,\n ...handlers: RequestHandler[]\n ): this {\n // Branching is load-bearing: Express's `app.use` overloads can't be\n // resolved against a `string | RequestHandler` union, so we narrow.\n if (typeof pathOrHandler === \"string\") {\n this.expressApp.use(pathOrHandler, ...handlers);\n } else {\n this.expressApp.use(pathOrHandler, ...handlers);\n }\n return this;\n }\n\n /**\n * Register Express error-handling middleware to run after the built-in\n * `/mcp` route (or your custom route). Use this to log or transform errors\n * thrown by tool handlers before the default error handler responds.\n *\n * @example\n * ```ts\n * app.useOnError((err, _req, _res, next) => {\n * logger.error(err);\n * next(err);\n * });\n * ```\n */\n useOnError(...handlers: ErrorRequestHandler[]): this;\n useOnError(path: string, ...handlers: ErrorRequestHandler[]): this;\n useOnError(\n pathOrHandler: string | ErrorRequestHandler,\n ...handlers: ErrorRequestHandler[]\n ): this {\n if (typeof pathOrHandler === \"string\") {\n this.errorMiddleware.push({ path: pathOrHandler, handlers });\n } else {\n this.errorMiddleware.push({ handlers: [pathOrHandler, ...handlers] });\n }\n return this;\n }\n\n /**\n * Start the HTTP server. Listens on `process.env.__PORT` (default `3000`),\n * mounts the `/mcp` route, and applies any custom Express middleware\n * registered via {@link Skybridge.use} / {@link Skybridge.useOnError}.\n *\n * On Cloudflare Workers / workerd, returns an object exposing `fetch` so\n * the runtime can bridge incoming requests to the Node HTTP server. On\n * Vercel (`VERCEL === \"1\"`), returns the Express app directly so the\n * serverless function entry can call it as a `(req, res)` handler. On\n * Node, returns `undefined` once listening. When the process was spawned\n * with an IPC channel, the bound port is reported to the parent as\n * `{ type: \"skybridge:listening\", port }`, the readiness signal test runners\n * wait on instead of polling.\n */\n async run(): Promise<\n { fetch: (...args: unknown[]) => unknown } | Express | undefined\n > {\n if (process.env.VERCEL === \"1\") {\n // createApp only reads httpServer inside its dev-only branch\n // (viewsDevServer); under VERCEL=1 + NODE_ENV=production it's a\n // bare object passed to satisfy the required parameter.\n const httpServer = http.createServer();\n await createApp({\n app: this,\n httpServer,\n errorMiddleware: this.errorMiddleware,\n });\n return this.expressApp;\n }\n\n const httpServer = http.createServer();\n\n await createApp({\n app: this,\n httpServer,\n errorMiddleware: this.errorMiddleware,\n });\n\n httpServer.on(\"request\", this.expressApp);\n const intendedPort = parseInt(process.env.__PORT ?? \"3000\", 10);\n await new Promise<void>((resolve, reject) => {\n httpServer.on(\"error\", (error: Error) => {\n console.error(\"Failed to start server:\", error);\n reject(error);\n });\n httpServer.listen(intendedPort, () => {\n resolve();\n });\n });\n\n const { port } = httpServer.address() as AddressInfo;\n process.send?.({ type: \"skybridge:listening\", port });\n\n // On workerd, bridge the Node http server to a Workers fetch handler.\n // The specifier is held in a variable to sidestep tsc's module resolution\n // (`cloudflare:node` only exists under wrangler/workerd).\n if (\n typeof navigator !== \"undefined\" &&\n navigator.userAgent === \"Cloudflare-Workers\"\n ) {\n const cloudflareNode = \"cloudflare:node\";\n const { httpServerHandler } = await import(cloudflareNode);\n return httpServerHandler({ port });\n }\n\n const shutdown = () => {\n // Drop both handlers so a second signal falls through to Node's default\n // (force-quit on a second Ctrl+C while drain is hanging).\n process.off(\"SIGTERM\", shutdown);\n process.off(\"SIGINT\", shutdown);\n httpServer.close(() => process.exit(0));\n // Force exit if connections don't drain in time so the port is still\n // released promptly (e.g. for nodemon restarts).\n setTimeout(() => process.exit(0), 3000).unref();\n };\n process.on(\"SIGTERM\", shutdown);\n process.on(\"SIGINT\", shutdown);\n return undefined;\n }\n\n private buildServer(): McpServer<TTools, TAuthExtra> {\n const server = new McpServer<Record<never, ToolDef>, TAuthExtra>(\n this.serverInfo,\n this.serverOptions,\n this.skybridgeOptions,\n );\n if (this.resolveResourceMetadataUrl) {\n server.setResourceMetadataUrlResolver(this.resolveResourceMetadataUrl);\n }\n return this.setup(server);\n }\n\n private middlewareEntries(\n server: McpServer<Record<never, ToolDef>, TAuthExtra>,\n ): McpMiddlewareEntry[] {\n return [\n ...(this.monitoringEntry ? [this.monitoringEntry] : []),\n ...server.protocolMiddlewareEntries(),\n ];\n }\n\n private instrumentHandlers(\n server: McpServer<Record<never, ToolDef>, TAuthExtra>,\n ): void {\n const entries = this.middlewareEntries(server);\n const { requestHandlers, notificationHandlers } = getHandlerMaps(\n server.server,\n );\n\n const instrumentMap = (\n map: Map<string, (...args: unknown[]) => Promise<unknown>>,\n isNotification: boolean,\n ) => {\n for (const [method, handler] of map) {\n map.set(\n method,\n buildMiddlewareChain(method, isNotification, handler, entries),\n );\n }\n const originalSet = map.set.bind(map);\n map.set = (\n method: string,\n handler: (...args: unknown[]) => Promise<unknown>,\n ) =>\n originalSet(\n method,\n buildMiddlewareChain(method, isNotification, handler, entries),\n );\n };\n\n instrumentMap(requestHandlers, false);\n instrumentMap(notificationHandlers, true);\n }\n}\n"]}
@@ -0,0 +1,33 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Discovery doc validated as OAuth AS metadata + `jwks_uri`. The SDK's
4
+ * `OAuthMetadataSchema` omits `jwks_uri` (its client never verifies tokens), but
5
+ * we need it for JWKS token verification, so we extend the schema to keep it.
6
+ */
7
+ declare const DiscoverySchema: z.ZodObject<{
8
+ issuer: z.ZodString;
9
+ authorization_endpoint: z.ZodURL;
10
+ token_endpoint: z.ZodURL;
11
+ registration_endpoint: z.ZodOptional<z.ZodURL>;
12
+ scopes_supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
13
+ response_types_supported: z.ZodArray<z.ZodString>;
14
+ response_modes_supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
15
+ grant_types_supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
16
+ token_endpoint_auth_methods_supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
17
+ token_endpoint_auth_signing_alg_values_supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
18
+ service_documentation: z.ZodOptional<z.ZodURL>;
19
+ revocation_endpoint: z.ZodOptional<z.ZodURL>;
20
+ revocation_endpoint_auth_methods_supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
21
+ revocation_endpoint_auth_signing_alg_values_supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
22
+ introspection_endpoint: z.ZodOptional<z.ZodString>;
23
+ introspection_endpoint_auth_methods_supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
24
+ introspection_endpoint_auth_signing_alg_values_supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
25
+ code_challenge_methods_supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
26
+ client_id_metadata_document_supported: z.ZodOptional<z.ZodBoolean>;
27
+ authorization_response_iss_parameter_supported: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
28
+ jwks_uri: z.ZodOptional<z.ZodString>;
29
+ }, z.core.$loose>;
30
+ export type DiscoveredMetadata = z.infer<typeof DiscoverySchema>;
31
+ /** Fetches and validates an authorization server's discovery document. */
32
+ export declare function discoverAuthorizationServer(issuer: string): Promise<DiscoveredMetadata>;
33
+ export {};
@@ -0,0 +1,56 @@
1
+ import { OAuthMetadataSchema } from "@modelcontextprotocol/core";
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,4BAA4B,CAAC;AACjE,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/core\";\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,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,30 @@
1
+ import type { OAuthMetadata } from "@modelcontextprotocol/server";
2
+ import type { ExtraClaims, TokenVerifier } from "../auth.js";
3
+ /**
4
+ * Resource-server OAuth config for `SkybridgeServerOptions.oauth`.
5
+ *
6
+ * `TExtra` comes from the `verifier` and flows on to tool handlers and
7
+ * `mcpMiddleware`, so the claim shape is declared once by whoever checks the
8
+ * token. The branded providers set it from the claims their IdP documents.
9
+ *
10
+ * @typeParam TExtra - Claims the verifier populates in `AuthInfo["extra"]`.
11
+ */
12
+ export type OAuthConfig<TExtra extends ExtraClaims = ExtraClaims> = {
13
+ /**
14
+ * Public URL of this server; sets `resourceServerUrl` and the
15
+ * `resource_metadata` URL. When omitted, it is inferred per request from
16
+ * `x-forwarded-host`/`origin`/`host` headers.
17
+ */
18
+ baseUrl?: string;
19
+ /** AS metadata served at `/.well-known/oauth-authorization-server`. */
20
+ oauthMetadata: OAuthMetadata;
21
+ /** Scopes advertised in protected-resource metadata. */
22
+ scopesSupported?: string[];
23
+ /** Server-wide required-scope floor. */
24
+ requiredScopes?: string[];
25
+ /**
26
+ * Checks each bearer token. Build one with `createJwksVerifier` for a
27
+ * JWT-issuing IdP, or supply your own for opaque tokens.
28
+ */
29
+ verifier: TokenVerifier<TExtra>;
30
+ };
@@ -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/server\";\nimport type { ExtraClaims, TokenVerifier } from \"../auth.js\";\n\n/**\n * Resource-server OAuth config for `SkybridgeServerOptions.oauth`.\n *\n * `TExtra` comes from the `verifier` and flows on to tool handlers and\n * `mcpMiddleware`, so the claim shape is declared once by whoever checks the\n * token. The branded providers set it from the claims their IdP documents.\n *\n * @typeParam TExtra - Claims the verifier populates in `AuthInfo[\"extra\"]`.\n */\nexport type OAuthConfig<TExtra extends ExtraClaims = ExtraClaims> = {\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 /** Scopes advertised in protected-resource metadata. */\n scopesSupported?: string[];\n /** Server-wide required-scope floor. */\n requiredScopes?: string[];\n /**\n * Checks each bearer token. Build one with `createJwksVerifier` for a\n * JWT-issuing IdP, or supply your own for opaque tokens.\n */\n verifier: TokenVerifier<TExtra>;\n};\n"]}
@@ -0,0 +1,40 @@
1
+ import type { ExtraClaims } from "../../auth.js";
2
+ import type { OAuthConfig } from "../index.js";
3
+ import type { RegisteredClaims } from "../verify.js";
4
+ import { type CustomProviderOptions } from "./custom.js";
5
+ /**
6
+ * Claims an Auth0 access token carries for a custom API audience. Auth0 puts no
7
+ * user attributes beyond the id in the token, so there is no `email`: call
8
+ * `/userinfo` for the profile. Custom claims must be namespaced (a bare `email`
9
+ * key is silently dropped), so add them through the type parameter under their
10
+ * full URI.
11
+ *
12
+ * @see https://auth0.com/docs/secure/tokens/access-tokens
13
+ * @see https://auth0.com/docs/secure/tokens/json-web-tokens/create-custom-claims
14
+ */
15
+ export type Auth0Claims = {
16
+ /** Authorized party: the client id the token was issued to. */
17
+ azp?: string;
18
+ /** Only when the API has RBAC and "Add Permissions in the Access Token" on. */
19
+ permissions?: string[];
20
+ /** Organization id, when the tenant uses Organizations. */
21
+ org_id?: string;
22
+ /** Organization name, when the tenant uses Organizations. */
23
+ org_name?: string;
24
+ };
25
+ /**
26
+ * OAuth provider for Auth0. `domain` is the tenant domain (e.g.
27
+ * `acme.us.auth0.com`); `audience` is the API Identifier; `serverUrl` is this
28
+ * server's public URL. Requires DCR enabled on the tenant.
29
+ *
30
+ * Auth0 can't use the client's resource indicator — `audience` must be *in* the
31
+ * authorize request. So it runs as skybridge-as-AS (`serverUrl`, see
32
+ * {@link customProvider}) and bakes `?audience=<id>` into the advertised
33
+ * `authorization_endpoint`. The token's `aud` is the API; `verify.issuer` stays
34
+ * Auth0 (the token's real `iss`).
35
+ */
36
+ export declare function auth0Provider<TCustom extends ExtraClaims = Record<never, never>>(opts: {
37
+ domain: string;
38
+ audience: string;
39
+ serverUrl: string;
40
+ } & Omit<CustomProviderOptions, "issuer" | "audience" | "baseUrl" | "serverUrl">): Promise<OAuthConfig<Auth0Claims & TCustom & RegisteredClaims>>;
@@ -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":"AAGA,OAAO,EAA8B,cAAc,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAuB1C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAGjC,IAGC;IAED,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;IAC3C,MAAM,MAAM,GAAG,MAAM,cAAc,CAAwB;QACzD,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 { ExtraClaims } from \"../../auth.js\";\nimport type { OAuthConfig } 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 an Auth0 access token carries for a custom API audience. Auth0 puts no\n * user attributes beyond the id in the token, so there is no `email`: call\n * `/userinfo` for the profile. Custom claims must be namespaced (a bare `email`\n * key is silently dropped), so add them through the type parameter under their\n * full URI.\n *\n * @see https://auth0.com/docs/secure/tokens/access-tokens\n * @see https://auth0.com/docs/secure/tokens/json-web-tokens/create-custom-claims\n */\nexport type Auth0Claims = {\n /** Authorized party: the client id the token was issued to. */\n azp?: string;\n /** Only when the API has RBAC and \"Add Permissions in the Access Token\" on. */\n permissions?: string[];\n /** Organization id, when the tenant uses Organizations. */\n org_id?: string;\n /** Organization name, when the tenant uses Organizations. */\n org_name?: string;\n};\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 TCustom extends ExtraClaims = Record<never, never>,\n>(\n opts: { domain: string; audience: string; serverUrl: string } & Omit<\n CustomProviderOptions,\n \"issuer\" | \"audience\" | \"baseUrl\" | \"serverUrl\"\n >,\n): Promise<OAuthConfig<Auth0Claims & TCustom & RegisteredClaims>> {\n const { domain, audience, ...rest } = opts;\n const config = await customProvider<Auth0Claims & TCustom>({\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"]}