skybridge 0.0.0-dev.81dc563 → 0.0.0-dev.81fd90a

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 (316) hide show
  1. package/README.md +3 -0
  2. package/dist/cli/build-helpers.d.ts +6 -2
  3. package/dist/cli/build-helpers.js +37 -12
  4. package/dist/cli/build-helpers.js.map +1 -1
  5. package/dist/cli/build-helpers.test.js +71 -3
  6. package/dist/cli/build-helpers.test.js.map +1 -1
  7. package/dist/cli/build-steps.d.ts +2 -0
  8. package/dist/cli/build-steps.js +73 -0
  9. package/dist/cli/build-steps.js.map +1 -0
  10. package/dist/cli/build-steps.test.js +52 -0
  11. package/dist/cli/build-steps.test.js.map +1 -0
  12. package/dist/cli/header.d.ts +1 -1
  13. package/dist/cli/resolve-skybridge-config.d.ts +5 -0
  14. package/dist/cli/{resolve-views-dir.js → resolve-skybridge-config.js} +4 -3
  15. package/dist/cli/resolve-skybridge-config.js.map +1 -0
  16. package/dist/cli/run-plain.d.ts +18 -0
  17. package/dist/cli/run-plain.js +89 -0
  18. package/dist/cli/run-plain.js.map +1 -0
  19. package/dist/cli/use-execute-steps.d.ts +1 -1
  20. package/dist/cli/use-nodemon.d.ts +14 -0
  21. package/dist/cli/use-nodemon.js +71 -60
  22. package/dist/cli/use-nodemon.js.map +1 -1
  23. package/dist/cli/use-typescript-check.d.ts +8 -2
  24. package/dist/cli/use-typescript-check.js +70 -67
  25. package/dist/cli/use-typescript-check.js.map +1 -1
  26. package/dist/commands/build.d.ts +0 -2
  27. package/dist/commands/build.js +2 -61
  28. package/dist/commands/build.js.map +1 -1
  29. package/dist/commands/dev.d.ts +1 -0
  30. package/dist/commands/dev.js +34 -2
  31. package/dist/commands/dev.js.map +1 -1
  32. package/dist/context-warnings.d.ts +5 -0
  33. package/dist/context-warnings.js +31 -0
  34. package/dist/context-warnings.js.map +1 -0
  35. package/dist/context-warnings.test.d.ts +1 -0
  36. package/dist/context-warnings.test.js +28 -0
  37. package/dist/context-warnings.test.js.map +1 -0
  38. package/dist/server/auth/discovery.d.ts +33 -0
  39. package/dist/server/auth/discovery.js +56 -0
  40. package/dist/server/auth/discovery.js.map +1 -0
  41. package/dist/server/auth/discovery.test.d.ts +1 -0
  42. package/dist/server/auth/discovery.test.js +93 -0
  43. package/dist/server/auth/discovery.test.js.map +1 -0
  44. package/dist/server/auth/index.d.ts +17 -7
  45. package/dist/server/auth/index.js.map +1 -1
  46. package/dist/server/auth/providers/auth0.d.ts +40 -0
  47. package/dist/server/auth/providers/auth0.js +31 -0
  48. package/dist/server/auth/providers/auth0.js.map +1 -0
  49. package/dist/server/auth/providers/auth0.test.d.ts +1 -0
  50. package/dist/server/auth/providers/auth0.test.js +50 -0
  51. package/dist/server/auth/providers/auth0.test.js.map +1 -0
  52. package/dist/server/auth/providers/authplane.d.ts +49 -0
  53. package/dist/server/auth/providers/authplane.js +57 -0
  54. package/dist/server/auth/providers/authplane.js.map +1 -0
  55. package/dist/server/auth/providers/authplane.test.d.ts +1 -0
  56. package/dist/server/auth/providers/authplane.test.js +140 -0
  57. package/dist/server/auth/providers/authplane.test.js.map +1 -0
  58. package/dist/server/auth/providers/clerk.d.ts +51 -0
  59. package/dist/server/auth/providers/clerk.js +19 -0
  60. package/dist/server/auth/providers/clerk.js.map +1 -0
  61. package/dist/server/auth/providers/clerk.test.d.ts +1 -0
  62. package/dist/server/auth/providers/clerk.test.js +30 -0
  63. package/dist/server/auth/providers/clerk.test.js.map +1 -0
  64. package/dist/server/auth/providers/custom.d.ts +40 -0
  65. package/dist/server/auth/providers/custom.js +56 -0
  66. package/dist/server/auth/providers/custom.js.map +1 -0
  67. package/dist/server/auth/providers/custom.test.d.ts +1 -0
  68. package/dist/server/auth/providers/custom.test.js +158 -0
  69. package/dist/server/auth/providers/custom.test.js.map +1 -0
  70. package/dist/server/auth/providers/descope.d.ts +44 -0
  71. package/dist/server/auth/providers/descope.js +44 -0
  72. package/dist/server/auth/providers/descope.js.map +1 -0
  73. package/dist/server/auth/providers/descope.test.d.ts +1 -0
  74. package/dist/server/auth/providers/descope.test.js +62 -0
  75. package/dist/server/auth/providers/descope.test.js.map +1 -0
  76. package/dist/server/auth/providers/shared.d.ts +2 -0
  77. package/dist/server/auth/providers/shared.js +6 -0
  78. package/dist/server/auth/providers/shared.js.map +1 -0
  79. package/dist/server/auth/providers/shared.test.d.ts +1 -0
  80. package/dist/server/auth/providers/shared.test.js +10 -0
  81. package/dist/server/auth/providers/shared.test.js.map +1 -0
  82. package/dist/server/auth/providers/stytch.d.ts +25 -0
  83. package/dist/server/auth/providers/stytch.js +16 -0
  84. package/dist/server/auth/providers/stytch.js.map +1 -0
  85. package/dist/server/auth/providers/verify-spy.d.ts +12 -0
  86. package/dist/server/auth/providers/verify-spy.js +21 -0
  87. package/dist/server/auth/providers/verify-spy.js.map +1 -0
  88. package/dist/server/auth/providers/workos.d.ts +33 -0
  89. package/dist/server/auth/providers/workos.js +15 -0
  90. package/dist/server/auth/providers/workos.js.map +1 -0
  91. package/dist/server/auth/security-schemes.d.ts +23 -0
  92. package/dist/server/auth/security-schemes.js +64 -0
  93. package/dist/server/auth/security-schemes.js.map +1 -0
  94. package/dist/server/auth/security-schemes.test.d.ts +1 -0
  95. package/dist/server/auth/security-schemes.test.js +99 -0
  96. package/dist/server/auth/security-schemes.test.js.map +1 -0
  97. package/dist/server/auth/setup.d.ts +3 -1
  98. package/dist/server/auth/setup.js +64 -25
  99. package/dist/server/auth/setup.js.map +1 -1
  100. package/dist/server/auth/setup.test.js +330 -19
  101. package/dist/server/auth/setup.test.js.map +1 -1
  102. package/dist/server/auth/verify.d.ts +40 -5
  103. package/dist/server/auth/verify.js +29 -4
  104. package/dist/server/auth/verify.js.map +1 -1
  105. package/dist/server/auth/verify.test.js +62 -0
  106. package/dist/server/auth/verify.test.js.map +1 -1
  107. package/dist/server/auth-extra.test-d.d.ts +1 -0
  108. package/dist/server/auth-extra.test-d.js +71 -0
  109. package/dist/server/auth-extra.test-d.js.map +1 -0
  110. package/dist/server/auth.d.ts +42 -5
  111. package/dist/server/auth.js +3 -4
  112. package/dist/server/auth.js.map +1 -1
  113. package/dist/server/content-helpers.d.ts +1 -1
  114. package/dist/server/content-helpers.js.map +1 -1
  115. package/dist/server/express.js +9 -28
  116. package/dist/server/express.js.map +1 -1
  117. package/dist/server/express.test.js +42 -7
  118. package/dist/server/express.test.js.map +1 -1
  119. package/dist/server/host.d.ts +2 -0
  120. package/dist/server/host.js +11 -0
  121. package/dist/server/host.js.map +1 -0
  122. package/dist/server/host.test.d.ts +1 -0
  123. package/dist/server/host.test.js +13 -0
  124. package/dist/server/host.test.js.map +1 -0
  125. package/dist/server/index.d.ts +14 -3
  126. package/dist/server/index.js +12 -2
  127. package/dist/server/index.js.map +1 -1
  128. package/dist/server/mcp-errors.test.d.ts +1 -0
  129. package/dist/server/mcp-errors.test.js +37 -0
  130. package/dist/server/mcp-errors.test.js.map +1 -0
  131. package/dist/server/middleware.d.ts +35 -10
  132. package/dist/server/middleware.js +33 -0
  133. package/dist/server/middleware.js.map +1 -1
  134. package/dist/server/middleware.test-d.js +1 -1
  135. package/dist/server/middleware.test-d.js.map +1 -1
  136. package/dist/server/middleware.test.js +38 -3
  137. package/dist/server/middleware.test.js.map +1 -1
  138. package/dist/server/register-tool.test.d.ts +1 -0
  139. package/dist/server/register-tool.test.js +25 -0
  140. package/dist/server/register-tool.test.js.map +1 -0
  141. package/dist/server/requestOrigin.d.ts +3 -2
  142. package/dist/server/requestOrigin.js +8 -8
  143. package/dist/server/requestOrigin.js.map +1 -1
  144. package/dist/server/server.d.ts +138 -50
  145. package/dist/server/server.js +280 -147
  146. package/dist/server/server.js.map +1 -1
  147. package/dist/server/skills-integration.test.d.ts +1 -0
  148. package/dist/server/skills-integration.test.js +79 -0
  149. package/dist/server/skills-integration.test.js.map +1 -0
  150. package/dist/server/skills.d.ts +27 -0
  151. package/dist/server/skills.js +188 -0
  152. package/dist/server/skills.js.map +1 -0
  153. package/dist/server/skills.test.d.ts +1 -0
  154. package/dist/server/skills.test.js +243 -0
  155. package/dist/server/skills.test.js.map +1 -0
  156. package/dist/server/tool-extra.test.d.ts +1 -0
  157. package/dist/server/tool-extra.test.js +72 -0
  158. package/dist/server/tool-extra.test.js.map +1 -0
  159. package/dist/server/view-name.test-d.d.ts +1 -0
  160. package/dist/server/view-name.test-d.js +8 -0
  161. package/dist/server/view-name.test-d.js.map +1 -0
  162. package/dist/server/view-resource-resolution.test.js +93 -12
  163. package/dist/server/view-resource-resolution.test.js.map +1 -1
  164. package/dist/server/viewsDevServer.js +4 -1
  165. package/dist/server/viewsDevServer.js.map +1 -1
  166. package/dist/standard-schema.d.ts +3 -0
  167. package/dist/standard-schema.js +2 -0
  168. package/dist/standard-schema.js.map +1 -0
  169. package/dist/test/utils.d.ts +5 -8
  170. package/dist/test/utils.js +8 -4
  171. package/dist/test/utils.js.map +1 -1
  172. package/dist/test/view.test.js +82 -114
  173. package/dist/test/view.test.js.map +1 -1
  174. package/dist/web/bridges/adaptor.d.ts +51 -0
  175. package/dist/web/bridges/adaptor.js +327 -0
  176. package/dist/web/bridges/adaptor.js.map +1 -0
  177. package/dist/web/bridges/adaptor.test.d.ts +1 -0
  178. package/dist/web/bridges/adaptor.test.js +225 -0
  179. package/dist/web/bridges/adaptor.test.js.map +1 -0
  180. package/dist/web/bridges/apps-sdk/bridge.d.ts +6 -2
  181. package/dist/web/bridges/apps-sdk/bridge.js +15 -4
  182. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
  183. package/dist/web/bridges/apps-sdk/index.d.ts +0 -1
  184. package/dist/web/bridges/apps-sdk/index.js +0 -1
  185. package/dist/web/bridges/apps-sdk/index.js.map +1 -1
  186. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.d.ts +1 -1
  187. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js +1 -1
  188. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
  189. package/dist/web/bridges/get-adaptor.d.ts +4 -4
  190. package/dist/web/bridges/get-adaptor.js +6 -11
  191. package/dist/web/bridges/get-adaptor.js.map +1 -1
  192. package/dist/web/bridges/get-adaptor.test.d.ts +1 -0
  193. package/dist/web/bridges/get-adaptor.test.js +32 -0
  194. package/dist/web/bridges/get-adaptor.test.js.map +1 -0
  195. package/dist/web/bridges/mcp-app/bridge.d.ts +13 -2
  196. package/dist/web/bridges/mcp-app/bridge.js +51 -7
  197. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  198. package/dist/web/bridges/mcp-app/bridge.test.d.ts +1 -0
  199. package/dist/web/bridges/mcp-app/bridge.test.js +17 -0
  200. package/dist/web/bridges/mcp-app/bridge.test.js.map +1 -0
  201. package/dist/web/bridges/mcp-app/index.d.ts +0 -1
  202. package/dist/web/bridges/mcp-app/index.js +0 -1
  203. package/dist/web/bridges/mcp-app/index.js.map +1 -1
  204. package/dist/web/bridges/mcp-app/types.d.ts +2 -0
  205. package/dist/web/bridges/mcp-app/types.js.map +1 -1
  206. package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +1 -1
  207. package/dist/web/bridges/mcp-app/use-mcp-app-context.js +1 -1
  208. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
  209. package/dist/web/bridges/mcp-app/view-tools.test.js +5 -6
  210. package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -1
  211. package/dist/web/bridges/types.d.ts +27 -9
  212. package/dist/web/bridges/types.js +14 -1
  213. package/dist/web/bridges/types.js.map +1 -1
  214. package/dist/web/bridges/types.test.d.ts +1 -0
  215. package/dist/web/bridges/types.test.js +19 -0
  216. package/dist/web/bridges/types.test.js.map +1 -0
  217. package/dist/web/components/modal-provider.d.ts +1 -1
  218. package/dist/web/components/modal-provider.js +4 -3
  219. package/dist/web/components/modal-provider.js.map +1 -1
  220. package/dist/web/create-store.test.js +9 -6
  221. package/dist/web/create-store.test.js.map +1 -1
  222. package/dist/web/data-llm.d.ts +1 -1
  223. package/dist/web/data-llm.test.js +19 -6
  224. package/dist/web/data-llm.test.js.map +1 -1
  225. package/dist/web/hooks/index.d.ts +2 -1
  226. package/dist/web/hooks/index.js +2 -1
  227. package/dist/web/hooks/index.js.map +1 -1
  228. package/dist/web/hooks/test/utils.d.ts +2 -1
  229. package/dist/web/hooks/test/utils.js +1 -1
  230. package/dist/web/hooks/test/utils.js.map +1 -1
  231. package/dist/web/hooks/use-call-tool.test.js +39 -25
  232. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  233. package/dist/web/hooks/use-display-mode.d.ts +1 -1
  234. package/dist/web/hooks/use-display-mode.js +1 -1
  235. package/dist/web/hooks/use-display-mode.js.map +1 -1
  236. package/dist/web/hooks/use-display-mode.test.js +57 -21
  237. package/dist/web/hooks/use-display-mode.test.js.map +1 -1
  238. package/dist/web/hooks/use-download.d.ts +1 -3
  239. package/dist/web/hooks/use-download.js +1 -2
  240. package/dist/web/hooks/use-download.js.map +1 -1
  241. package/dist/web/hooks/use-download.test.js +16 -8
  242. package/dist/web/hooks/use-download.test.js.map +1 -1
  243. package/dist/web/hooks/use-files.test.js +11 -3
  244. package/dist/web/hooks/use-files.test.js.map +1 -1
  245. package/dist/web/hooks/use-host.d.ts +22 -0
  246. package/dist/web/hooks/use-host.js +30 -0
  247. package/dist/web/hooks/use-host.js.map +1 -0
  248. package/dist/web/hooks/use-host.test.d.ts +1 -0
  249. package/dist/web/hooks/use-host.test.js +59 -0
  250. package/dist/web/hooks/use-host.test.js.map +1 -0
  251. package/dist/web/hooks/use-open-external.test.js +14 -4
  252. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  253. package/dist/web/hooks/use-register-view-tool.d.ts +2 -3
  254. package/dist/web/hooks/use-register-view-tool.js.map +1 -1
  255. package/dist/web/hooks/use-request-close.test.js +35 -40
  256. package/dist/web/hooks/use-request-close.test.js.map +1 -1
  257. package/dist/web/hooks/use-request-modal.test.js +11 -1
  258. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  259. package/dist/web/hooks/use-request-size.d.ts +1 -1
  260. package/dist/web/hooks/use-request-size.js +1 -1
  261. package/dist/web/hooks/use-request-size.js.map +1 -1
  262. package/dist/web/hooks/use-request-size.test.js +35 -53
  263. package/dist/web/hooks/use-request-size.test.js.map +1 -1
  264. package/dist/web/hooks/use-set-open-in-app-url.test.js +29 -9
  265. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  266. package/dist/web/hooks/use-tool-info.d.ts +1 -20
  267. package/dist/web/hooks/use-tool-info.js +0 -1
  268. package/dist/web/hooks/use-tool-info.js.map +1 -1
  269. package/dist/web/hooks/use-tool-info.test-d.js +0 -5
  270. package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
  271. package/dist/web/hooks/use-tool-info.test.js +37 -32
  272. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  273. package/dist/web/hooks/use-user.d.ts +3 -2
  274. package/dist/web/hooks/use-user.js +4 -3
  275. package/dist/web/hooks/use-user.js.map +1 -1
  276. package/dist/web/hooks/use-user.test.js +84 -36
  277. package/dist/web/hooks/use-user.test.js.map +1 -1
  278. package/dist/web/hooks/use-view-state.js +19 -9
  279. package/dist/web/hooks/use-view-state.js.map +1 -1
  280. package/dist/web/hooks/use-view-state.test.js +38 -3
  281. package/dist/web/hooks/use-view-state.test.js.map +1 -1
  282. package/dist/web/hooks/use-viewport.d.ts +20 -0
  283. package/dist/web/hooks/use-viewport.js +21 -0
  284. package/dist/web/hooks/use-viewport.js.map +1 -0
  285. package/dist/web/hooks/use-viewport.test.d.ts +1 -0
  286. package/dist/web/hooks/use-viewport.test.js +109 -0
  287. package/dist/web/hooks/use-viewport.test.js.map +1 -0
  288. package/dist/web/index.d.ts +1 -0
  289. package/dist/web/index.js.map +1 -1
  290. package/dist/web/mount-view.d.ts +3 -2
  291. package/dist/web/mount-view.js +3 -2
  292. package/dist/web/mount-view.js.map +1 -1
  293. package/dist/web/plugin/plugin-build.test.d.ts +1 -0
  294. package/dist/web/plugin/plugin-build.test.js +54 -0
  295. package/dist/web/plugin/plugin-build.test.js.map +1 -0
  296. package/dist/web/plugin/plugin.d.ts +6 -0
  297. package/dist/web/plugin/plugin.js +18 -7
  298. package/dist/web/plugin/plugin.js.map +1 -1
  299. package/dist/web/plugin/plugin.test.d.ts +1 -0
  300. package/dist/web/plugin/plugin.test.js +61 -0
  301. package/dist/web/plugin/plugin.test.js.map +1 -0
  302. package/package.json +28 -23
  303. package/dist/cli/resolve-views-dir.d.ts +0 -1
  304. package/dist/cli/resolve-views-dir.js.map +0 -1
  305. package/dist/web/bridges/apps-sdk/adaptor.d.ts +0 -29
  306. package/dist/web/bridges/apps-sdk/adaptor.js +0 -117
  307. package/dist/web/bridges/apps-sdk/adaptor.js.map +0 -1
  308. package/dist/web/bridges/mcp-app/adaptor.d.ts +0 -53
  309. package/dist/web/bridges/mcp-app/adaptor.js +0 -283
  310. package/dist/web/bridges/mcp-app/adaptor.js.map +0 -1
  311. package/dist/web/hooks/use-layout.d.ts +0 -24
  312. package/dist/web/hooks/use-layout.js +0 -25
  313. package/dist/web/hooks/use-layout.js.map +0 -1
  314. package/dist/web/hooks/use-layout.test.js +0 -96
  315. package/dist/web/hooks/use-layout.test.js.map +0 -1
  316. /package/dist/{web/hooks/use-layout.test.d.ts → cli/build-steps.test.d.ts} +0 -0
@@ -1,10 +1,10 @@
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";
7
6
  import { McpServer } from "../server.js";
7
+ import { createJwksVerifier } from "./verify.js";
8
8
  vi.mock("@skybridge/devtools", () => ({
9
9
  devtoolsStaticServer: () => ((_req, _res, next) => next()),
10
10
  }));
@@ -36,10 +36,10 @@ async function startJwks() {
36
36
  const port = server.address().port;
37
37
  return { privateKey, jwksUri: `http://localhost:${port}/jwks` };
38
38
  }
39
- function signToken(key) {
39
+ function signToken(key, scope = "openid email") {
40
40
  return new jose.SignJWT({
41
41
  client_id: "client-1",
42
- scope: "openid email",
42
+ scope,
43
43
  sub: "user-1",
44
44
  })
45
45
  .setProtectedHeader({ alg: "RS256", kid: "test-key" })
@@ -48,7 +48,7 @@ function signToken(key) {
48
48
  .setExpirationTime("1h")
49
49
  .sign(key);
50
50
  }
51
- async function bootServer(jwksUri, { baseUrl = "https://app.example.test", enforcement = "required", } = {}) {
51
+ async function bootServer(jwksUri, { baseUrl = "https://app.example.test" } = {}) {
52
52
  const { createApp } = await import("../express.js");
53
53
  const server = new McpServer({ name: "auth-test", version: "0.0.0" }, { capabilities: {} }, {
54
54
  oauth: {
@@ -59,18 +59,23 @@ async function bootServer(jwksUri, { baseUrl = "https://app.example.test", enfor
59
59
  token_endpoint: `${ISSUER}/token`,
60
60
  response_types_supported: ["code"],
61
61
  },
62
- verify: { issuer: ISSUER, audience: AUDIENCE, jwksUri },
62
+ verifier: createJwksVerifier({
63
+ issuer: ISSUER,
64
+ audience: AUDIENCE,
65
+ jwksUri,
66
+ }),
63
67
  scopesSupported: ["openid", "email"],
64
68
  requiredScopes: ["openid"],
65
- enforcement,
66
69
  },
67
70
  }).registerTool({
68
71
  name: "whoami",
69
72
  description: "Returns the caller identity.",
70
73
  inputSchema: {},
71
74
  }, (_args, extra) => ({
72
- structuredContent: { clientId: extra.authInfo?.clientId ?? null },
73
- content: [{ type: "text", text: extra.authInfo?.clientId ?? "anon" }],
75
+ structuredContent: { clientId: extra.http?.authInfo?.clientId ?? null },
76
+ content: [
77
+ { type: "text", text: extra.http?.authInfo?.clientId ?? "anon" },
78
+ ],
74
79
  }));
75
80
  const httpServer = http.createServer();
76
81
  await createApp({ mcpServer: server, httpServer });
@@ -128,6 +133,29 @@ describe("baseUrl inferred from headers", () => {
128
133
  const body = (await res.json());
129
134
  expect(body.resource).toBe("https://infer.example.test/");
130
135
  });
136
+ it("uses the first hop of a forwarded-host chain", async () => {
137
+ const { jwksUri } = await startJwks();
138
+ const base = await bootServer(jwksUri, { baseUrl: null });
139
+ const res = await fetch(`${base}/.well-known/oauth-protected-resource`, {
140
+ headers: {
141
+ "x-forwarded-host": "public.example, internal.local",
142
+ "x-forwarded-proto": "https, http",
143
+ },
144
+ });
145
+ expect(res.status).toBe(200);
146
+ const body = (await res.json());
147
+ expect(body.resource).toBe("https://public.example/");
148
+ });
149
+ it("ignores the client Origin header, using Host instead", async () => {
150
+ const { jwksUri } = await startJwks();
151
+ const base = await bootServer(jwksUri, { baseUrl: null });
152
+ const res = await fetch(`${base}/.well-known/oauth-protected-resource`, {
153
+ headers: { origin: "https://chatgpt.com" },
154
+ });
155
+ expect(res.status).toBe(200);
156
+ const body = (await res.json());
157
+ expect(body.resource).toMatch(/^http:\/\/(localhost|127\.0\.0\.1):/);
158
+ });
131
159
  it("points the 401 WWW-Authenticate at the inferred host", async () => {
132
160
  const { jwksUri } = await startJwks();
133
161
  const base = await bootServer(jwksUri, { baseUrl: null });
@@ -143,10 +171,133 @@ describe("baseUrl inferred from headers", () => {
143
171
  expect(res.headers.get("www-authenticate")).toMatch(/resource_metadata="https:\/\/infer\.example\.test\//);
144
172
  });
145
173
  });
146
- describe("enforcement: optional", () => {
147
- it("lets an anonymous /mcp request through", async () => {
174
+ async function bootMixedServer(jwksUri) {
175
+ const { createApp } = await import("../express.js");
176
+ const server = new McpServer({ name: "mixed-auth-test", version: "0.0.0" }, { capabilities: {} }, {
177
+ oauth: {
178
+ baseUrl: "https://app.example.test",
179
+ oauthMetadata: {
180
+ issuer: ISSUER,
181
+ authorization_endpoint: `${ISSUER}/authorize`,
182
+ token_endpoint: `${ISSUER}/token`,
183
+ response_types_supported: ["code"],
184
+ },
185
+ verifier: createJwksVerifier({
186
+ issuer: ISSUER,
187
+ audience: AUDIENCE,
188
+ jwksUri,
189
+ }),
190
+ },
191
+ })
192
+ .registerTool({
193
+ name: "public-whoami",
194
+ description: "Public.",
195
+ inputSchema: {},
196
+ auth: { allowsAnonymous: true },
197
+ }, (_args, extra) => ({
198
+ content: [
199
+ { type: "text", text: extra.http?.authInfo?.clientId ?? "anon" },
200
+ ],
201
+ }))
202
+ .registerTool({
203
+ name: "private-whoami",
204
+ description: "Private.",
205
+ inputSchema: {},
206
+ auth: {},
207
+ }, (_args, extra) => ({
208
+ content: [
209
+ { type: "text", text: extra.http?.authInfo?.clientId ?? "anon" },
210
+ ],
211
+ }));
212
+ server.registerTool({
213
+ name: "undeclared-whoami",
214
+ description: "Registered without auth or securitySchemes.",
215
+ inputSchema: {},
216
+ }, (_args, extra) => ({
217
+ content: [
218
+ { type: "text", text: extra.http?.authInfo?.clientId ?? "anon" },
219
+ ],
220
+ }));
221
+ const httpServer = http.createServer();
222
+ await createApp({ mcpServer: server, httpServer });
223
+ const listening = http.createServer(server.express);
224
+ await new Promise((resolve) => listening.listen(0, resolve));
225
+ appServer = listening;
226
+ const port = listening.address().port;
227
+ return `http://localhost:${port}`;
228
+ }
229
+ describe("mixed-auth door", () => {
230
+ it("lets an anonymous caller initialize and run a noauth tool", async () => {
148
231
  const { jwksUri } = await startJwks();
149
- const base = await bootServer(jwksUri, { enforcement: "optional" });
232
+ const base = await bootMixedServer(jwksUri);
233
+ const client = new Client({ name: "test-client", version: "0.0.0" });
234
+ await client.connect(new StreamableHTTPClientTransport(new URL(`${base}/mcp`)));
235
+ const result = (await client.callTool({
236
+ name: "public-whoami",
237
+ arguments: {},
238
+ }));
239
+ expect(result.content[0]?.text).toBe("anon");
240
+ await client.close();
241
+ });
242
+ it("exposes securitySchemes at the top level of tools/list (not only _meta)", async () => {
243
+ const { jwksUri } = await startJwks();
244
+ const base = await bootMixedServer(jwksUri);
245
+ const headers = {
246
+ "Content-Type": "application/json",
247
+ Accept: "application/json, text/event-stream",
248
+ };
249
+ await fetch(`${base}/mcp`, {
250
+ method: "POST",
251
+ headers,
252
+ body: JSON.stringify({
253
+ jsonrpc: "2.0",
254
+ id: 1,
255
+ method: "initialize",
256
+ params: {
257
+ protocolVersion: "2025-06-18",
258
+ capabilities: {},
259
+ clientInfo: { name: "c", version: "0" },
260
+ },
261
+ }),
262
+ });
263
+ const res = await fetch(`${base}/mcp`, {
264
+ method: "POST",
265
+ headers,
266
+ body: JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list" }),
267
+ });
268
+ const text = await res.text();
269
+ const json = JSON.parse((text.match(/\{[\s\S]*\}/) ?? ["{}"])[0]);
270
+ const whoami = json.result.tools.find((t) => t.name === "private-whoami");
271
+ expect(whoami?.securitySchemes).toEqual([{ type: "oauth2" }]);
272
+ });
273
+ it("denies a protected tool inside an anonymous JSON-RPC batch", async () => {
274
+ const { jwksUri } = await startJwks();
275
+ const base = await bootMixedServer(jwksUri);
276
+ const res = await fetch(`${base}/mcp`, {
277
+ method: "POST",
278
+ headers: {
279
+ "Content-Type": "application/json",
280
+ Accept: "application/json, text/event-stream",
281
+ },
282
+ body: JSON.stringify([
283
+ {
284
+ jsonrpc: "2.0",
285
+ id: 1,
286
+ method: "tools/call",
287
+ params: { name: "private-whoami", arguments: {} },
288
+ },
289
+ ]),
290
+ });
291
+ expect(res.status).toBe(401);
292
+ const wwwAuthenticate = res.headers.get("www-authenticate") ?? "";
293
+ expect(wwwAuthenticate).toMatch(/^Bearer /);
294
+ expect(wwwAuthenticate).toContain("resource_metadata=");
295
+ const text = await res.text();
296
+ expect(text).not.toContain("client-1");
297
+ });
298
+ it("gates a tool with no declared schemes (secure default)", async () => {
299
+ const { jwksUri } = await startJwks();
300
+ const base = await bootMixedServer(jwksUri);
150
301
  const res = await fetch(`${base}/mcp`, {
151
302
  method: "POST",
152
303
  headers: {
@@ -155,16 +306,173 @@ describe("enforcement: optional", () => {
155
306
  },
156
307
  body: JSON.stringify({
157
308
  jsonrpc: "2.0",
158
- method: "initialize",
159
309
  id: 1,
160
- params: {
161
- protocolVersion: "2025-06-18",
162
- capabilities: {},
163
- clientInfo: { name: "anon", version: "0.0.0" },
164
- },
310
+ method: "tools/call",
311
+ params: { name: "undeclared-whoami", arguments: {} },
312
+ }),
313
+ });
314
+ expect(res.status).toBe(401);
315
+ });
316
+ it("returns 401 + WWW-Authenticate for a protected tool while anonymous", async () => {
317
+ const { jwksUri } = await startJwks();
318
+ const base = await bootMixedServer(jwksUri);
319
+ const res = await fetch(`${base}/mcp`, {
320
+ method: "POST",
321
+ headers: {
322
+ "Content-Type": "application/json",
323
+ Accept: "application/json, text/event-stream",
324
+ },
325
+ body: JSON.stringify({
326
+ jsonrpc: "2.0",
327
+ id: 1,
328
+ method: "tools/call",
329
+ params: { name: "private-whoami", arguments: {} },
330
+ }),
331
+ });
332
+ expect(res.status).toBe(401);
333
+ expect(res.headers.get("www-authenticate")).toMatch(/error="invalid_token"/);
334
+ expect(res.headers.get("www-authenticate")).toMatch(/resource_metadata=/);
335
+ });
336
+ it("returns an in-band mcp/www_authenticate array to ChatGPT (not a transport 401)", async () => {
337
+ const { jwksUri } = await startJwks();
338
+ const base = await bootMixedServer(jwksUri);
339
+ const res = await fetch(`${base}/mcp`, {
340
+ method: "POST",
341
+ headers: {
342
+ "Content-Type": "application/json",
343
+ Accept: "application/json, text/event-stream",
344
+ "User-Agent": "openai-mcp/1.0",
345
+ },
346
+ body: JSON.stringify({
347
+ jsonrpc: "2.0",
348
+ id: 7,
349
+ method: "tools/call",
350
+ params: { name: "private-whoami", arguments: {} },
165
351
  }),
166
352
  });
167
353
  expect(res.status).toBe(200);
354
+ expect(res.headers.get("www-authenticate")).toBeNull();
355
+ const body = (await res.json());
356
+ expect(body.id).toBe(7);
357
+ expect(body.result.isError).toBe(true);
358
+ const challenge = body.result._meta["mcp/www_authenticate"];
359
+ expect(Array.isArray(challenge)).toBe(true);
360
+ expect(challenge[0]).toMatch(/error="invalid_token"/);
361
+ expect(challenge[0]).toMatch(/resource_metadata=/);
362
+ });
363
+ it("fails closed on a ChatGPT batch (transport 401, no in-band challenge)", async () => {
364
+ const { jwksUri } = await startJwks();
365
+ const base = await bootMixedServer(jwksUri);
366
+ const res = await fetch(`${base}/mcp`, {
367
+ method: "POST",
368
+ headers: {
369
+ "Content-Type": "application/json",
370
+ Accept: "application/json, text/event-stream",
371
+ "User-Agent": "openai-mcp/1.0",
372
+ },
373
+ body: JSON.stringify([
374
+ {
375
+ jsonrpc: "2.0",
376
+ id: 1,
377
+ method: "tools/call",
378
+ params: { name: "private-whoami", arguments: {} },
379
+ },
380
+ ]),
381
+ });
382
+ expect(res.status).toBe(401);
383
+ expect(res.headers.get("www-authenticate")).toMatch(/^Bearer /);
384
+ const text = await res.text();
385
+ expect(text).not.toContain("client-1");
386
+ });
387
+ });
388
+ async function bootScopedServer(jwksUri) {
389
+ const { createApp } = await import("../express.js");
390
+ const server = new McpServer({ name: "scoped-auth-test", version: "0.0.0" }, { capabilities: {} }, {
391
+ oauth: {
392
+ baseUrl: "https://app.example.test",
393
+ oauthMetadata: {
394
+ issuer: ISSUER,
395
+ authorization_endpoint: `${ISSUER}/authorize`,
396
+ token_endpoint: `${ISSUER}/token`,
397
+ response_types_supported: ["code"],
398
+ },
399
+ verifier: createJwksVerifier({
400
+ issuer: ISSUER,
401
+ audience: AUDIENCE,
402
+ jwksUri,
403
+ }),
404
+ },
405
+ }).registerTool({
406
+ name: "checkout",
407
+ description: "Needs the checkout scope.",
408
+ inputSchema: {},
409
+ auth: { scopes: ["checkout"] },
410
+ }, () => ({ content: [{ type: "text", text: "ok" }] }));
411
+ const httpServer = http.createServer();
412
+ await createApp({ mcpServer: server, httpServer });
413
+ const listening = http.createServer(server.express);
414
+ await new Promise((resolve) => listening.listen(0, resolve));
415
+ appServer = listening;
416
+ const port = listening.address().port;
417
+ return `http://localhost:${port}`;
418
+ }
419
+ describe("per-tool scope enforcement in fully-authenticated mode", () => {
420
+ it("returns 403 for a valid token missing the tool's scope", async () => {
421
+ const { privateKey, jwksUri } = await startJwks();
422
+ const base = await bootScopedServer(jwksUri);
423
+ const token = await signToken(privateKey, "openid");
424
+ const res = await fetch(`${base}/mcp`, {
425
+ method: "POST",
426
+ headers: {
427
+ "Content-Type": "application/json",
428
+ Accept: "application/json, text/event-stream",
429
+ Authorization: `Bearer ${token}`,
430
+ },
431
+ body: JSON.stringify({
432
+ jsonrpc: "2.0",
433
+ id: 1,
434
+ method: "tools/call",
435
+ params: { name: "checkout", arguments: {} },
436
+ }),
437
+ });
438
+ expect(res.status).toBe(403);
439
+ const header = res.headers.get("www-authenticate");
440
+ expect(header).toMatch(/error="insufficient_scope"/);
441
+ expect(header).toMatch(/scope="checkout"/);
442
+ });
443
+ it("allows a token carrying the tool's scope", async () => {
444
+ const { privateKey, jwksUri } = await startJwks();
445
+ const base = await bootScopedServer(jwksUri);
446
+ const token = await signToken(privateKey, "openid checkout");
447
+ const client = new Client({ name: "test-client", version: "0.0.0" });
448
+ await client.connect(new StreamableHTTPClientTransport(new URL(`${base}/mcp`), {
449
+ requestInit: { headers: { Authorization: `Bearer ${token}` } },
450
+ }));
451
+ const result = (await client.callTool({
452
+ name: "checkout",
453
+ arguments: {},
454
+ }));
455
+ expect(result.content[0]?.text).toBe("ok");
456
+ await client.close();
457
+ });
458
+ });
459
+ describe("auth shorthand validation", () => {
460
+ it("throws when `auth` requires sign-in but no oauth provider is configured", () => {
461
+ expect(() => new McpServer({ name: "t", version: "0" }).registerTool({ name: "x", inputSchema: {}, auth: {} }, () => ({ content: [{ type: "text", text: "" }] }))).toThrow(/no `oauth` provider/);
462
+ });
463
+ it("throws when `auth` sets scopes but no oauth provider is configured", () => {
464
+ expect(() => new McpServer({ name: "t", version: "0" }).registerTool({ name: "x", inputSchema: {}, auth: { scopes: ["checkout"] } }, () => ({ content: [{ type: "text", text: "" }] }))).toThrow(/no `oauth` provider/);
465
+ });
466
+ it("allows `auth: { allowsAnonymous: true }` without an oauth provider", () => {
467
+ expect(() => new McpServer({ name: "t", version: "0" }).registerTool({ name: "x", inputSchema: {}, auth: { allowsAnonymous: true } }, () => ({ content: [{ type: "text", text: "" }] }))).not.toThrow();
468
+ });
469
+ it("prefers `securitySchemes` over `auth` when both are set (untyped path)", () => {
470
+ expect(() => new McpServer({ name: "t", version: "0" }).registerTool({
471
+ name: "x",
472
+ inputSchema: {},
473
+ auth: {},
474
+ securitySchemes: [{ type: "oauth2" }],
475
+ }, () => ({ content: [{ type: "text", text: "" }] }))).not.toThrow();
168
476
  });
169
477
  });
170
478
  describe("oauth config validation", () => {
@@ -179,7 +487,10 @@ describe("oauth config validation", () => {
179
487
  oauth: {
180
488
  baseUrl: "not-a-url",
181
489
  oauthMetadata: validMetadata,
182
- verify: { issuer: ISSUER, audience: AUDIENCE },
490
+ verifier: createJwksVerifier({
491
+ issuer: ISSUER,
492
+ audience: AUDIENCE,
493
+ }),
183
494
  },
184
495
  })).toThrow(/baseUrl must be a valid absolute URL/);
185
496
  });
@@ -1 +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 {\n baseUrl = \"https://app.example.test\",\n enforcement = \"required\",\n }: {\n baseUrl?: string | null;\n enforcement?: \"required\" | \"optional\";\n } = {},\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 enforcement,\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(\"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(\"enforcement: optional\", () => {\n it(\"lets an anonymous /mcp request through\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootServer(jwksUri, { enforcement: \"optional\" });\n\n const res = await fetch(`${base}/mcp`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json, text/event-stream\",\n },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n method: \"initialize\",\n id: 1,\n params: {\n protocolVersion: \"2025-06-18\",\n capabilities: {},\n clientInfo: { name: \"anon\", version: \"0.0.0\" },\n },\n }),\n });\n expect(res.status).toBe(200);\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"]}
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 {\n Client,\n StreamableHTTPClientTransport,\n} from \"@modelcontextprotocol/client\";\nimport type { RequestHandler } from \"express\";\nimport * as jose from \"jose\";\nimport { afterEach, describe, expect, it, vi } from \"vitest\";\nimport { McpServer } from \"../server.js\";\nimport { createJwksVerifier } from \"./verify.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, scope = \"openid email\") {\n return new jose.SignJWT({\n client_id: \"client-1\",\n scope,\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 verifier: createJwksVerifier({\n issuer: ISSUER,\n audience: AUDIENCE,\n jwksUri,\n }),\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.http?.authInfo?.clientId ?? null },\n content: [\n { type: \"text\", text: extra.http?.authInfo?.clientId ?? \"anon\" },\n ],\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\nasync function bootMixedServer(jwksUri: string) {\n const { createApp } = await import(\"../express.js\");\n const server = new McpServer(\n { name: \"mixed-auth-test\", version: \"0.0.0\" },\n { capabilities: {} },\n {\n oauth: {\n baseUrl: \"https://app.example.test\",\n oauthMetadata: {\n issuer: ISSUER,\n authorization_endpoint: `${ISSUER}/authorize`,\n token_endpoint: `${ISSUER}/token`,\n response_types_supported: [\"code\"],\n },\n verifier: createJwksVerifier({\n issuer: ISSUER,\n audience: AUDIENCE,\n jwksUri,\n }),\n },\n },\n )\n .registerTool(\n {\n name: \"public-whoami\",\n description: \"Public.\",\n inputSchema: {},\n auth: { allowsAnonymous: true },\n },\n (_args, extra) => ({\n content: [\n { type: \"text\", text: extra.http?.authInfo?.clientId ?? \"anon\" },\n ],\n }),\n )\n .registerTool(\n {\n name: \"private-whoami\",\n description: \"Private.\",\n inputSchema: {},\n auth: {},\n },\n (_args, extra) => ({\n content: [\n { type: \"text\", text: extra.http?.authInfo?.clientId ?? \"anon\" },\n ],\n }),\n );\n\n server.registerTool(\n {\n name: \"undeclared-whoami\",\n description: \"Registered without auth or securitySchemes.\",\n inputSchema: {},\n },\n (_args, extra) => ({\n content: [\n { type: \"text\", text: extra.http?.authInfo?.clientId ?? \"anon\" },\n ],\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(\"mixed-auth door\", () => {\n it(\"lets an anonymous caller initialize and run a noauth tool\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootMixedServer(jwksUri);\n\n const client = new Client({ name: \"test-client\", version: \"0.0.0\" });\n await client.connect(\n new StreamableHTTPClientTransport(new URL(`${base}/mcp`)),\n );\n\n const result = (await client.callTool({\n name: \"public-whoami\",\n arguments: {},\n })) as unknown as { content: { text: string }[] };\n expect(result.content[0]?.text).toBe(\"anon\");\n\n await client.close();\n });\n\n it(\"exposes securitySchemes at the top level of tools/list (not only _meta)\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootMixedServer(jwksUri);\n const headers = {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json, text/event-stream\",\n };\n await fetch(`${base}/mcp`, {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: 1,\n method: \"initialize\",\n params: {\n protocolVersion: \"2025-06-18\",\n capabilities: {},\n clientInfo: { name: \"c\", version: \"0\" },\n },\n }),\n });\n const res = await fetch(`${base}/mcp`, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ jsonrpc: \"2.0\", id: 2, method: \"tools/list\" }),\n });\n const text = await res.text();\n const json = JSON.parse((text.match(/\\{[\\s\\S]*\\}/) ?? [\"{}\"])[0]) as {\n result: { tools: Array<{ name: string; securitySchemes?: unknown }> };\n };\n const whoami = json.result.tools.find((t) => t.name === \"private-whoami\");\n expect(whoami?.securitySchemes).toEqual([{ type: \"oauth2\" }]);\n });\n\n it(\"denies a protected tool inside an anonymous JSON-RPC batch\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootMixedServer(jwksUri);\n\n const res = await fetch(`${base}/mcp`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json, text/event-stream\",\n },\n body: JSON.stringify([\n {\n jsonrpc: \"2.0\",\n id: 1,\n method: \"tools/call\",\n params: { name: \"private-whoami\", arguments: {} },\n },\n ]),\n });\n expect(res.status).toBe(401);\n const wwwAuthenticate = res.headers.get(\"www-authenticate\") ?? \"\";\n expect(wwwAuthenticate).toMatch(/^Bearer /);\n expect(wwwAuthenticate).toContain(\"resource_metadata=\");\n const text = await res.text();\n expect(text).not.toContain(\"client-1\");\n });\n\n it(\"gates a tool with no declared schemes (secure default)\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootMixedServer(jwksUri);\n\n const res = await fetch(`${base}/mcp`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json, text/event-stream\",\n },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: 1,\n method: \"tools/call\",\n params: { name: \"undeclared-whoami\", arguments: {} },\n }),\n });\n expect(res.status).toBe(401);\n });\n\n it(\"returns 401 + WWW-Authenticate for a protected tool while anonymous\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootMixedServer(jwksUri);\n\n const res = await fetch(`${base}/mcp`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json, text/event-stream\",\n },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: 1,\n method: \"tools/call\",\n params: { name: \"private-whoami\", arguments: {} },\n }),\n });\n expect(res.status).toBe(401);\n expect(res.headers.get(\"www-authenticate\")).toMatch(\n /error=\"invalid_token\"/,\n );\n expect(res.headers.get(\"www-authenticate\")).toMatch(/resource_metadata=/);\n });\n\n it(\"returns an in-band mcp/www_authenticate array to ChatGPT (not a transport 401)\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootMixedServer(jwksUri);\n\n const res = await fetch(`${base}/mcp`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json, text/event-stream\",\n \"User-Agent\": \"openai-mcp/1.0\",\n },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: 7,\n method: \"tools/call\",\n params: { name: \"private-whoami\", arguments: {} },\n }),\n });\n expect(res.status).toBe(200);\n expect(res.headers.get(\"www-authenticate\")).toBeNull();\n const body = (await res.json()) as {\n id: number;\n result: {\n isError: boolean;\n _meta: { \"mcp/www_authenticate\": string[] };\n };\n };\n expect(body.id).toBe(7);\n expect(body.result.isError).toBe(true);\n const challenge = body.result._meta[\"mcp/www_authenticate\"];\n expect(Array.isArray(challenge)).toBe(true);\n expect(challenge[0]).toMatch(/error=\"invalid_token\"/);\n expect(challenge[0]).toMatch(/resource_metadata=/);\n });\n\n it(\"fails closed on a ChatGPT batch (transport 401, no in-band challenge)\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootMixedServer(jwksUri);\n\n const res = await fetch(`${base}/mcp`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json, text/event-stream\",\n \"User-Agent\": \"openai-mcp/1.0\",\n },\n body: JSON.stringify([\n {\n jsonrpc: \"2.0\",\n id: 1,\n method: \"tools/call\",\n params: { name: \"private-whoami\", arguments: {} },\n },\n ]),\n });\n expect(res.status).toBe(401);\n expect(res.headers.get(\"www-authenticate\")).toMatch(/^Bearer /);\n const text = await res.text();\n expect(text).not.toContain(\"client-1\");\n });\n});\n\nasync function bootScopedServer(jwksUri: string) {\n const { createApp } = await import(\"../express.js\");\n const server = new McpServer(\n { name: \"scoped-auth-test\", version: \"0.0.0\" },\n { capabilities: {} },\n {\n oauth: {\n baseUrl: \"https://app.example.test\",\n oauthMetadata: {\n issuer: ISSUER,\n authorization_endpoint: `${ISSUER}/authorize`,\n token_endpoint: `${ISSUER}/token`,\n response_types_supported: [\"code\"],\n },\n verifier: createJwksVerifier({\n issuer: ISSUER,\n audience: AUDIENCE,\n jwksUri,\n }),\n },\n },\n ).registerTool(\n {\n name: \"checkout\",\n description: \"Needs the checkout scope.\",\n inputSchema: {},\n auth: { scopes: [\"checkout\"] },\n },\n () => ({ content: [{ type: \"text\", text: \"ok\" }] }),\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(\"per-tool scope enforcement in fully-authenticated mode\", () => {\n it(\"returns 403 for a valid token missing the tool's scope\", async () => {\n const { privateKey, jwksUri } = await startJwks();\n const base = await bootScopedServer(jwksUri);\n const token = await signToken(privateKey, \"openid\");\n\n const res = await fetch(`${base}/mcp`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json, text/event-stream\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: 1,\n method: \"tools/call\",\n params: { name: \"checkout\", arguments: {} },\n }),\n });\n expect(res.status).toBe(403);\n const header = res.headers.get(\"www-authenticate\");\n expect(header).toMatch(/error=\"insufficient_scope\"/);\n expect(header).toMatch(/scope=\"checkout\"/);\n });\n\n it(\"allows a token carrying the tool's scope\", async () => {\n const { privateKey, jwksUri } = await startJwks();\n const base = await bootScopedServer(jwksUri);\n const token = await signToken(privateKey, \"openid checkout\");\n\n const client = new Client({ name: \"test-client\", version: \"0.0.0\" });\n await client.connect(\n new StreamableHTTPClientTransport(new URL(`${base}/mcp`), {\n requestInit: { headers: { Authorization: `Bearer ${token}` } },\n }),\n );\n\n const result = (await client.callTool({\n name: \"checkout\",\n arguments: {},\n })) as unknown as { isError?: boolean; content: { text: string }[] };\n expect(result.content[0]?.text).toBe(\"ok\");\n\n await client.close();\n });\n});\n\ndescribe(\"auth shorthand validation\", () => {\n it(\"throws when `auth` requires sign-in but no oauth provider is configured\", () => {\n expect(() =>\n new McpServer({ name: \"t\", version: \"0\" }).registerTool(\n { name: \"x\", inputSchema: {}, auth: {} },\n () => ({ content: [{ type: \"text\", text: \"\" }] }),\n ),\n ).toThrow(/no `oauth` provider/);\n });\n\n it(\"throws when `auth` sets scopes but no oauth provider is configured\", () => {\n expect(() =>\n new McpServer({ name: \"t\", version: \"0\" }).registerTool(\n { name: \"x\", inputSchema: {}, auth: { scopes: [\"checkout\"] } },\n () => ({ content: [{ type: \"text\", text: \"\" }] }),\n ),\n ).toThrow(/no `oauth` provider/);\n });\n\n it(\"allows `auth: { allowsAnonymous: true }` without an oauth provider\", () => {\n expect(() =>\n new McpServer({ name: \"t\", version: \"0\" }).registerTool(\n { name: \"x\", inputSchema: {}, auth: { allowsAnonymous: true } },\n () => ({ content: [{ type: \"text\", text: \"\" }] }),\n ),\n ).not.toThrow();\n });\n\n it(\"prefers `securitySchemes` over `auth` when both are set (untyped path)\", () => {\n expect(() =>\n (\n new McpServer({ name: \"t\", version: \"0\" }).registerTool as (\n ...a: unknown[]\n ) => unknown\n )(\n {\n name: \"x\",\n inputSchema: {},\n auth: {},\n securitySchemes: [{ type: \"oauth2\" }],\n },\n () => ({ content: [{ type: \"text\", text: \"\" }] }),\n ),\n ).not.toThrow();\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 verifier: createJwksVerifier({\n issuer: ISSUER,\n audience: AUDIENCE,\n }),\n },\n }),\n ).toThrow(/baseUrl must be a valid absolute URL/);\n });\n});\n"]}
@@ -1,11 +1,46 @@
1
- import type { OAuthTokenVerifier } from "@modelcontextprotocol/sdk/server/auth/provider.js";
1
+ import type { ExtraClaims, TokenVerifier } from "../auth.js";
2
2
  export type JwksVerifyConfig = {
3
3
  /** Expected `iss` claim. */
4
4
  issuer: string;
5
- /** Expected `aud` claim. */
6
- audience: string;
5
+ /** Expected `aud` claim. Omit to skip audience verification — for IdPs that
6
+ * don't bind an audience to their access tokens (e.g. Clerk). */
7
+ audience?: string;
7
8
  /** Defaults to `${issuer}/.well-known/jwks.json`. */
8
9
  jwksUri?: string;
9
10
  };
10
- /** Builds an `OAuthTokenVerifier` validating JWTs against a remote JWKS. Internal, not exported. */
11
- export declare function createJwksVerifier(config: JwksVerifyConfig): OAuthTokenVerifier;
11
+ /**
12
+ * Claims a JWKS-verified token always carries into `extra`, whatever the IdP:
13
+ * `subject` (the renamed `sub`) plus the registered claims that survive the
14
+ * mapping. `client_id`, `scope` and `exp` do not appear here, since they become
15
+ * `AuthInfo` fields instead. Intersected into every claim shape a JWKS verifier
16
+ * resolves.
17
+ *
18
+ * @see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1
19
+ */
20
+ export type RegisteredClaims = {
21
+ /** The token's `sub`, renamed on the way into `extra`. */
22
+ subject?: string;
23
+ /** Issuer of the token. */
24
+ iss?: string;
25
+ /** Audience the token was minted for. */
26
+ aud?: string | string[];
27
+ /** Issued-at, in unix seconds. */
28
+ iat?: number;
29
+ /** Not-valid-before, in unix seconds. */
30
+ nbf?: number;
31
+ /** Unique token id. */
32
+ jti?: string;
33
+ };
34
+ /**
35
+ * Builds a {@link TokenVerifier} that validates JWTs against a remote JWKS.
36
+ *
37
+ * `extra` receives the token's claims with `sub` renamed to `subject`, minus
38
+ * `client_id`, `scope` and `exp`, which map onto `AuthInfo` fields instead. Pass
39
+ * `TExtra` to declare what your IdP puts there; the branded providers do this
40
+ * for you. {@link RegisteredClaims} is always included, since those claims
41
+ * survive the mapping.
42
+ *
43
+ * @typeParam TExtra - Claims the verified token carries in `extra`. An
44
+ * assertion, not a runtime check: nothing rejects a token whose claims differ.
45
+ */
46
+ export declare function createJwksVerifier<TExtra extends ExtraClaims = ExtraClaims>(config: JwksVerifyConfig): TokenVerifier<TExtra & RegisteredClaims>;