skybridge 0.0.0-dev.ba6476a → 0.0.0-dev.ba711bf

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 (90) hide show
  1. package/README.md +1 -0
  2. package/dist/cli/build-helpers.d.ts +5 -1
  3. package/dist/cli/build-helpers.js +30 -10
  4. package/dist/cli/build-helpers.js.map +1 -1
  5. package/dist/cli/build-helpers.test.js +58 -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.d.ts +1 -0
  11. package/dist/cli/build-steps.test.js +52 -0
  12. package/dist/cli/build-steps.test.js.map +1 -0
  13. package/dist/cli/header.d.ts +1 -1
  14. package/dist/cli/run-plain.d.ts +18 -0
  15. package/dist/cli/run-plain.js +89 -0
  16. package/dist/cli/run-plain.js.map +1 -0
  17. package/dist/cli/use-nodemon.d.ts +14 -0
  18. package/dist/cli/use-nodemon.js +71 -60
  19. package/dist/cli/use-nodemon.js.map +1 -1
  20. package/dist/cli/use-typescript-check.d.ts +8 -2
  21. package/dist/cli/use-typescript-check.js +70 -67
  22. package/dist/cli/use-typescript-check.js.map +1 -1
  23. package/dist/commands/build.d.ts +0 -2
  24. package/dist/commands/build.js +2 -61
  25. package/dist/commands/build.js.map +1 -1
  26. package/dist/commands/dev.d.ts +1 -0
  27. package/dist/commands/dev.js +32 -0
  28. package/dist/commands/dev.js.map +1 -1
  29. package/dist/server/auth/providers/custom.d.ts +7 -0
  30. package/dist/server/auth/providers/custom.js +20 -6
  31. package/dist/server/auth/providers/custom.js.map +1 -1
  32. package/dist/server/auth/providers/custom.test.js +53 -4
  33. package/dist/server/auth/providers/custom.test.js.map +1 -1
  34. package/dist/server/auth/providers/descope.js +5 -2
  35. package/dist/server/auth/providers/descope.js.map +1 -1
  36. package/dist/server/auth/providers/descope.test.js +39 -15
  37. package/dist/server/auth/providers/descope.test.js.map +1 -1
  38. package/dist/server/auth/security-schemes.d.ts +10 -0
  39. package/dist/server/auth/security-schemes.js +19 -10
  40. package/dist/server/auth/security-schemes.js.map +1 -1
  41. package/dist/server/auth/security-schemes.test.js +13 -3
  42. package/dist/server/auth/security-schemes.test.js.map +1 -1
  43. package/dist/server/auth/setup.d.ts +2 -1
  44. package/dist/server/auth/setup.js +23 -18
  45. package/dist/server/auth/setup.js.map +1 -1
  46. package/dist/server/auth/setup.test.js +63 -9
  47. package/dist/server/auth/setup.test.js.map +1 -1
  48. package/dist/server/auth/verify.d.ts +4 -2
  49. package/dist/server/auth/verify.js +2 -2
  50. package/dist/server/auth/verify.js.map +1 -1
  51. package/dist/server/auth/verify.test.js +17 -0
  52. package/dist/server/auth/verify.test.js.map +1 -1
  53. package/dist/server/host.d.ts +2 -0
  54. package/dist/server/host.js +11 -0
  55. package/dist/server/host.js.map +1 -0
  56. package/dist/server/host.test.d.ts +1 -0
  57. package/dist/server/host.test.js +13 -0
  58. package/dist/server/host.test.js.map +1 -0
  59. package/dist/server/index.d.ts +2 -1
  60. package/dist/server/index.js +2 -1
  61. package/dist/server/index.js.map +1 -1
  62. package/dist/server/server.d.ts +35 -5
  63. package/dist/server/server.js +105 -21
  64. package/dist/server/server.js.map +1 -1
  65. package/dist/server/skills-integration.test.d.ts +1 -0
  66. package/dist/server/skills-integration.test.js +63 -0
  67. package/dist/server/skills-integration.test.js.map +1 -0
  68. package/dist/server/skills.d.ts +23 -0
  69. package/dist/server/skills.js +178 -0
  70. package/dist/server/skills.js.map +1 -0
  71. package/dist/server/skills.test.d.ts +1 -0
  72. package/dist/server/skills.test.js +210 -0
  73. package/dist/server/skills.test.js.map +1 -0
  74. package/dist/server/view-resource-resolution.test.js +69 -1
  75. package/dist/server/view-resource-resolution.test.js.map +1 -1
  76. package/dist/server/viewsDevServer.js +4 -1
  77. package/dist/server/viewsDevServer.js.map +1 -1
  78. package/dist/test/view.test.js +3 -3
  79. package/dist/test/view.test.js.map +1 -1
  80. package/dist/web/bridges/adaptor.js +22 -2
  81. package/dist/web/bridges/adaptor.js.map +1 -1
  82. package/dist/web/bridges/adaptor.test.js +11 -0
  83. package/dist/web/bridges/adaptor.test.js.map +1 -1
  84. package/dist/web/components/modal-provider.d.ts +1 -1
  85. package/dist/web/data-llm.d.ts +1 -1
  86. package/dist/web/data-llm.test.js +8 -0
  87. package/dist/web/data-llm.test.js.map +1 -1
  88. package/dist/web/plugin/plugin.js +0 -1
  89. package/dist/web/plugin/plugin.js.map +1 -1
  90. package/package.json +21 -20
@@ -1 +1 @@
1
- {"version":3,"file":"security-schemes.test.js","sourceRoot":"","sources":["../../../src/server/auth/security-schemes.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport type { AuthInfo } from \"../auth.js\";\nimport type { SecurityScheme } from \"../server.js\";\nimport {\n authToSecuritySchemes,\n clientPrefersInBandChallenge,\n evaluateSecuritySchemes,\n securitySchemesAllowAnonymous,\n wwwAuthenticateHeader,\n} from \"./security-schemes.js\";\n\ndescribe(\"clientPrefersInBandChallenge\", () => {\n it(\"detects ChatGPT / OpenAI user agents\", () => {\n expect(clientPrefersInBandChallenge(\"openai-mcp/1.0\")).toBe(true);\n expect(clientPrefersInBandChallenge(\"ChatGPT\")).toBe(true);\n expect(clientPrefersInBandChallenge(\"Claude-User/1.0\")).toBe(false);\n expect(clientPrefersInBandChallenge(undefined)).toBe(false);\n });\n});\n\ndescribe(\"authToSecuritySchemes\", () => {\n it(\"maps the three auth shorthands\", () => {\n expect(authToSecuritySchemes(\"public\")).toEqual([\n { type: \"noauth\" },\n { type: \"oauth2\" },\n ]);\n expect(authToSecuritySchemes(\"required\")).toEqual([{ type: \"oauth2\" }]);\n expect(authToSecuritySchemes({ scopes: [\"checkout\"] })).toEqual([\n { type: \"oauth2\", scopes: [\"checkout\"] },\n ]);\n });\n});\n\nconst authInfo = (scopes: string[]): AuthInfo => ({\n token: \"t\",\n clientId: \"c\",\n scopes,\n});\n\ndescribe(\"securitySchemesAllowAnonymous\", () => {\n it(\"is true only when a noauth scheme is present\", () => {\n expect(securitySchemesAllowAnonymous([{ type: \"noauth\" }])).toBe(true);\n expect(\n securitySchemesAllowAnonymous([{ type: \"noauth\" }, { type: \"oauth2\" }]),\n ).toBe(true);\n expect(securitySchemesAllowAnonymous([{ type: \"oauth2\" }])).toBe(false);\n expect(securitySchemesAllowAnonymous(undefined)).toBe(false);\n });\n});\n\ndescribe(\"evaluateSecuritySchemes\", () => {\n it(\"lets anonymous callers through a noauth tool\", () => {\n const schemes: SecurityScheme[] = [{ type: \"noauth\" }, { type: \"oauth2\" }];\n expect(evaluateSecuritySchemes(schemes, undefined)).toBeUndefined();\n });\n\n it(\"fails an oauth2-only tool with no token (invalid_token / 401)\", () => {\n expect(\n evaluateSecuritySchemes([{ type: \"oauth2\" }], undefined)?.error,\n ).toBe(\"invalid_token\");\n });\n\n it(\"fails when the token is missing a required scope (insufficient_scope / 403)\", () => {\n const schemes: SecurityScheme[] = [\n { type: \"oauth2\", scopes: [\"checkout\"] },\n ];\n expect(evaluateSecuritySchemes(schemes, authInfo([\"openid\"]))?.error).toBe(\n \"insufficient_scope\",\n );\n });\n\n it(\"passes when the token carries every required scope\", () => {\n const schemes: SecurityScheme[] = [\n { type: \"oauth2\", scopes: [\"checkout\"] },\n ];\n expect(\n evaluateSecuritySchemes(schemes, authInfo([\"openid\", \"checkout\"])),\n ).toBeUndefined();\n });\n\n it(\"treats an undeclared tool as auth-required, satisfied by any valid token\", () => {\n expect(evaluateSecuritySchemes(undefined, undefined)?.error).toBe(\n \"invalid_token\",\n );\n expect(evaluateSecuritySchemes(undefined, authInfo([]))).toBeUndefined();\n });\n\n it(\"carries the tool's required scopes on the failure\", () => {\n const schemes: SecurityScheme[] = [\n { type: \"oauth2\", scopes: [\"checkout\"] },\n ];\n expect(evaluateSecuritySchemes(schemes, undefined)?.scopes).toEqual([\n \"checkout\",\n ]);\n expect(\n evaluateSecuritySchemes(schemes, authInfo([\"openid\"]))?.scopes,\n ).toEqual([\"checkout\"]);\n });\n});\n\ndescribe(\"wwwAuthenticateHeader\", () => {\n it(\"names the required scopes so the client can step up\", () => {\n const header = wwwAuthenticateHeader({\n error: \"insufficient_scope\",\n description: \"Missing required scope for this tool.\",\n scopes: [\"checkout\"],\n });\n expect(header).toContain('error=\"insufficient_scope\"');\n expect(header).toContain('scope=\"checkout\"');\n });\n\n it(\"omits the scope parameter when there are none\", () => {\n const header = wwwAuthenticateHeader({\n error: \"invalid_token\",\n description: \"Sign in to use this tool.\",\n scopes: [],\n });\n expect(header).not.toContain(\"scope=\");\n });\n});\n"]}
1
+ {"version":3,"file":"security-schemes.test.js","sourceRoot":"","sources":["../../../src/server/auth/security-schemes.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport type { AuthInfo } from \"../auth.js\";\nimport type { SecurityScheme } from \"../server.js\";\nimport {\n authToSecuritySchemes,\n clientPrefersInBandChallenge,\n evaluateSecuritySchemes,\n securitySchemesAllowAnonymous,\n wwwAuthenticateHeader,\n} from \"./security-schemes.js\";\n\ndescribe(\"clientPrefersInBandChallenge\", () => {\n it(\"detects ChatGPT / OpenAI user agents\", () => {\n expect(clientPrefersInBandChallenge(\"openai-mcp/1.0\")).toBe(true);\n expect(clientPrefersInBandChallenge(\"ChatGPT\")).toBe(true);\n expect(clientPrefersInBandChallenge(\"Claude-User/1.0\")).toBe(false);\n expect(clientPrefersInBandChallenge(undefined)).toBe(false);\n });\n});\n\ndescribe(\"authToSecuritySchemes\", () => {\n it(\"maps the auth shorthand\", () => {\n expect(authToSecuritySchemes({ allowsAnonymous: true })).toEqual([\n { type: \"noauth\" },\n { type: \"oauth2\" },\n ]);\n expect(authToSecuritySchemes({})).toEqual([{ type: \"oauth2\" }]);\n expect(authToSecuritySchemes({ scopes: [\"checkout\"] })).toEqual([\n { type: \"oauth2\", scopes: [\"checkout\"] },\n ]);\n expect(\n authToSecuritySchemes({ allowsAnonymous: true, scopes: [\"checkout\"] }),\n ).toEqual([{ type: \"noauth\" }, { type: \"oauth2\", scopes: [\"checkout\"] }]);\n });\n});\n\nconst authInfo = (scopes: string[]): AuthInfo => ({\n token: \"t\",\n clientId: \"c\",\n scopes,\n});\n\ndescribe(\"securitySchemesAllowAnonymous\", () => {\n it(\"is true only when a noauth scheme is present\", () => {\n expect(securitySchemesAllowAnonymous([{ type: \"noauth\" }])).toBe(true);\n expect(\n securitySchemesAllowAnonymous([{ type: \"noauth\" }, { type: \"oauth2\" }]),\n ).toBe(true);\n expect(securitySchemesAllowAnonymous([{ type: \"oauth2\" }])).toBe(false);\n expect(securitySchemesAllowAnonymous(undefined)).toBe(false);\n });\n});\n\ndescribe(\"evaluateSecuritySchemes\", () => {\n it(\"lets anonymous callers through a noauth tool\", () => {\n const schemes: SecurityScheme[] = [{ type: \"noauth\" }, { type: \"oauth2\" }];\n expect(evaluateSecuritySchemes(schemes, undefined)).toBeUndefined();\n });\n\n it(\"enforces oauth2 scopes on a public tool once a token is present\", () => {\n const schemes: SecurityScheme[] = [\n { type: \"noauth\" },\n { type: \"oauth2\", scopes: [\"checkout\"] },\n ];\n expect(evaluateSecuritySchemes(schemes, undefined)).toBeUndefined();\n expect(evaluateSecuritySchemes(schemes, authInfo([\"openid\"]))?.error).toBe(\n \"insufficient_scope\",\n );\n expect(\n evaluateSecuritySchemes(schemes, authInfo([\"checkout\"])),\n ).toBeUndefined();\n });\n\n it(\"fails an oauth2-only tool with no token (invalid_token / 401)\", () => {\n expect(\n evaluateSecuritySchemes([{ type: \"oauth2\" }], undefined)?.error,\n ).toBe(\"invalid_token\");\n });\n\n it(\"fails when the token is missing a required scope (insufficient_scope / 403)\", () => {\n const schemes: SecurityScheme[] = [\n { type: \"oauth2\", scopes: [\"checkout\"] },\n ];\n expect(evaluateSecuritySchemes(schemes, authInfo([\"openid\"]))?.error).toBe(\n \"insufficient_scope\",\n );\n });\n\n it(\"passes when the token carries every required scope\", () => {\n const schemes: SecurityScheme[] = [\n { type: \"oauth2\", scopes: [\"checkout\"] },\n ];\n expect(\n evaluateSecuritySchemes(schemes, authInfo([\"openid\", \"checkout\"])),\n ).toBeUndefined();\n });\n\n it(\"treats an undeclared tool as auth-required, satisfied by any valid token\", () => {\n expect(evaluateSecuritySchemes(undefined, undefined)?.error).toBe(\n \"invalid_token\",\n );\n expect(evaluateSecuritySchemes(undefined, authInfo([]))).toBeUndefined();\n });\n\n it(\"carries the tool's required scopes on the failure\", () => {\n const schemes: SecurityScheme[] = [\n { type: \"oauth2\", scopes: [\"checkout\"] },\n ];\n expect(evaluateSecuritySchemes(schemes, undefined)?.scopes).toEqual([\n \"checkout\",\n ]);\n expect(\n evaluateSecuritySchemes(schemes, authInfo([\"openid\"]))?.scopes,\n ).toEqual([\"checkout\"]);\n });\n});\n\ndescribe(\"wwwAuthenticateHeader\", () => {\n it(\"names the required scopes so the client can step up\", () => {\n const header = wwwAuthenticateHeader({\n error: \"insufficient_scope\",\n description: \"Missing required scope for this tool.\",\n scopes: [\"checkout\"],\n });\n expect(header).toContain('error=\"insufficient_scope\"');\n expect(header).toContain('scope=\"checkout\"');\n });\n\n it(\"omits the scope parameter when there are none\", () => {\n const header = wwwAuthenticateHeader({\n error: \"invalid_token\",\n description: \"Sign in to use this tool.\",\n scopes: [],\n });\n expect(header).not.toContain(\"scope=\");\n });\n});\n"]}
@@ -1,5 +1,6 @@
1
1
  import type { Express } from "express";
2
2
  import type { SecurityScheme } from "../server.js";
3
3
  import type { OAuthConfig } from "./index.js";
4
+ export type ResourceMetadataUrlResolver = (getHeader: (key: string) => string | undefined) => string;
4
5
  /** Mounts the well-known OAuth metadata and bearer auth on `/mcp`. */
5
- export declare function setupOAuth(app: Express, config: OAuthConfig, schemesByTool: Map<string, SecurityScheme[] | undefined>): void;
6
+ export declare function setupOAuth(app: Express, config: OAuthConfig, schemesByTool: Map<string, SecurityScheme[] | undefined>): ResourceMetadataUrlResolver;
@@ -2,7 +2,7 @@ import { getOAuthProtectedResourceMetadataUrl, mcpAuthMetadataRouter, } from "@m
2
2
  import cors from "cors";
3
3
  import { optionalBearerAuth, requireBearerAuth } from "../auth.js";
4
4
  import { resolveServerOrigin } from "../requestOrigin.js";
5
- import { clientPrefersInBandChallenge, evaluateSecuritySchemes, httpStatusForFailure, securitySchemesAllowAnonymous, wwwAuthenticateHeader, } from "./security-schemes.js";
5
+ import { clientPrefersInBandChallenge, evaluateSecuritySchemes, httpStatusForFailure, inBandChallengeResult, securitySchemesAllowAnonymous, wwwAuthenticateHeader, } from "./security-schemes.js";
6
6
  import { createJwksVerifier } from "./verify.js";
7
7
  /** Mounts the well-known OAuth metadata and bearer auth on `/mcp`. */
8
8
  export function setupOAuth(app, config, schemesByTool) {
@@ -38,50 +38,55 @@ export function setupOAuth(app, config, schemesByTool) {
38
38
  // No baseUrl: resolve the resource origin per request from forwarded headers
39
39
  // (same precedence the framework uses for view serverUrl). Origin headers are
40
40
  // pathless, so the PRM path stays at root, matching the SDK's static layout.
41
- const resolveOrigin = (req) => new URL(resolveServerOrigin((key) => req.get(key)));
41
+ const resolveOrigin = (getHeader) => new URL(resolveServerOrigin(getHeader));
42
42
  app.use("/.well-known/oauth-authorization-server", cors(), (_req, res) => void res.json(config.oauthMetadata));
43
43
  app.use("/.well-known/oauth-protected-resource", cors(), (req, res) => {
44
44
  res.json({
45
- resource: resolveOrigin(req).href,
45
+ resource: resolveOrigin((key) => req.get(key)).href,
46
46
  authorization_servers: [config.oauthMetadata.issuer],
47
47
  scopes_supported: config.scopesSupported,
48
48
  });
49
49
  });
50
- resourceMetadataUrl = (req) => getOAuthProtectedResourceMetadataUrl(resolveOrigin(req));
50
+ resourceMetadataUrl = (getHeader) => getOAuthProtectedResourceMetadataUrl(resolveOrigin(getHeader));
51
51
  }
52
52
  app.use("/mcp", (req, res, next) => bearer({
53
53
  verifier,
54
54
  requiredScopes: config.requiredScopes,
55
- resourceMetadataUrl: resourceMetadataUrl(req),
55
+ resourceMetadataUrl: resourceMetadataUrl((key) => req.get(key)),
56
56
  })(req, res, next));
57
57
  app.use("/mcp", (req, res, next) => {
58
58
  const body = req.body;
59
- const tool = body?.params?.name;
60
- if (body?.method !== "tools/call" || !tool || !schemesByTool.has(tool)) {
61
- return next();
59
+ const calls = Array.isArray(body) ? body : [body];
60
+ const auth = req.auth;
61
+ let failure;
62
+ for (const call of calls) {
63
+ const tool = call?.params?.name;
64
+ if (call?.method === "tools/call" && tool && schemesByTool.has(tool)) {
65
+ failure = evaluateSecuritySchemes(schemesByTool.get(tool), auth);
66
+ if (failure) {
67
+ break;
68
+ }
69
+ }
62
70
  }
63
- const failure = evaluateSecuritySchemes(schemesByTool.get(tool), req.auth);
64
71
  if (!failure) {
65
72
  return next();
66
73
  }
67
- const challenge = wwwAuthenticateHeader(failure, resourceMetadataUrl(req));
68
- if (clientPrefersInBandChallenge(req.get("user-agent"))) {
74
+ const url = resourceMetadataUrl((key) => req.get(key));
75
+ if (!Array.isArray(body) &&
76
+ clientPrefersInBandChallenge(req.get("user-agent"))) {
69
77
  res.json({
70
78
  jsonrpc: "2.0",
71
- id: body.id ?? null,
72
- result: {
73
- content: [{ type: "text", text: failure.description }],
74
- isError: true,
75
- _meta: { "mcp/www_authenticate": [challenge] },
76
- },
79
+ id: body?.id ?? null,
80
+ result: inBandChallengeResult(failure, url),
77
81
  });
78
82
  return;
79
83
  }
80
- res.set("WWW-Authenticate", challenge);
84
+ res.set("WWW-Authenticate", wwwAuthenticateHeader(failure, url));
81
85
  res.status(httpStatusForFailure(failure)).json({
82
86
  error: failure.error,
83
87
  error_description: failure.description,
84
88
  });
85
89
  });
90
+ return resourceMetadataUrl;
86
91
  }
87
92
  //# sourceMappingURL=setup.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../src/server/auth/setup.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BearerAuthMiddlewareOptions } from \"@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js\";\nimport {\n getOAuthProtectedResourceMetadataUrl,\n mcpAuthMetadataRouter,\n} from \"@modelcontextprotocol/sdk/server/auth/router.js\";\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 securitySchemesAllowAnonymous,\n wwwAuthenticateHeader,\n} from \"./security-schemes.js\";\nimport { createJwksVerifier } from \"./verify.js\";\n\n/** Mounts the well-known OAuth metadata and bearer auth on `/mcp`. */\nexport function setupOAuth(\n app: Express,\n config: OAuthConfig,\n schemesByTool: Map<string, SecurityScheme[] | undefined>,\n): void {\n if (!config.verify?.issuer) {\n throw new Error(\"oauth.verify requires an `issuer`\");\n }\n\n const acceptsAnonymous = () =>\n [...schemesByTool.values()].some(securitySchemesAllowAnonymous);\n const verifier = createJwksVerifier(config.verify);\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: (req: Request) => string;\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 = (req: Request) =>\n new URL(resolveServerOrigin((key) => req.get(key)));\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 resourceMetadataUrl = (req) =>\n getOAuthProtectedResourceMetadataUrl(resolveOrigin(req));\n }\n\n app.use(\"/mcp\", (req, res, next) =>\n bearer({\n verifier,\n requiredScopes: config.requiredScopes,\n resourceMetadataUrl: resourceMetadataUrl(req),\n })(req, res, next),\n );\n\n app.use(\"/mcp\", (req, res, next) => {\n const body = req.body as\n | {\n id?: string | number | null;\n method?: string;\n params?: { name?: string };\n }\n | undefined;\n const tool = body?.params?.name;\n if (body?.method !== \"tools/call\" || !tool || !schemesByTool.has(tool)) {\n return next();\n }\n const failure = evaluateSecuritySchemes(\n schemesByTool.get(tool),\n (req as Request & { auth?: AuthInfo }).auth,\n );\n if (!failure) {\n return next();\n }\n const challenge = wwwAuthenticateHeader(failure, resourceMetadataUrl(req));\n if (clientPrefersInBandChallenge(req.get(\"user-agent\"))) {\n res.json({\n jsonrpc: \"2.0\",\n id: body.id ?? null,\n result: {\n content: [{ type: \"text\", text: failure.description }],\n isError: true,\n _meta: { \"mcp/www_authenticate\": [challenge] },\n },\n });\n return;\n }\n res.set(\"WWW-Authenticate\", challenge);\n res.status(httpStatusForFailure(failure)).json({\n error: failure.error,\n error_description: failure.description,\n });\n });\n}\n"]}
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../src/server/auth/setup.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BearerAuthMiddlewareOptions } from \"@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js\";\nimport {\n getOAuthProtectedResourceMetadataUrl,\n mcpAuthMetadataRouter,\n} from \"@modelcontextprotocol/sdk/server/auth/router.js\";\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\";\nimport { createJwksVerifier } from \"./verify.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,\n schemesByTool: Map<string, SecurityScheme[] | undefined>,\n): ResourceMetadataUrlResolver {\n if (!config.verify?.issuer) {\n throw new Error(\"oauth.verify requires an `issuer`\");\n }\n\n const acceptsAnonymous = () =>\n [...schemesByTool.values()].some(securitySchemesAllowAnonymous);\n const verifier = createJwksVerifier(config.verify);\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"]}
@@ -183,7 +183,7 @@ async function bootMixedServer(jwksUri) {
183
183
  name: "public-whoami",
184
184
  description: "Public.",
185
185
  inputSchema: {},
186
- auth: "public",
186
+ auth: { allowsAnonymous: true },
187
187
  }, (_args, extra) => ({
188
188
  content: [{ type: "text", text: extra.authInfo?.clientId ?? "anon" }],
189
189
  }))
@@ -191,7 +191,13 @@ async function bootMixedServer(jwksUri) {
191
191
  name: "private-whoami",
192
192
  description: "Private.",
193
193
  inputSchema: {},
194
- auth: "required",
194
+ auth: {},
195
+ }, (_args, extra) => ({
196
+ content: [{ type: "text", text: extra.authInfo?.clientId ?? "anon" }],
197
+ }));
198
+ server.registerTool("legacy-whoami", {
199
+ description: "Registered via the legacy string overload.",
200
+ inputSchema: {},
195
201
  }, (_args, extra) => ({
196
202
  content: [{ type: "text", text: extra.authInfo?.clientId ?? "anon" }],
197
203
  }));
@@ -265,9 +271,30 @@ describe("mixed-auth door", () => {
265
271
  },
266
272
  ]),
267
273
  });
274
+ expect(res.status).toBe(401);
275
+ const wwwAuthenticate = res.headers.get("www-authenticate") ?? "";
276
+ expect(wwwAuthenticate).toMatch(/^Bearer /);
277
+ expect(wwwAuthenticate).toContain("resource_metadata=");
268
278
  const text = await res.text();
269
279
  expect(text).not.toContain("client-1");
270
- expect(text).toContain("Sign in to use this tool.");
280
+ });
281
+ it("gates a tool registered via the legacy string overload (secure default)", async () => {
282
+ const { jwksUri } = await startJwks();
283
+ const base = await bootMixedServer(jwksUri);
284
+ const res = await fetch(`${base}/mcp`, {
285
+ method: "POST",
286
+ headers: {
287
+ "Content-Type": "application/json",
288
+ Accept: "application/json, text/event-stream",
289
+ },
290
+ body: JSON.stringify({
291
+ jsonrpc: "2.0",
292
+ id: 1,
293
+ method: "tools/call",
294
+ params: { name: "legacy-whoami", arguments: {} },
295
+ }),
296
+ });
297
+ expect(res.status).toBe(401);
271
298
  });
272
299
  it("returns 401 + WWW-Authenticate for a protected tool while anonymous", async () => {
273
300
  const { jwksUri } = await startJwks();
@@ -316,6 +343,30 @@ describe("mixed-auth door", () => {
316
343
  expect(challenge[0]).toMatch(/error="invalid_token"/);
317
344
  expect(challenge[0]).toMatch(/resource_metadata=/);
318
345
  });
346
+ it("fails closed on a ChatGPT batch (transport 401, no in-band challenge)", async () => {
347
+ const { jwksUri } = await startJwks();
348
+ const base = await bootMixedServer(jwksUri);
349
+ const res = await fetch(`${base}/mcp`, {
350
+ method: "POST",
351
+ headers: {
352
+ "Content-Type": "application/json",
353
+ Accept: "application/json, text/event-stream",
354
+ "User-Agent": "openai-mcp/1.0",
355
+ },
356
+ body: JSON.stringify([
357
+ {
358
+ jsonrpc: "2.0",
359
+ id: 1,
360
+ method: "tools/call",
361
+ params: { name: "private-whoami", arguments: {} },
362
+ },
363
+ ]),
364
+ });
365
+ expect(res.status).toBe(401);
366
+ expect(res.headers.get("www-authenticate")).toMatch(/^Bearer /);
367
+ const text = await res.text();
368
+ expect(text).not.toContain("client-1");
369
+ });
319
370
  });
320
371
  async function bootScopedServer(jwksUri) {
321
372
  const { createApp } = await import("../express.js");
@@ -385,17 +436,20 @@ describe("per-tool scope enforcement in fully-authenticated mode", () => {
385
436
  });
386
437
  });
387
438
  describe("auth shorthand validation", () => {
388
- it("throws when `auth` is set but no oauth provider is configured", () => {
389
- expect(() => new McpServer({ name: "t", version: "0" }).registerTool({ name: "x", inputSchema: {}, auth: "required" }, () => ({ content: [{ type: "text", text: "" }] }))).toThrow(/no `oauth` provider/);
439
+ it("throws when `auth` requires sign-in but no oauth provider is configured", () => {
440
+ expect(() => new McpServer({ name: "t", version: "0" }).registerTool({ name: "x", inputSchema: {}, auth: {} }, () => ({ content: [{ type: "text", text: "" }] }))).toThrow(/no `oauth` provider/);
441
+ });
442
+ it("throws when `auth` sets scopes but no oauth provider is configured", () => {
443
+ expect(() => new McpServer({ name: "t", version: "0" }).registerTool({ name: "x", inputSchema: {}, auth: { scopes: ["checkout"] } }, () => ({ content: [{ type: "text", text: "" }] }))).toThrow(/no `oauth` provider/);
390
444
  });
391
- it("allows `auth: \"public\"` without an oauth provider", () => {
392
- expect(() => new McpServer({ name: "t", version: "0" }).registerTool({ name: "x", inputSchema: {}, auth: "public" }, () => ({ content: [{ type: "text", text: "" }] }))).not.toThrow();
445
+ it("allows `auth: { allowsAnonymous: true }` without an oauth provider", () => {
446
+ expect(() => new McpServer({ name: "t", version: "0" }).registerTool({ name: "x", inputSchema: {}, auth: { allowsAnonymous: true } }, () => ({ content: [{ type: "text", text: "" }] }))).not.toThrow();
393
447
  });
394
- it("prefers `securitySchemes` over `auth` when both are set", () => {
448
+ it("prefers `securitySchemes` over `auth` when both are set (untyped path)", () => {
395
449
  expect(() => new McpServer({ name: "t", version: "0" }).registerTool({
396
450
  name: "x",
397
451
  inputSchema: {},
398
- auth: "required",
452
+ auth: {},
399
453
  securitySchemes: [{ type: "oauth2" }],
400
454
  }, () => ({ content: [{ type: "text", text: "" }] }))).not.toThrow();
401
455
  });
@@ -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, 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 verify: { issuer: ISSUER, audience: AUDIENCE, jwksUri },\n scopesSupported: [\"openid\", \"email\"],\n requiredScopes: [\"openid\"],\n },\n },\n ).registerTool(\n {\n name: \"whoami\",\n description: \"Returns the caller identity.\",\n inputSchema: {},\n },\n (_args, extra) => ({\n structuredContent: { clientId: extra.authInfo?.clientId ?? null },\n content: [{ type: \"text\", text: extra.authInfo?.clientId ?? \"anon\" }],\n }),\n );\n\n const httpServer = http.createServer();\n await createApp({ mcpServer: server, httpServer });\n const listening = http.createServer(server.express);\n await new Promise<void>((resolve) => listening.listen(0, resolve));\n appServer = listening;\n const port = (listening.address() as { port: number }).port;\n return `http://localhost:${port}`;\n}\n\ndescribe(\"setupOAuth wiring\", () => {\n it(\"serves protected-resource metadata\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootServer(jwksUri);\n\n const res = await fetch(`${base}/.well-known/oauth-protected-resource`);\n expect(res.status).toBe(200);\n const body = (await res.json()) as {\n resource: string;\n authorization_servers: string[];\n scopes_supported: string[];\n };\n expect(body.resource).toBe(\"https://app.example.test/\");\n expect(body.authorization_servers).toContain(ISSUER);\n expect(body.scopes_supported).toEqual([\"openid\", \"email\"]);\n });\n\n it(\"rejects /mcp without a token (401 + WWW-Authenticate)\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootServer(jwksUri);\n\n const res = await fetch(`${base}/mcp`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ jsonrpc: \"2.0\", method: \"initialize\", id: 1 }),\n });\n expect(res.status).toBe(401);\n expect(res.headers.get(\"www-authenticate\")).toMatch(/resource_metadata=/);\n });\n\n it(\"threads authInfo into the tool handler for a valid token\", async () => {\n const { privateKey, jwksUri } = await startJwks();\n const base = await bootServer(jwksUri);\n const token = await signToken(privateKey);\n\n const client = new Client({ name: \"test-client\", version: \"0.0.0\" });\n const transport = new StreamableHTTPClientTransport(\n new URL(`${base}/mcp`),\n { requestInit: { headers: { Authorization: `Bearer ${token}` } } },\n );\n await client.connect(transport);\n\n const result = (await client.callTool({\n name: \"whoami\",\n arguments: {},\n })) as unknown as {\n content: { type: string; text: string }[];\n };\n expect(result.content[0]?.text).toBe(\"client-1\");\n\n await client.close();\n });\n});\n\ndescribe(\"baseUrl inferred from headers\", () => {\n it(\"derives the resource origin from x-forwarded-host\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootServer(jwksUri, { baseUrl: null });\n\n const res = await fetch(`${base}/.well-known/oauth-protected-resource`, {\n headers: { \"x-forwarded-host\": \"infer.example.test\" },\n });\n expect(res.status).toBe(200);\n const body = (await res.json()) as { resource: string };\n expect(body.resource).toBe(\"https://infer.example.test/\");\n });\n\n it(\"uses the first hop of a forwarded-host chain\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootServer(jwksUri, { baseUrl: null });\n\n const res = await fetch(`${base}/.well-known/oauth-protected-resource`, {\n headers: {\n \"x-forwarded-host\": \"public.example, internal.local\",\n \"x-forwarded-proto\": \"https, http\",\n },\n });\n expect(res.status).toBe(200);\n const body = (await res.json()) as { resource: string };\n expect(body.resource).toBe(\"https://public.example/\");\n });\n\n it(\"ignores the client Origin header, using Host instead\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootServer(jwksUri, { baseUrl: null });\n\n const res = await fetch(`${base}/.well-known/oauth-protected-resource`, {\n headers: { origin: \"https://chatgpt.com\" },\n });\n expect(res.status).toBe(200);\n const body = (await res.json()) as { resource: string };\n expect(body.resource).toMatch(/^http:\\/\\/(localhost|127\\.0\\.0\\.1):/);\n });\n\n it(\"points the 401 WWW-Authenticate at the inferred host\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootServer(jwksUri, { baseUrl: null });\n\n const res = await fetch(`${base}/mcp`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-forwarded-host\": \"infer.example.test\",\n },\n body: JSON.stringify({ jsonrpc: \"2.0\", method: \"initialize\", id: 1 }),\n });\n expect(res.status).toBe(401);\n expect(res.headers.get(\"www-authenticate\")).toMatch(\n /resource_metadata=\"https:\\/\\/infer\\.example\\.test\\//,\n );\n });\n});\n\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 verify: { issuer: ISSUER, audience: AUDIENCE, jwksUri },\n },\n },\n )\n .registerTool(\n {\n name: \"public-whoami\",\n description: \"Public.\",\n inputSchema: {},\n auth: \"public\",\n },\n (_args, extra) => ({\n content: [{ type: \"text\", text: extra.authInfo?.clientId ?? \"anon\" }],\n }),\n )\n .registerTool(\n {\n name: \"private-whoami\",\n description: \"Private.\",\n inputSchema: {},\n auth: \"required\",\n },\n (_args, extra) => ({\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(\"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 const text = await res.text();\n expect(text).not.toContain(\"client-1\");\n expect(text).toContain(\"Sign in to use this tool.\");\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\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 verify: { issuer: ISSUER, audience: AUDIENCE, jwksUri },\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` is set but no oauth provider is configured\", () => {\n expect(() =>\n new McpServer({ name: \"t\", version: \"0\" }).registerTool(\n { name: \"x\", inputSchema: {}, auth: \"required\" },\n () => ({ content: [{ type: \"text\", text: \"\" }] }),\n ),\n ).toThrow(/no `oauth` provider/);\n });\n\n it(\"allows `auth: \\\"public\\\"` without an oauth provider\", () => {\n expect(() =>\n new McpServer({ name: \"t\", version: \"0\" }).registerTool(\n { name: \"x\", inputSchema: {}, auth: \"public\" },\n () => ({ content: [{ type: \"text\", text: \"\" }] }),\n ),\n ).not.toThrow();\n });\n\n it(\"prefers `securitySchemes` over `auth` when both are set\", () => {\n expect(() =>\n new McpServer({ name: \"t\", version: \"0\" }).registerTool(\n {\n name: \"x\",\n inputSchema: {},\n auth: \"required\",\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 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 { 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, 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 verify: { issuer: ISSUER, audience: AUDIENCE, jwksUri },\n scopesSupported: [\"openid\", \"email\"],\n requiredScopes: [\"openid\"],\n },\n },\n ).registerTool(\n {\n name: \"whoami\",\n description: \"Returns the caller identity.\",\n inputSchema: {},\n },\n (_args, extra) => ({\n structuredContent: { clientId: extra.authInfo?.clientId ?? null },\n content: [{ type: \"text\", text: extra.authInfo?.clientId ?? \"anon\" }],\n }),\n );\n\n const httpServer = http.createServer();\n await createApp({ mcpServer: server, httpServer });\n const listening = http.createServer(server.express);\n await new Promise<void>((resolve) => listening.listen(0, resolve));\n appServer = listening;\n const port = (listening.address() as { port: number }).port;\n return `http://localhost:${port}`;\n}\n\ndescribe(\"setupOAuth wiring\", () => {\n it(\"serves protected-resource metadata\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootServer(jwksUri);\n\n const res = await fetch(`${base}/.well-known/oauth-protected-resource`);\n expect(res.status).toBe(200);\n const body = (await res.json()) as {\n resource: string;\n authorization_servers: string[];\n scopes_supported: string[];\n };\n expect(body.resource).toBe(\"https://app.example.test/\");\n expect(body.authorization_servers).toContain(ISSUER);\n expect(body.scopes_supported).toEqual([\"openid\", \"email\"]);\n });\n\n it(\"rejects /mcp without a token (401 + WWW-Authenticate)\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootServer(jwksUri);\n\n const res = await fetch(`${base}/mcp`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ jsonrpc: \"2.0\", method: \"initialize\", id: 1 }),\n });\n expect(res.status).toBe(401);\n expect(res.headers.get(\"www-authenticate\")).toMatch(/resource_metadata=/);\n });\n\n it(\"threads authInfo into the tool handler for a valid token\", async () => {\n const { privateKey, jwksUri } = await startJwks();\n const base = await bootServer(jwksUri);\n const token = await signToken(privateKey);\n\n const client = new Client({ name: \"test-client\", version: \"0.0.0\" });\n const transport = new StreamableHTTPClientTransport(\n new URL(`${base}/mcp`),\n { requestInit: { headers: { Authorization: `Bearer ${token}` } } },\n );\n await client.connect(transport);\n\n const result = (await client.callTool({\n name: \"whoami\",\n arguments: {},\n })) as unknown as {\n content: { type: string; text: string }[];\n };\n expect(result.content[0]?.text).toBe(\"client-1\");\n\n await client.close();\n });\n});\n\ndescribe(\"baseUrl inferred from headers\", () => {\n it(\"derives the resource origin from x-forwarded-host\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootServer(jwksUri, { baseUrl: null });\n\n const res = await fetch(`${base}/.well-known/oauth-protected-resource`, {\n headers: { \"x-forwarded-host\": \"infer.example.test\" },\n });\n expect(res.status).toBe(200);\n const body = (await res.json()) as { resource: string };\n expect(body.resource).toBe(\"https://infer.example.test/\");\n });\n\n it(\"uses the first hop of a forwarded-host chain\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootServer(jwksUri, { baseUrl: null });\n\n const res = await fetch(`${base}/.well-known/oauth-protected-resource`, {\n headers: {\n \"x-forwarded-host\": \"public.example, internal.local\",\n \"x-forwarded-proto\": \"https, http\",\n },\n });\n expect(res.status).toBe(200);\n const body = (await res.json()) as { resource: string };\n expect(body.resource).toBe(\"https://public.example/\");\n });\n\n it(\"ignores the client Origin header, using Host instead\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootServer(jwksUri, { baseUrl: null });\n\n const res = await fetch(`${base}/.well-known/oauth-protected-resource`, {\n headers: { origin: \"https://chatgpt.com\" },\n });\n expect(res.status).toBe(200);\n const body = (await res.json()) as { resource: string };\n expect(body.resource).toMatch(/^http:\\/\\/(localhost|127\\.0\\.0\\.1):/);\n });\n\n it(\"points the 401 WWW-Authenticate at the inferred host\", async () => {\n const { jwksUri } = await startJwks();\n const base = await bootServer(jwksUri, { baseUrl: null });\n\n const res = await fetch(`${base}/mcp`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-forwarded-host\": \"infer.example.test\",\n },\n body: JSON.stringify({ jsonrpc: \"2.0\", method: \"initialize\", id: 1 }),\n });\n expect(res.status).toBe(401);\n expect(res.headers.get(\"www-authenticate\")).toMatch(\n /resource_metadata=\"https:\\/\\/infer\\.example\\.test\\//,\n );\n });\n});\n\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 verify: { issuer: ISSUER, audience: AUDIENCE, jwksUri },\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: [{ type: \"text\", text: extra.authInfo?.clientId ?? \"anon\" }],\n }),\n )\n .registerTool(\n {\n name: \"private-whoami\",\n description: \"Private.\",\n inputSchema: {},\n auth: {},\n },\n (_args, extra) => ({\n content: [{ type: \"text\", text: extra.authInfo?.clientId ?? \"anon\" }],\n }),\n );\n\n (server.registerTool as (...a: unknown[]) => unknown)(\n \"legacy-whoami\",\n {\n description: \"Registered via the legacy string overload.\",\n inputSchema: {},\n },\n (_args: unknown, extra: { authInfo?: { clientId?: string } }) => ({\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(\"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 registered via the legacy string overload (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: \"legacy-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 verify: { issuer: ISSUER, audience: AUDIENCE, jwksUri },\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 verify: { issuer: ISSUER, audience: AUDIENCE },\n },\n }),\n ).toThrow(/baseUrl must be a valid absolute URL/);\n });\n});\n"]}
@@ -1,7 +1,9 @@
1
1
  import type { OAuthTokenVerifier } from "@modelcontextprotocol/sdk/server/auth/provider.js";
2
2
  export type JwksVerifyConfig = {
3
- /** Expected `iss` claim. */
4
- issuer: string;
3
+ /** Expected `iss` claim. Pass several when one IdP mints tokens under either
4
+ * of two issuer identifiers (e.g. Descope's base project and agentic URLs);
5
+ * a token matching any of them is accepted. */
6
+ issuer: string | string[];
5
7
  /** Expected `aud` claim. Omit to skip audience verification — for IdPs that
6
8
  * don't bind an audience to their access tokens (e.g. Clerk). */
7
9
  audience?: string;
@@ -2,8 +2,8 @@ import { InvalidTokenError } from "@modelcontextprotocol/sdk/server/auth/errors.
2
2
  import * as jose from "jose";
3
3
  /** Builds an `OAuthTokenVerifier` validating JWTs against a remote JWKS. Internal, not exported. */
4
4
  export function createJwksVerifier(config) {
5
- const jwksUri = config.jwksUri ??
6
- `${config.issuer.replace(/\/$/, "")}/.well-known/jwks.json`;
5
+ const issuers = [config.issuer].flat();
6
+ const jwksUri = config.jwksUri ?? `${issuers[0]?.replace(/\/$/, "")}/.well-known/jwks.json`;
7
7
  const jwks = jose.createRemoteJWKSet(new URL(jwksUri));
8
8
  return {
9
9
  async verifyAccessToken(token) {
@@ -1 +1 @@
1
- {"version":3,"file":"verify.js","sourceRoot":"","sources":["../../../src/server/auth/verify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iDAAiD,CAAC;AAGpF,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAY7B,oGAAoG;AACpG,MAAM,UAAU,kBAAkB,CAChC,MAAwB;IAExB,MAAM,OAAO,GACX,MAAM,CAAC,OAAO;QACd,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,wBAAwB,CAAC;IAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IAEvD,OAAO;QACL,KAAK,CAAC,iBAAiB,CAAC,KAAa;YACnC,IAAI,OAAwB,CAAC;YAC7B,IAAI,CAAC;gBACH,CAAC,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE;oBAC/C,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,yDAAyD;oBACzD,GAAG,CAAC,MAAM,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;iBACpE,CAAC,CAAC,CAAC;YACN,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACjE,MAAM,IAAI,iBAAiB,CAAC,8BAA8B,OAAO,EAAE,CAAC,CAAC;YACvE,CAAC;YAED,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,OAQ/C,CAAC;YAEF,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;gBACjC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC;gBACnB,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ;oBACzB,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;oBACpC,CAAC,CAAC,EAAE,CAAC;YAET,OAAO;gBACL,KAAK;gBACL,QAAQ,EAAE,SAAS,IAAI,EAAE;gBACzB,MAAM;gBACN,SAAS,EAAE,GAAG;gBACd,KAAK,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE;aACd,CAAC;QACvB,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { InvalidTokenError } from \"@modelcontextprotocol/sdk/server/auth/errors.js\";\nimport type { OAuthTokenVerifier } from \"@modelcontextprotocol/sdk/server/auth/provider.js\";\nimport type { AuthInfo } from \"@modelcontextprotocol/sdk/server/auth/types.js\";\nimport * as jose from \"jose\";\n\nexport type JwksVerifyConfig = {\n /** Expected `iss` claim. */\n issuer: string;\n /** Expected `aud` claim. Omit to skip audience verification — for IdPs that\n * don't bind an audience to their access tokens (e.g. Clerk). */\n audience?: string;\n /** Defaults to `${issuer}/.well-known/jwks.json`. */\n jwksUri?: string;\n};\n\n/** Builds an `OAuthTokenVerifier` validating JWTs against a remote JWKS. Internal, not exported. */\nexport function createJwksVerifier(\n config: JwksVerifyConfig,\n): OAuthTokenVerifier {\n const jwksUri =\n config.jwksUri ??\n `${config.issuer.replace(/\\/$/, \"\")}/.well-known/jwks.json`;\n const jwks = jose.createRemoteJWKSet(new URL(jwksUri));\n\n return {\n async verifyAccessToken(token: string): Promise<AuthInfo> {\n let payload: jose.JWTPayload;\n try {\n ({ payload } = await jose.jwtVerify(token, jwks, {\n issuer: config.issuer,\n // jose skips the aud check when `audience` is undefined.\n ...(config.audience !== undefined && { audience: config.audience }),\n }));\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new InvalidTokenError(`Token verification failed: ${message}`);\n }\n\n const { client_id, scope, exp, sub, ...rest } = payload as Record<\n string,\n unknown\n > & {\n client_id?: string;\n scope?: string | string[];\n exp?: number;\n sub?: string;\n };\n\n const scopes = Array.isArray(scope)\n ? scope.map(String)\n : typeof scope === \"string\"\n ? scope.split(/\\s+/).filter(Boolean)\n : [];\n\n return {\n token,\n clientId: client_id ?? \"\",\n scopes,\n expiresAt: exp,\n extra: { subject: sub, ...rest },\n } satisfies AuthInfo;\n },\n };\n}\n"]}
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../../../src/server/auth/verify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iDAAiD,CAAC;AAGpF,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAc7B,oGAAoG;AACpG,MAAM,UAAU,kBAAkB,CAChC,MAAwB;IAExB,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IACvC,MAAM,OAAO,GACX,MAAM,CAAC,OAAO,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,wBAAwB,CAAC;IAC9E,MAAM,IAAI,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IAEvD,OAAO;QACL,KAAK,CAAC,iBAAiB,CAAC,KAAa;YACnC,IAAI,OAAwB,CAAC;YAC7B,IAAI,CAAC;gBACH,CAAC,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE;oBAC/C,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,yDAAyD;oBACzD,GAAG,CAAC,MAAM,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;iBACpE,CAAC,CAAC,CAAC;YACN,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACjE,MAAM,IAAI,iBAAiB,CAAC,8BAA8B,OAAO,EAAE,CAAC,CAAC;YACvE,CAAC;YAED,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,OAQ/C,CAAC;YAEF,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;gBACjC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC;gBACnB,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ;oBACzB,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;oBACpC,CAAC,CAAC,EAAE,CAAC;YAET,OAAO;gBACL,KAAK;gBACL,QAAQ,EAAE,SAAS,IAAI,EAAE;gBACzB,MAAM;gBACN,SAAS,EAAE,GAAG;gBACd,KAAK,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE;aACd,CAAC;QACvB,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { InvalidTokenError } from \"@modelcontextprotocol/sdk/server/auth/errors.js\";\nimport type { OAuthTokenVerifier } from \"@modelcontextprotocol/sdk/server/auth/provider.js\";\nimport type { AuthInfo } from \"@modelcontextprotocol/sdk/server/auth/types.js\";\nimport * as jose from \"jose\";\n\nexport type JwksVerifyConfig = {\n /** Expected `iss` claim. Pass several when one IdP mints tokens under either\n * of two issuer identifiers (e.g. Descope's base project and agentic URLs);\n * a token matching any of them is accepted. */\n issuer: string | string[];\n /** Expected `aud` claim. Omit to skip audience verification — for IdPs that\n * don't bind an audience to their access tokens (e.g. Clerk). */\n audience?: string;\n /** Defaults to `${issuer}/.well-known/jwks.json`. */\n jwksUri?: string;\n};\n\n/** Builds an `OAuthTokenVerifier` validating JWTs against a remote JWKS. Internal, not exported. */\nexport function createJwksVerifier(\n config: JwksVerifyConfig,\n): OAuthTokenVerifier {\n const issuers = [config.issuer].flat();\n const jwksUri =\n config.jwksUri ?? `${issuers[0]?.replace(/\\/$/, \"\")}/.well-known/jwks.json`;\n const jwks = jose.createRemoteJWKSet(new URL(jwksUri));\n\n return {\n async verifyAccessToken(token: string): Promise<AuthInfo> {\n let payload: jose.JWTPayload;\n try {\n ({ payload } = await jose.jwtVerify(token, jwks, {\n issuer: config.issuer,\n // jose skips the aud check when `audience` is undefined.\n ...(config.audience !== undefined && { audience: config.audience }),\n }));\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new InvalidTokenError(`Token verification failed: ${message}`);\n }\n\n const { client_id, scope, exp, sub, ...rest } = payload as Record<\n string,\n unknown\n > & {\n client_id?: string;\n scope?: string | string[];\n exp?: number;\n sub?: string;\n };\n\n const scopes = Array.isArray(scope)\n ? scope.map(String)\n : typeof scope === \"string\"\n ? scope.split(/\\s+/).filter(Boolean)\n : [];\n\n return {\n token,\n clientId: client_id ?? \"\",\n scopes,\n expiresAt: exp,\n extra: { subject: sub, ...rest },\n } satisfies AuthInfo;\n },\n };\n}\n"]}
@@ -33,6 +33,23 @@ function sign(key, claims, opts = {}) {
33
33
  .sign(key);
34
34
  }
35
35
  describe("createJwksVerifier", () => {
36
+ it("accepts a token from any of several configured issuers", async () => {
37
+ const { privateKey, jwksUri } = await startJwks();
38
+ const other = "https://other-issuer.test";
39
+ const verifier = createJwksVerifier({
40
+ issuer: [ISSUER, other],
41
+ audience: AUDIENCE,
42
+ jwksUri,
43
+ });
44
+ for (const issuer of [ISSUER, other]) {
45
+ const token = await sign(privateKey, { sub: "user-1" }, { issuer });
46
+ await expect(verifier.verifyAccessToken(token)).resolves.toMatchObject({
47
+ extra: { subject: "user-1" },
48
+ });
49
+ }
50
+ const foreign = await sign(privateKey, { sub: "user-1" }, { issuer: "https://evil.test" });
51
+ await expect(verifier.verifyAccessToken(foreign)).rejects.toThrow(/iss/);
52
+ });
36
53
  it("verifies a valid token and maps claims to AuthInfo", async () => {
37
54
  const { privateKey, jwksUri } = await startJwks();
38
55
  const verifier = createJwksVerifier({