skybridge 0.0.0-dev.fbea14c → 0.0.0-dev.fc1170f

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