skybridge 0.0.0-dev.faa58ad → 0.0.0-dev.faf20cc

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 (340) hide show
  1. package/README.md +17 -14
  2. package/dist/cli/build-helpers.d.ts +6 -2
  3. package/dist/cli/build-helpers.js +39 -14
  4. package/dist/cli/build-helpers.js.map +1 -1
  5. package/dist/cli/build-helpers.test.js +73 -5
  6. package/dist/cli/build-helpers.test.js.map +1 -1
  7. package/dist/cli/build-steps.d.ts +2 -0
  8. package/dist/cli/build-steps.js +73 -0
  9. package/dist/cli/build-steps.js.map +1 -0
  10. package/dist/cli/build-steps.test.js +52 -0
  11. package/dist/cli/build-steps.test.js.map +1 -0
  12. package/dist/cli/detect-port.js +0 -1
  13. package/dist/cli/detect-port.js.map +1 -1
  14. package/dist/cli/header.d.ts +1 -1
  15. package/dist/cli/resolve-skybridge-config.d.ts +5 -0
  16. package/dist/cli/{resolve-views-dir.js → resolve-skybridge-config.js} +4 -3
  17. package/dist/cli/resolve-skybridge-config.js.map +1 -0
  18. package/dist/cli/run-plain.d.ts +18 -0
  19. package/dist/cli/run-plain.js +90 -0
  20. package/dist/cli/run-plain.js.map +1 -0
  21. package/dist/cli/telemetry.js +2 -0
  22. package/dist/cli/telemetry.js.map +1 -1
  23. package/dist/cli/use-execute-steps.d.ts +1 -1
  24. package/dist/cli/use-nodemon.d.ts +25 -1
  25. package/dist/cli/use-nodemon.js +96 -63
  26. package/dist/cli/use-nodemon.js.map +1 -1
  27. package/dist/cli/use-typescript-check.d.ts +8 -2
  28. package/dist/cli/use-typescript-check.js +70 -67
  29. package/dist/cli/use-typescript-check.js.map +1 -1
  30. package/dist/commands/build.d.ts +0 -2
  31. package/dist/commands/build.js +2 -61
  32. package/dist/commands/build.js.map +1 -1
  33. package/dist/commands/dev.d.ts +1 -0
  34. package/dist/commands/dev.js +38 -6
  35. package/dist/commands/dev.js.map +1 -1
  36. package/dist/commands/start.js +2 -2
  37. package/dist/commands/start.js.map +1 -1
  38. package/dist/context-warnings.d.ts +5 -0
  39. package/dist/context-warnings.js +31 -0
  40. package/dist/context-warnings.js.map +1 -0
  41. package/dist/context-warnings.test.js +28 -0
  42. package/dist/context-warnings.test.js.map +1 -0
  43. package/dist/server/app.d.ts +194 -0
  44. package/dist/server/app.js +266 -0
  45. package/dist/server/app.js.map +1 -0
  46. package/dist/server/app.test.js +97 -0
  47. package/dist/server/app.test.js.map +1 -0
  48. package/dist/server/auth/discovery.d.ts +2 -1
  49. package/dist/server/auth/discovery.js +2 -2
  50. package/dist/server/auth/discovery.js.map +1 -1
  51. package/dist/server/auth/index.d.ts +35 -5
  52. package/dist/server/auth/index.js.map +1 -1
  53. package/dist/server/auth/providers/auth0.d.ts +25 -3
  54. package/dist/server/auth/providers/auth0.js +13 -8
  55. package/dist/server/auth/providers/auth0.js.map +1 -1
  56. package/dist/server/auth/providers/auth0.test.js +5 -3
  57. package/dist/server/auth/providers/auth0.test.js.map +1 -1
  58. package/dist/server/auth/providers/authplane.d.ts +49 -0
  59. package/dist/server/auth/providers/authplane.js +57 -0
  60. package/dist/server/auth/providers/authplane.js.map +1 -0
  61. package/dist/server/auth/providers/authplane.test.js +143 -0
  62. package/dist/server/auth/providers/authplane.test.js.map +1 -0
  63. package/dist/server/auth/providers/clerk.d.ts +40 -3
  64. package/dist/server/auth/providers/clerk.js +4 -1
  65. package/dist/server/auth/providers/clerk.js.map +1 -1
  66. package/dist/server/auth/providers/clerk.test.js +5 -3
  67. package/dist/server/auth/providers/clerk.test.js.map +1 -1
  68. package/dist/server/auth/providers/custom.d.ts +24 -6
  69. package/dist/server/auth/providers/custom.js +33 -11
  70. package/dist/server/auth/providers/custom.js.map +1 -1
  71. package/dist/server/auth/providers/custom.test.js +71 -17
  72. package/dist/server/auth/providers/custom.test.js.map +1 -1
  73. package/dist/server/auth/providers/descope.d.ts +32 -3
  74. package/dist/server/auth/providers/descope.js +12 -2
  75. package/dist/server/auth/providers/descope.js.map +1 -1
  76. package/dist/server/auth/providers/descope.test.js +42 -18
  77. package/dist/server/auth/providers/descope.test.js.map +1 -1
  78. package/dist/server/auth/providers/stytch.d.ts +16 -3
  79. package/dist/server/auth/providers/stytch.js +4 -1
  80. package/dist/server/auth/providers/stytch.js.map +1 -1
  81. package/dist/server/auth/providers/verify-spy.d.ts +12 -0
  82. package/dist/server/auth/providers/verify-spy.js +21 -0
  83. package/dist/server/auth/providers/verify-spy.js.map +1 -0
  84. package/dist/server/auth/providers/workos.d.ts +28 -5
  85. package/dist/server/auth/providers/workos.js +6 -2
  86. package/dist/server/auth/providers/workos.js.map +1 -1
  87. package/dist/server/auth/security-schemes.d.ts +23 -0
  88. package/dist/server/auth/security-schemes.js +64 -0
  89. package/dist/server/auth/security-schemes.js.map +1 -0
  90. package/dist/server/auth/security-schemes.test.d.ts +1 -0
  91. package/dist/server/auth/security-schemes.test.js +99 -0
  92. package/dist/server/auth/security-schemes.test.js.map +1 -0
  93. package/dist/server/auth/setup.d.ts +3 -1
  94. package/dist/server/auth/setup.js +66 -26
  95. package/dist/server/auth/setup.js.map +1 -1
  96. package/dist/server/auth/setup.test.js +349 -20
  97. package/dist/server/auth/setup.test.js.map +1 -1
  98. package/dist/server/auth/verify.d.ts +42 -6
  99. package/dist/server/auth/verify.js +27 -3
  100. package/dist/server/auth/verify.js.map +1 -1
  101. package/dist/server/auth/verify.test.js +65 -0
  102. package/dist/server/auth/verify.test.js.map +1 -1
  103. package/dist/server/auth-extra.test-d.js +130 -0
  104. package/dist/server/auth-extra.test-d.js.map +1 -0
  105. package/dist/server/auth.d.ts +43 -6
  106. package/dist/server/auth.js +4 -5
  107. package/dist/server/auth.js.map +1 -1
  108. package/dist/server/build-manifest.test.js +7 -6
  109. package/dist/server/build-manifest.test.js.map +1 -1
  110. package/dist/server/content-helpers.d.ts +1 -1
  111. package/dist/server/content-helpers.js.map +1 -1
  112. package/dist/server/express.d.ts +23 -3
  113. package/dist/server/express.js +38 -32
  114. package/dist/server/express.js.map +1 -1
  115. package/dist/server/express.test.js +220 -103
  116. package/dist/server/express.test.js.map +1 -1
  117. package/dist/server/file-ref.d.ts +1 -1
  118. package/dist/server/file-ref.js +1 -1
  119. package/dist/server/file-ref.js.map +1 -1
  120. package/dist/server/host.d.ts +2 -0
  121. package/dist/server/host.js +11 -0
  122. package/dist/server/host.js.map +1 -0
  123. package/dist/server/host.test.d.ts +1 -0
  124. package/dist/server/host.test.js +13 -0
  125. package/dist/server/host.test.js.map +1 -0
  126. package/dist/server/index.d.ts +16 -9
  127. package/dist/server/index.js +9 -3
  128. package/dist/server/index.js.map +1 -1
  129. package/dist/server/mcp-errors.test.d.ts +1 -0
  130. package/dist/server/mcp-errors.test.js +41 -0
  131. package/dist/server/mcp-errors.test.js.map +1 -0
  132. package/dist/server/middleware.d.ts +35 -10
  133. package/dist/server/middleware.js +33 -0
  134. package/dist/server/middleware.js.map +1 -1
  135. package/dist/server/middleware.test-d.js +1 -1
  136. package/dist/server/middleware.test-d.js.map +1 -1
  137. package/dist/server/middleware.test.js +242 -134
  138. package/dist/server/middleware.test.js.map +1 -1
  139. package/dist/server/register-tool.test.d.ts +1 -0
  140. package/dist/server/register-tool.test.js +110 -0
  141. package/dist/server/register-tool.test.js.map +1 -0
  142. package/dist/server/server.d.ts +242 -128
  143. package/dist/server/server.js +263 -274
  144. package/dist/server/server.js.map +1 -1
  145. package/dist/server/skills-integration.test.d.ts +1 -0
  146. package/dist/server/skills-integration.test.js +86 -0
  147. package/dist/server/skills-integration.test.js.map +1 -0
  148. package/dist/server/skills.d.ts +27 -0
  149. package/dist/server/skills.js +188 -0
  150. package/dist/server/skills.js.map +1 -0
  151. package/dist/server/skills.test.d.ts +1 -0
  152. package/dist/server/skills.test.js +243 -0
  153. package/dist/server/skills.test.js.map +1 -0
  154. package/dist/server/templateHelper.d.ts +0 -3
  155. package/dist/server/templateHelper.js.map +1 -1
  156. package/dist/server/templates.generated.js +4 -8
  157. package/dist/server/templates.generated.js.map +1 -1
  158. package/dist/server/tool-extra.test.d.ts +1 -0
  159. package/dist/server/tool-extra.test.js +88 -0
  160. package/dist/server/tool-extra.test.js.map +1 -0
  161. package/dist/server/view-resource-resolution.test.js +18 -13
  162. package/dist/server/view-resource-resolution.test.js.map +1 -1
  163. package/dist/server/viewsDevServer.js +4 -1
  164. package/dist/server/viewsDevServer.js.map +1 -1
  165. package/dist/test/utils.d.ts +9 -11
  166. package/dist/test/utils.js +220 -201
  167. package/dist/test/utils.js.map +1 -1
  168. package/dist/test/view.test.js +44 -40
  169. package/dist/test/view.test.js.map +1 -1
  170. package/dist/views/index.d.ts +10 -0
  171. package/dist/views/index.js +3 -0
  172. package/dist/views/index.js.map +1 -0
  173. package/dist/{web/plugin → views}/scan-views.js +1 -1
  174. package/dist/views/scan-views.js.map +1 -0
  175. package/dist/views/scan-views.test.d.ts +1 -0
  176. package/dist/views/scan-views.test.js.map +1 -0
  177. package/dist/views/validate-view.d.ts +1 -0
  178. package/dist/{web/plugin → views}/validate-view.js +2 -2
  179. package/dist/views/validate-view.js.map +1 -0
  180. package/dist/views/validate-view.test.d.ts +1 -0
  181. package/dist/views/validate-view.test.js.map +1 -0
  182. package/dist/web/bridges/adaptor.d.ts +8 -3
  183. package/dist/web/bridges/adaptor.js +100 -16
  184. package/dist/web/bridges/adaptor.js.map +1 -1
  185. package/dist/web/bridges/adaptor.test.js +78 -11
  186. package/dist/web/bridges/adaptor.test.js.map +1 -1
  187. package/dist/web/bridges/apps-sdk/bridge.js +1 -1
  188. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
  189. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.d.ts +4 -4
  190. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js +4 -4
  191. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
  192. package/dist/web/bridges/get-adaptor.test.js +2 -2
  193. package/dist/web/bridges/get-adaptor.test.js.map +1 -1
  194. package/dist/web/bridges/mcp-app/bridge.d.ts +1 -1
  195. package/dist/web/bridges/mcp-app/bridge.js +15 -6
  196. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  197. package/dist/web/bridges/mcp-app/bridge.test.js +2 -2
  198. package/dist/web/bridges/mcp-app/bridge.test.js.map +1 -1
  199. package/dist/web/bridges/mcp-app/types.d.ts +19 -2
  200. package/dist/web/bridges/mcp-app/types.js.map +1 -1
  201. package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +2 -2
  202. package/dist/web/bridges/mcp-app/use-mcp-app-context.js +1 -1
  203. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
  204. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js +1 -1
  205. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -1
  206. package/dist/web/bridges/mcp-app/view-tools.test.js +1 -1
  207. package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -1
  208. package/dist/web/bridges/types.d.ts +47 -16
  209. package/dist/web/bridges/types.js +1 -1
  210. package/dist/web/bridges/types.js.map +1 -1
  211. package/dist/web/components/modal-provider.d.ts +1 -1
  212. package/dist/web/create-store.test.js +2 -2
  213. package/dist/web/create-store.test.js.map +1 -1
  214. package/dist/web/data-llm.d.ts +1 -1
  215. package/dist/web/data-llm.js +3 -2
  216. package/dist/web/data-llm.js.map +1 -1
  217. package/dist/web/data-llm.test.js +17 -2
  218. package/dist/web/data-llm.test.js.map +1 -1
  219. package/dist/web/generate-helpers.d.ts +23 -19
  220. package/dist/web/generate-helpers.js +21 -17
  221. package/dist/web/generate-helpers.js.map +1 -1
  222. package/dist/web/generate-helpers.test-d.js +3 -3
  223. package/dist/web/generate-helpers.test-d.js.map +1 -1
  224. package/dist/web/generate-helpers.test.js +2 -2
  225. package/dist/web/generate-helpers.test.js.map +1 -1
  226. package/dist/web/hooks/index.d.ts +5 -1
  227. package/dist/web/hooks/index.js +5 -1
  228. package/dist/web/hooks/index.js.map +1 -1
  229. package/dist/web/hooks/openai-extensions.test.d.ts +1 -0
  230. package/dist/web/hooks/openai-extensions.test.js +107 -0
  231. package/dist/web/hooks/openai-extensions.test.js.map +1 -0
  232. package/dist/web/hooks/test/utils.d.ts +2 -1
  233. package/dist/web/hooks/test/utils.js +1 -1
  234. package/dist/web/hooks/test/utils.js.map +1 -1
  235. package/dist/web/hooks/use-call-tool.d.ts +2 -2
  236. package/dist/web/hooks/use-call-tool.js +15 -10
  237. package/dist/web/hooks/use-call-tool.js.map +1 -1
  238. package/dist/web/hooks/use-call-tool.test.js +15 -4
  239. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  240. package/dist/web/hooks/use-deep-link.d.ts +22 -0
  241. package/dist/web/hooks/use-deep-link.js +34 -0
  242. package/dist/web/hooks/use-deep-link.js.map +1 -0
  243. package/dist/web/hooks/use-display-mode.d.ts +1 -1
  244. package/dist/web/hooks/use-display-mode.js +1 -1
  245. package/dist/web/hooks/use-display-mode.js.map +1 -1
  246. package/dist/web/hooks/use-display-mode.test.js +1 -1
  247. package/dist/web/hooks/use-display-mode.test.js.map +1 -1
  248. package/dist/web/hooks/use-download.d.ts +1 -3
  249. package/dist/web/hooks/use-download.js +1 -2
  250. package/dist/web/hooks/use-download.js.map +1 -1
  251. package/dist/web/hooks/use-download.test.js +7 -7
  252. package/dist/web/hooks/use-download.test.js.map +1 -1
  253. package/dist/web/hooks/use-files.test.js +1 -1
  254. package/dist/web/hooks/use-files.test.js.map +1 -1
  255. package/dist/web/hooks/use-host.d.ts +44 -0
  256. package/dist/web/hooks/use-host.js +45 -0
  257. package/dist/web/hooks/use-host.js.map +1 -0
  258. package/dist/web/hooks/use-host.test.d.ts +1 -0
  259. package/dist/web/hooks/use-host.test.js +59 -0
  260. package/dist/web/hooks/use-host.test.js.map +1 -0
  261. package/dist/web/hooks/use-model-context.d.ts +44 -0
  262. package/dist/web/hooks/use-model-context.js +63 -0
  263. package/dist/web/hooks/use-model-context.js.map +1 -0
  264. package/dist/web/hooks/use-open-external.test.js +2 -2
  265. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  266. package/dist/web/hooks/use-open-file.d.ts +20 -0
  267. package/dist/web/hooks/use-open-file.js +25 -0
  268. package/dist/web/hooks/use-open-file.js.map +1 -0
  269. package/dist/web/hooks/use-register-view-tool.d.ts +3 -4
  270. package/dist/web/hooks/use-register-view-tool.js +1 -1
  271. package/dist/web/hooks/use-register-view-tool.js.map +1 -1
  272. package/dist/web/hooks/use-request-close.test.js +1 -1
  273. package/dist/web/hooks/use-request-close.test.js.map +1 -1
  274. package/dist/web/hooks/use-request-modal.test.js +1 -1
  275. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  276. package/dist/web/hooks/use-request-size.d.ts +1 -1
  277. package/dist/web/hooks/use-request-size.js +1 -1
  278. package/dist/web/hooks/use-request-size.js.map +1 -1
  279. package/dist/web/hooks/use-request-size.test.js +1 -1
  280. package/dist/web/hooks/use-request-size.test.js.map +1 -1
  281. package/dist/web/hooks/use-send-follow-up-message.d.ts +8 -2
  282. package/dist/web/hooks/use-send-follow-up-message.js +6 -0
  283. package/dist/web/hooks/use-send-follow-up-message.js.map +1 -1
  284. package/dist/web/hooks/use-set-open-in-app-url.test.js +2 -8
  285. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  286. package/dist/web/hooks/use-tool-info.d.ts +1 -20
  287. package/dist/web/hooks/use-tool-info.js +0 -1
  288. package/dist/web/hooks/use-tool-info.js.map +1 -1
  289. package/dist/web/hooks/use-tool-info.test-d.js +0 -5
  290. package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
  291. package/dist/web/hooks/use-tool-info.test.js +2 -7
  292. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  293. package/dist/web/hooks/use-user.d.ts +3 -2
  294. package/dist/web/hooks/use-user.js +4 -3
  295. package/dist/web/hooks/use-user.js.map +1 -1
  296. package/dist/web/hooks/use-user.test.js +5 -3
  297. package/dist/web/hooks/use-user.test.js.map +1 -1
  298. package/dist/web/hooks/use-view-state.js +19 -9
  299. package/dist/web/hooks/use-view-state.js.map +1 -1
  300. package/dist/web/hooks/use-view-state.test.js +33 -2
  301. package/dist/web/hooks/use-view-state.test.js.map +1 -1
  302. package/dist/web/hooks/use-viewport.d.ts +20 -0
  303. package/dist/web/hooks/use-viewport.js +21 -0
  304. package/dist/web/hooks/use-viewport.js.map +1 -0
  305. package/dist/web/hooks/use-viewport.test.d.ts +1 -0
  306. package/dist/web/hooks/{use-layout.test.js → use-viewport.test.js} +12 -32
  307. package/dist/web/hooks/use-viewport.test.js.map +1 -0
  308. package/dist/web/mount-view.d.ts +3 -2
  309. package/dist/web/mount-view.js +5 -8
  310. package/dist/web/mount-view.js.map +1 -1
  311. package/package.json +32 -29
  312. package/dist/cli/resolve-views-dir.d.ts +0 -1
  313. package/dist/cli/resolve-views-dir.js.map +0 -1
  314. package/dist/web/hooks/use-layout.d.ts +0 -24
  315. package/dist/web/hooks/use-layout.js +0 -25
  316. package/dist/web/hooks/use-layout.js.map +0 -1
  317. package/dist/web/hooks/use-layout.test.js.map +0 -1
  318. package/dist/web/plugin/data-llm.test.js +0 -81
  319. package/dist/web/plugin/data-llm.test.js.map +0 -1
  320. package/dist/web/plugin/plugin.d.ts +0 -33
  321. package/dist/web/plugin/plugin.js +0 -189
  322. package/dist/web/plugin/plugin.js.map +0 -1
  323. package/dist/web/plugin/scan-views.js.map +0 -1
  324. package/dist/web/plugin/scan-views.test.js.map +0 -1
  325. package/dist/web/plugin/transform-data-llm.d.ts +0 -12
  326. package/dist/web/plugin/transform-data-llm.js +0 -96
  327. package/dist/web/plugin/transform-data-llm.js.map +0 -1
  328. package/dist/web/plugin/transform-data-llm.test.js +0 -81
  329. package/dist/web/plugin/transform-data-llm.test.js.map +0 -1
  330. package/dist/web/plugin/validate-view.d.ts +0 -1
  331. package/dist/web/plugin/validate-view.js.map +0 -1
  332. package/dist/web/plugin/validate-view.test.js.map +0 -1
  333. /package/dist/{web/hooks/use-layout.test.d.ts → cli/build-steps.test.d.ts} +0 -0
  334. /package/dist/{web/plugin/data-llm.test.d.ts → context-warnings.test.d.ts} +0 -0
  335. /package/dist/{web/plugin/scan-views.test.d.ts → server/app.test.d.ts} +0 -0
  336. /package/dist/{web/plugin/validate-view.test.d.ts → server/auth/providers/authplane.test.d.ts} +0 -0
  337. /package/dist/{web/plugin/transform-data-llm.test.d.ts → server/auth-extra.test-d.d.ts} +0 -0
  338. /package/dist/{web/plugin → views}/scan-views.d.ts +0 -0
  339. /package/dist/{web/plugin → views}/scan-views.test.js +0 -0
  340. /package/dist/{web/plugin → views}/validate-view.test.js +0 -0
@@ -1,14 +1,21 @@
1
- import { getOAuthProtectedResourceMetadataUrl, mcpAuthMetadataRouter, } from "@modelcontextprotocol/sdk/server/auth/router.js";
1
+ import { getOAuthProtectedResourceMetadataUrl, mcpAuthMetadataRouter, } from "@modelcontextprotocol/express";
2
2
  import cors from "cors";
3
- import { requireBearerAuth } from "../auth.js";
3
+ import { optionalBearerAuth, requireBearerAuth } from "../auth.js";
4
4
  import { resolveServerOrigin } from "../requestOrigin.js";
5
- import { createJwksVerifier } from "./verify.js";
5
+ import { clientPrefersInBandChallenge, evaluateSecuritySchemes, httpStatusForFailure, inBandChallengeResult, securitySchemesAllowAnonymous, wwwAuthenticateHeader, } from "./security-schemes.js";
6
6
  /** Mounts the well-known OAuth metadata and bearer auth on `/mcp`. */
7
- export function setupOAuth(app, config) {
8
- if (!config.verify?.issuer) {
9
- throw new Error("oauth.verify requires an `issuer`");
7
+ export function setupOAuth(app, config, schemesByTool) {
8
+ if (!config.verifier) {
9
+ throw new Error("oauth requires a `verifier`");
10
10
  }
11
- const verifier = createJwksVerifier(config.verify);
11
+ const acceptsAnonymous = () => [...schemesByTool.values()].some(securitySchemesAllowAnonymous);
12
+ const verifier = config.verifier;
13
+ const bearer = (options) => {
14
+ const required = requireBearerAuth(options);
15
+ const optional = optionalBearerAuth(options);
16
+ return (req, res, next) => (acceptsAnonymous() ? optional : required)(req, res, next);
17
+ };
18
+ let resourceMetadataUrl;
12
19
  // baseUrl known at boot: bake the resource URLs once, no Host-header trust.
13
20
  if (config.baseUrl !== undefined) {
14
21
  let baseUrl;
@@ -23,29 +30,62 @@ export function setupOAuth(app, config) {
23
30
  resourceServerUrl: baseUrl,
24
31
  scopesSupported: config.scopesSupported,
25
32
  }));
26
- app.use("/mcp", requireBearerAuth({
27
- verifier,
28
- requiredScopes: config.requiredScopes,
29
- resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(baseUrl),
30
- }));
31
- return;
33
+ const url = getOAuthProtectedResourceMetadataUrl(baseUrl);
34
+ resourceMetadataUrl = () => url;
32
35
  }
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,
36
+ else {
37
+ // No baseUrl: resolve the resource origin per request from forwarded headers
38
+ // (same precedence the framework uses for view serverUrl). Origin headers are
39
+ // pathless, so the PRM path stays at root, matching the SDK's static layout.
40
+ const resolveOrigin = (getHeader) => new URL(resolveServerOrigin(getHeader));
41
+ app.use("/.well-known/oauth-authorization-server", cors(), (_req, res) => void res.json(config.oauthMetadata));
42
+ app.use("/.well-known/oauth-protected-resource", cors(), (req, res) => {
43
+ res.json({
44
+ resource: resolveOrigin((key) => req.get(key)).href,
45
+ authorization_servers: [config.oauthMetadata.issuer],
46
+ scopes_supported: config.scopesSupported,
47
+ });
43
48
  });
44
- });
45
- app.use("/mcp", (req, res, next) => requireBearerAuth({
49
+ resourceMetadataUrl = (getHeader) => getOAuthProtectedResourceMetadataUrl(resolveOrigin(getHeader));
50
+ }
51
+ app.use("/mcp", (req, res, next) => bearer({
46
52
  verifier,
47
53
  requiredScopes: config.requiredScopes,
48
- resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(resolveOrigin(req)),
54
+ resourceMetadataUrl: resourceMetadataUrl((key) => req.get(key)),
49
55
  })(req, res, next));
56
+ app.use("/mcp", (req, res, next) => {
57
+ const body = req.body;
58
+ const calls = Array.isArray(body) ? body : [body];
59
+ const auth = req.auth;
60
+ let failure;
61
+ for (const call of calls) {
62
+ const tool = call?.params?.name;
63
+ if (call?.method === "tools/call" && tool && schemesByTool.has(tool)) {
64
+ failure = evaluateSecuritySchemes(schemesByTool.get(tool), auth);
65
+ if (failure) {
66
+ break;
67
+ }
68
+ }
69
+ }
70
+ if (!failure) {
71
+ return next();
72
+ }
73
+ const url = resourceMetadataUrl((key) => req.get(key));
74
+ if (!Array.isArray(body) &&
75
+ clientPrefersInBandChallenge(req.get("user-agent"))) {
76
+ res.json({
77
+ jsonrpc: "2.0",
78
+ id: body?.id ?? null,
79
+ result: inBandChallengeResult(failure, url),
80
+ });
81
+ return;
82
+ }
83
+ res.set("WWW-Authenticate", wwwAuthenticateHeader(failure, url));
84
+ res.status(httpStatusForFailure(failure)).json({
85
+ error: failure.error,
86
+ error_description: failure.description,
87
+ });
88
+ });
89
+ return resourceMetadataUrl;
50
90
  }
51
91
  //# sourceMappingURL=setup.js.map
@@ -1 +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,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvD,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) {\n throw new Error(\"oauth.verify requires an `issuer`\");\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"]}
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../src/server/auth/setup.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type BearerAuthMiddlewareOptions,\n getOAuthProtectedResourceMetadataUrl,\n mcpAuthMetadataRouter,\n} from \"@modelcontextprotocol/express\";\nimport cors from \"cors\";\nimport type { Express, Request, RequestHandler } from \"express\";\nimport type { AuthInfo } from \"../auth.js\";\nimport { optionalBearerAuth, requireBearerAuth } from \"../auth.js\";\nimport { resolveServerOrigin } from \"../requestOrigin.js\";\nimport type { SecurityScheme } from \"../server.js\";\nimport type { OAuthConfig } from \"./index.js\";\nimport {\n clientPrefersInBandChallenge,\n evaluateSecuritySchemes,\n httpStatusForFailure,\n inBandChallengeResult,\n type SchemeFailure,\n securitySchemesAllowAnonymous,\n wwwAuthenticateHeader,\n} from \"./security-schemes.js\";\n\nexport type ResourceMetadataUrlResolver = (\n getHeader: (key: string) => string | undefined,\n) => string;\n\n/** Mounts the well-known OAuth metadata and bearer auth on `/mcp`. */\nexport function setupOAuth(\n app: Express,\n config: OAuthConfig<Record<string, unknown>>,\n schemesByTool: ReadonlyMap<string, SecurityScheme[] | undefined>,\n): ResourceMetadataUrlResolver {\n if (!config.verifier) {\n throw new Error(\"oauth requires a `verifier`\");\n }\n\n const acceptsAnonymous = () =>\n [...schemesByTool.values()].some(securitySchemesAllowAnonymous);\n const verifier = config.verifier;\n const bearer = (options: BearerAuthMiddlewareOptions): RequestHandler => {\n const required = requireBearerAuth(options);\n const optional = optionalBearerAuth(options);\n return (req, res, next) =>\n (acceptsAnonymous() ? optional : required)(req, res, next);\n };\n\n let resourceMetadataUrl: ResourceMetadataUrlResolver;\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 const url = getOAuthProtectedResourceMetadataUrl(baseUrl);\n resourceMetadataUrl = () => url;\n } else {\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 = (getHeader: (key: string) => string | undefined) =>\n new URL(resolveServerOrigin(getHeader));\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((key) => req.get(key)).href,\n authorization_servers: [config.oauthMetadata.issuer],\n scopes_supported: config.scopesSupported,\n });\n });\n resourceMetadataUrl = (getHeader) =>\n getOAuthProtectedResourceMetadataUrl(resolveOrigin(getHeader));\n }\n\n app.use(\"/mcp\", (req, res, next) =>\n bearer({\n verifier,\n requiredScopes: config.requiredScopes,\n resourceMetadataUrl: resourceMetadataUrl((key) => req.get(key)),\n })(req, res, next),\n );\n\n app.use(\"/mcp\", (req, res, next) => {\n type Call = {\n id?: string | number | null;\n method?: string;\n params?: { name?: string };\n };\n const body = req.body as Call | Call[] | undefined;\n const calls = Array.isArray(body) ? body : [body];\n const auth = (req as Request & { auth?: AuthInfo }).auth;\n let failure: SchemeFailure | undefined;\n for (const call of calls) {\n const tool = call?.params?.name;\n if (call?.method === \"tools/call\" && tool && schemesByTool.has(tool)) {\n failure = evaluateSecuritySchemes(schemesByTool.get(tool), auth);\n if (failure) {\n break;\n }\n }\n }\n if (!failure) {\n return next();\n }\n const url = resourceMetadataUrl((key) => req.get(key));\n if (\n !Array.isArray(body) &&\n clientPrefersInBandChallenge(req.get(\"user-agent\"))\n ) {\n res.json({\n jsonrpc: \"2.0\",\n id: body?.id ?? null,\n result: inBandChallengeResult(failure, url),\n });\n return;\n }\n res.set(\"WWW-Authenticate\", wwwAuthenticateHeader(failure, url));\n res.status(httpStatusForFailure(failure)).json({\n error: failure.error,\n error_description: failure.description,\n });\n });\n\n return resourceMetadataUrl;\n}\n"]}
@@ -1,10 +1,11 @@
1
1
  // @vitest-environment node
2
2
  import http from "node:http";
3
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
3
+ import { Client, StreamableHTTPClientTransport, } from "@modelcontextprotocol/client";
5
4
  import * as jose from "jose";
6
5
  import { afterEach, describe, expect, it, vi } from "vitest";
6
+ import { Skybridge } from "../app.js";
7
7
  import { McpServer } from "../server.js";
8
+ import { createJwksVerifier } from "./verify.js";
8
9
  vi.mock("@skybridge/devtools", () => ({
9
10
  devtoolsStaticServer: () => ((_req, _res, next) => next()),
10
11
  }));
@@ -36,10 +37,10 @@ async function startJwks() {
36
37
  const port = server.address().port;
37
38
  return { privateKey, jwksUri: `http://localhost:${port}/jwks` };
38
39
  }
39
- function signToken(key) {
40
+ function signToken(key, scope = "openid email") {
40
41
  return new jose.SignJWT({
41
42
  client_id: "client-1",
42
- scope: "openid email",
43
+ scope,
43
44
  sub: "user-1",
44
45
  })
45
46
  .setProtectedHeader({ alg: "RS256", kid: "test-key" })
@@ -50,7 +51,9 @@ function signToken(key) {
50
51
  }
51
52
  async function bootServer(jwksUri, { baseUrl = "https://app.example.test" } = {}) {
52
53
  const { createApp } = await import("../express.js");
53
- const server = new McpServer({ name: "auth-test", version: "0.0.0" }, { capabilities: {} }, {
54
+ const app = new Skybridge({
55
+ name: "auth-test",
56
+ version: "0.0.0",
54
57
  oauth: {
55
58
  ...(baseUrl === null ? {} : { baseUrl }),
56
59
  oauthMetadata: {
@@ -59,21 +62,30 @@ async function bootServer(jwksUri, { baseUrl = "https://app.example.test" } = {}
59
62
  token_endpoint: `${ISSUER}/token`,
60
63
  response_types_supported: ["code"],
61
64
  },
62
- verify: { issuer: ISSUER, audience: AUDIENCE, jwksUri },
65
+ verifier: createJwksVerifier({
66
+ issuer: ISSUER,
67
+ audience: AUDIENCE,
68
+ jwksUri,
69
+ }),
63
70
  scopesSupported: ["openid", "email"],
64
71
  requiredScopes: ["openid"],
65
72
  },
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
- }));
73
+ handler: (server) => server.registerTool({
74
+ name: "whoami",
75
+ description: "Returns the caller identity.",
76
+ inputSchema: {},
77
+ }, (_args, extra) => ({
78
+ structuredContent: {
79
+ clientId: extra.http?.authInfo?.clientId ?? null,
80
+ },
81
+ content: [
82
+ { type: "text", text: extra.http?.authInfo?.clientId ?? "anon" },
83
+ ],
84
+ })),
85
+ });
74
86
  const httpServer = http.createServer();
75
- await createApp({ mcpServer: server, httpServer });
76
- const listening = http.createServer(server.express);
87
+ await createApp({ app, httpServer });
88
+ const listening = http.createServer(app.express);
77
89
  await new Promise((resolve) => listening.listen(0, resolve));
78
90
  appServer = listening;
79
91
  const port = listening.address().port;
@@ -165,6 +177,316 @@ describe("baseUrl inferred from headers", () => {
165
177
  expect(res.headers.get("www-authenticate")).toMatch(/resource_metadata="https:\/\/infer\.example\.test\//);
166
178
  });
167
179
  });
180
+ async function bootMixedServer(jwksUri) {
181
+ const { createApp } = await import("../express.js");
182
+ const app = new Skybridge({
183
+ name: "mixed-auth-test",
184
+ version: "0.0.0",
185
+ oauth: {
186
+ baseUrl: "https://app.example.test",
187
+ oauthMetadata: {
188
+ issuer: ISSUER,
189
+ authorization_endpoint: `${ISSUER}/authorize`,
190
+ token_endpoint: `${ISSUER}/token`,
191
+ response_types_supported: ["code"],
192
+ },
193
+ verifier: createJwksVerifier({
194
+ issuer: ISSUER,
195
+ audience: AUDIENCE,
196
+ jwksUri,
197
+ }),
198
+ },
199
+ handler: (server) => server
200
+ .registerTool({
201
+ name: "public-whoami",
202
+ description: "Public.",
203
+ inputSchema: {},
204
+ auth: { allowsAnonymous: true },
205
+ }, (_args, extra) => ({
206
+ content: [
207
+ { type: "text", text: extra.http?.authInfo?.clientId ?? "anon" },
208
+ ],
209
+ }))
210
+ .registerTool({
211
+ name: "private-whoami",
212
+ description: "Private.",
213
+ inputSchema: {},
214
+ auth: {},
215
+ }, (_args, extra) => ({
216
+ content: [
217
+ { type: "text", text: extra.http?.authInfo?.clientId ?? "anon" },
218
+ ],
219
+ }))
220
+ .registerTool({
221
+ name: "undeclared-whoami",
222
+ description: "Registered without auth or securitySchemes.",
223
+ inputSchema: {},
224
+ }, (_args, extra) => ({
225
+ content: [
226
+ { type: "text", text: extra.http?.authInfo?.clientId ?? "anon" },
227
+ ],
228
+ })),
229
+ });
230
+ const httpServer = http.createServer();
231
+ await createApp({ app, httpServer });
232
+ const listening = http.createServer(app.express);
233
+ await new Promise((resolve) => listening.listen(0, resolve));
234
+ appServer = listening;
235
+ const port = listening.address().port;
236
+ return `http://localhost:${port}`;
237
+ }
238
+ describe("mixed-auth door", () => {
239
+ it("lets an anonymous caller initialize and run a noauth tool", async () => {
240
+ const { jwksUri } = await startJwks();
241
+ const base = await bootMixedServer(jwksUri);
242
+ const client = new Client({ name: "test-client", version: "0.0.0" });
243
+ await client.connect(new StreamableHTTPClientTransport(new URL(`${base}/mcp`)));
244
+ const result = (await client.callTool({
245
+ name: "public-whoami",
246
+ arguments: {},
247
+ }));
248
+ expect(result.content[0]?.text).toBe("anon");
249
+ await client.close();
250
+ });
251
+ it("exposes securitySchemes at the top level of tools/list (not only _meta)", async () => {
252
+ const { jwksUri } = await startJwks();
253
+ const base = await bootMixedServer(jwksUri);
254
+ const headers = {
255
+ "Content-Type": "application/json",
256
+ Accept: "application/json, text/event-stream",
257
+ };
258
+ await fetch(`${base}/mcp`, {
259
+ method: "POST",
260
+ headers,
261
+ body: JSON.stringify({
262
+ jsonrpc: "2.0",
263
+ id: 1,
264
+ method: "initialize",
265
+ params: {
266
+ protocolVersion: "2025-06-18",
267
+ capabilities: {},
268
+ clientInfo: { name: "c", version: "0" },
269
+ },
270
+ }),
271
+ });
272
+ const res = await fetch(`${base}/mcp`, {
273
+ method: "POST",
274
+ headers,
275
+ body: JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list" }),
276
+ });
277
+ const text = await res.text();
278
+ const json = JSON.parse((text.match(/\{[\s\S]*\}/) ?? ["{}"])[0]);
279
+ const whoami = json.result.tools.find((t) => t.name === "private-whoami");
280
+ expect(whoami?.securitySchemes).toEqual([{ type: "oauth2" }]);
281
+ });
282
+ it("denies a protected tool inside an anonymous JSON-RPC batch", async () => {
283
+ const { jwksUri } = await startJwks();
284
+ const base = await bootMixedServer(jwksUri);
285
+ const res = await fetch(`${base}/mcp`, {
286
+ method: "POST",
287
+ headers: {
288
+ "Content-Type": "application/json",
289
+ Accept: "application/json, text/event-stream",
290
+ },
291
+ body: JSON.stringify([
292
+ {
293
+ jsonrpc: "2.0",
294
+ id: 1,
295
+ method: "tools/call",
296
+ params: { name: "private-whoami", arguments: {} },
297
+ },
298
+ ]),
299
+ });
300
+ expect(res.status).toBe(401);
301
+ const wwwAuthenticate = res.headers.get("www-authenticate") ?? "";
302
+ expect(wwwAuthenticate).toMatch(/^Bearer /);
303
+ expect(wwwAuthenticate).toContain("resource_metadata=");
304
+ const text = await res.text();
305
+ expect(text).not.toContain("client-1");
306
+ });
307
+ it("gates a tool with no declared schemes (secure default)", async () => {
308
+ const { jwksUri } = await startJwks();
309
+ const base = await bootMixedServer(jwksUri);
310
+ const res = await fetch(`${base}/mcp`, {
311
+ method: "POST",
312
+ headers: {
313
+ "Content-Type": "application/json",
314
+ Accept: "application/json, text/event-stream",
315
+ },
316
+ body: JSON.stringify({
317
+ jsonrpc: "2.0",
318
+ id: 1,
319
+ method: "tools/call",
320
+ params: { name: "undeclared-whoami", arguments: {} },
321
+ }),
322
+ });
323
+ expect(res.status).toBe(401);
324
+ });
325
+ it("returns 401 + WWW-Authenticate for a protected tool while anonymous", async () => {
326
+ const { jwksUri } = await startJwks();
327
+ const base = await bootMixedServer(jwksUri);
328
+ const res = await fetch(`${base}/mcp`, {
329
+ method: "POST",
330
+ headers: {
331
+ "Content-Type": "application/json",
332
+ Accept: "application/json, text/event-stream",
333
+ },
334
+ body: JSON.stringify({
335
+ jsonrpc: "2.0",
336
+ id: 1,
337
+ method: "tools/call",
338
+ params: { name: "private-whoami", arguments: {} },
339
+ }),
340
+ });
341
+ expect(res.status).toBe(401);
342
+ expect(res.headers.get("www-authenticate")).toMatch(/error="invalid_token"/);
343
+ expect(res.headers.get("www-authenticate")).toMatch(/resource_metadata=/);
344
+ });
345
+ it("returns an in-band mcp/www_authenticate array to ChatGPT (not a transport 401)", async () => {
346
+ const { jwksUri } = await startJwks();
347
+ const base = await bootMixedServer(jwksUri);
348
+ const res = await fetch(`${base}/mcp`, {
349
+ method: "POST",
350
+ headers: {
351
+ "Content-Type": "application/json",
352
+ Accept: "application/json, text/event-stream",
353
+ "User-Agent": "openai-mcp/1.0",
354
+ },
355
+ body: JSON.stringify({
356
+ jsonrpc: "2.0",
357
+ id: 7,
358
+ method: "tools/call",
359
+ params: { name: "private-whoami", arguments: {} },
360
+ }),
361
+ });
362
+ expect(res.status).toBe(200);
363
+ expect(res.headers.get("www-authenticate")).toBeNull();
364
+ const body = (await res.json());
365
+ expect(body.id).toBe(7);
366
+ expect(body.result.isError).toBe(true);
367
+ const challenge = body.result._meta["mcp/www_authenticate"];
368
+ expect(Array.isArray(challenge)).toBe(true);
369
+ expect(challenge[0]).toMatch(/error="invalid_token"/);
370
+ expect(challenge[0]).toMatch(/resource_metadata=/);
371
+ });
372
+ it("fails closed on a ChatGPT batch (transport 401, no in-band challenge)", async () => {
373
+ const { jwksUri } = await startJwks();
374
+ const base = await bootMixedServer(jwksUri);
375
+ const res = await fetch(`${base}/mcp`, {
376
+ method: "POST",
377
+ headers: {
378
+ "Content-Type": "application/json",
379
+ Accept: "application/json, text/event-stream",
380
+ "User-Agent": "openai-mcp/1.0",
381
+ },
382
+ body: JSON.stringify([
383
+ {
384
+ jsonrpc: "2.0",
385
+ id: 1,
386
+ method: "tools/call",
387
+ params: { name: "private-whoami", arguments: {} },
388
+ },
389
+ ]),
390
+ });
391
+ expect(res.status).toBe(401);
392
+ expect(res.headers.get("www-authenticate")).toMatch(/^Bearer /);
393
+ const text = await res.text();
394
+ expect(text).not.toContain("client-1");
395
+ });
396
+ });
397
+ async function bootScopedServer(jwksUri) {
398
+ const { createApp } = await import("../express.js");
399
+ const app = new Skybridge({
400
+ name: "scoped-auth-test",
401
+ version: "0.0.0",
402
+ oauth: {
403
+ baseUrl: "https://app.example.test",
404
+ oauthMetadata: {
405
+ issuer: ISSUER,
406
+ authorization_endpoint: `${ISSUER}/authorize`,
407
+ token_endpoint: `${ISSUER}/token`,
408
+ response_types_supported: ["code"],
409
+ },
410
+ verifier: createJwksVerifier({
411
+ issuer: ISSUER,
412
+ audience: AUDIENCE,
413
+ jwksUri,
414
+ }),
415
+ },
416
+ handler: (server) => server.registerTool({
417
+ name: "checkout",
418
+ description: "Needs the checkout scope.",
419
+ inputSchema: {},
420
+ auth: { scopes: ["checkout"] },
421
+ }, () => ({ content: [{ type: "text", text: "ok" }] })),
422
+ });
423
+ const httpServer = http.createServer();
424
+ await createApp({ app, httpServer });
425
+ const listening = http.createServer(app.express);
426
+ await new Promise((resolve) => listening.listen(0, resolve));
427
+ appServer = listening;
428
+ const port = listening.address().port;
429
+ return `http://localhost:${port}`;
430
+ }
431
+ describe("per-tool scope enforcement in fully-authenticated mode", () => {
432
+ it("returns 403 for a valid token missing the tool's scope", async () => {
433
+ const { privateKey, jwksUri } = await startJwks();
434
+ const base = await bootScopedServer(jwksUri);
435
+ const token = await signToken(privateKey, "openid");
436
+ const res = await fetch(`${base}/mcp`, {
437
+ method: "POST",
438
+ headers: {
439
+ "Content-Type": "application/json",
440
+ Accept: "application/json, text/event-stream",
441
+ Authorization: `Bearer ${token}`,
442
+ },
443
+ body: JSON.stringify({
444
+ jsonrpc: "2.0",
445
+ id: 1,
446
+ method: "tools/call",
447
+ params: { name: "checkout", arguments: {} },
448
+ }),
449
+ });
450
+ expect(res.status).toBe(403);
451
+ const header = res.headers.get("www-authenticate");
452
+ expect(header).toMatch(/error="insufficient_scope"/);
453
+ expect(header).toMatch(/scope="checkout"/);
454
+ });
455
+ it("allows a token carrying the tool's scope", async () => {
456
+ const { privateKey, jwksUri } = await startJwks();
457
+ const base = await bootScopedServer(jwksUri);
458
+ const token = await signToken(privateKey, "openid checkout");
459
+ const client = new Client({ name: "test-client", version: "0.0.0" });
460
+ await client.connect(new StreamableHTTPClientTransport(new URL(`${base}/mcp`), {
461
+ requestInit: { headers: { Authorization: `Bearer ${token}` } },
462
+ }));
463
+ const result = (await client.callTool({
464
+ name: "checkout",
465
+ arguments: {},
466
+ }));
467
+ expect(result.content[0]?.text).toBe("ok");
468
+ await client.close();
469
+ });
470
+ });
471
+ describe("auth shorthand validation", () => {
472
+ it("throws when `auth` requires sign-in but no oauth provider is configured", () => {
473
+ expect(() => new McpServer({ name: "t", version: "0" }).registerTool({ name: "x", inputSchema: {}, auth: {} }, () => ({ content: [{ type: "text", text: "" }] }))).toThrow(/no `oauth` provider/);
474
+ });
475
+ it("throws when `auth` sets scopes but no oauth provider is configured", () => {
476
+ expect(() => new McpServer({ name: "t", version: "0" }).registerTool({ name: "x", inputSchema: {}, auth: { scopes: ["checkout"] } }, () => ({ content: [{ type: "text", text: "" }] }))).toThrow(/no `oauth` provider/);
477
+ });
478
+ it("allows `auth: { allowsAnonymous: true }` without an oauth provider", () => {
479
+ expect(() => new McpServer({ name: "t", version: "0" }).registerTool({ name: "x", inputSchema: {}, auth: { allowsAnonymous: true } }, () => ({ content: [{ type: "text", text: "" }] }))).not.toThrow();
480
+ });
481
+ it("prefers `securitySchemes` over `auth` when both are set (untyped path)", () => {
482
+ expect(() => new McpServer({ name: "t", version: "0" }).registerTool({
483
+ name: "x",
484
+ inputSchema: {},
485
+ auth: {},
486
+ securitySchemes: [{ type: "oauth2" }],
487
+ }, () => ({ content: [{ type: "text", text: "" }] }))).not.toThrow();
488
+ });
489
+ });
168
490
  describe("oauth config validation", () => {
169
491
  const validMetadata = {
170
492
  issuer: ISSUER,
@@ -172,14 +494,21 @@ describe("oauth config validation", () => {
172
494
  token_endpoint: `${ISSUER}/token`,
173
495
  response_types_supported: ["code"],
174
496
  };
175
- it("throws on a non-absolute baseUrl", () => {
176
- expect(() => new McpServer({ name: "t", version: "0" }, undefined, {
497
+ it("rejects a non-absolute baseUrl when the app is first used", async () => {
498
+ const app = new Skybridge({
499
+ name: "t",
500
+ version: "0",
177
501
  oauth: {
178
502
  baseUrl: "not-a-url",
179
503
  oauthMetadata: validMetadata,
180
- verify: { issuer: ISSUER, audience: AUDIENCE },
504
+ verifier: createJwksVerifier({
505
+ issuer: ISSUER,
506
+ audience: AUDIENCE,
507
+ }),
181
508
  },
182
- })).toThrow(/baseUrl must be a valid absolute URL/);
509
+ handler: (server) => server,
510
+ });
511
+ await expect(app.createServerInstance()).rejects.toThrow(/baseUrl must be a valid absolute URL/);
183
512
  });
184
513
  });
185
514
  //# sourceMappingURL=setup.test.js.map