router.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. exports.createOAuthMetadata = void 0;
  7. exports.mcpAuthRouter = mcpAuthRouter;
  8. exports.mcpAuthMetadataRouter = mcpAuthMetadataRouter;
  9. exports.getOAuthProtectedResourceMetadataUrl = getOAuthProtectedResourceMetadataUrl;
  10. const express_1 = __importDefault(require("express"));
  11. const register_js_1 = require("./handlers/register.js");
  12. const token_js_1 = require("./handlers/token.js");
  13. const authorize_js_1 = require("./handlers/authorize.js");
  14. const revoke_js_1 = require("./handlers/revoke.js");
  15. const metadata_js_1 = require("./handlers/metadata.js");
  16. // Check for dev mode flag that allows HTTP issuer URLs (for development/testing only)
  17. const allowInsecureIssuerUrl = process.env.MCP_DANGEROUSLY_ALLOW_INSECURE_ISSUER_URL === 'true' || process.env.MCP_DANGEROUSLY_ALLOW_INSECURE_ISSUER_URL === '1';
  18. if (allowInsecureIssuerUrl) {
  19. // eslint-disable-next-line no-console
  20. console.warn('MCP_DANGEROUSLY_ALLOW_INSECURE_ISSUER_URL is enabled - HTTP issuer URLs are allowed. Do not use in production.');
  21. }
  22. const checkIssuerUrl = (issuer) => {
  23. // Technically RFC 8414 does not permit a localhost HTTPS exemption, but this will be necessary for ease of testing
  24. if (issuer.protocol !== 'https:' && issuer.hostname !== 'localhost' && issuer.hostname !== '127.0.0.1' && !allowInsecureIssuerUrl) {
  25. throw new Error('Issuer URL must be HTTPS');
  26. }
  27. if (issuer.hash) {
  28. throw new Error(`Issuer URL must not have a fragment: ${issuer}`);
  29. }
  30. if (issuer.search) {
  31. throw new Error(`Issuer URL must not have a query string: ${issuer}`);
  32. }
  33. };
  34. const createOAuthMetadata = (options) => {
  35. const issuer = options.issuerUrl;
  36. const baseUrl = options.baseUrl;
  37. checkIssuerUrl(issuer);
  38. const authorization_endpoint = '/authorize';
  39. const token_endpoint = '/token';
  40. const registration_endpoint = options.provider.clientsStore.registerClient ? '/register' : undefined;
  41. const revocation_endpoint = options.provider.revokeToken ? '/revoke' : undefined;
  42. const metadata = {
  43. issuer: issuer.href,
  44. service_documentation: options.serviceDocumentationUrl?.href,
  45. authorization_endpoint: new URL(authorization_endpoint, baseUrl || issuer).href,
  46. response_types_supported: ['code'],
  47. code_challenge_methods_supported: ['S256'],
  48. token_endpoint: new URL(token_endpoint, baseUrl || issuer).href,
  49. token_endpoint_auth_methods_supported: ['client_secret_post', 'none'],
  50. grant_types_supported: ['authorization_code', 'refresh_token'],
  51. scopes_supported: options.scopesSupported,
  52. revocation_endpoint: revocation_endpoint ? new URL(revocation_endpoint, baseUrl || issuer).href : undefined,
  53. revocation_endpoint_auth_methods_supported: revocation_endpoint ? ['client_secret_post'] : undefined,
  54. registration_endpoint: registration_endpoint ? new URL(registration_endpoint, baseUrl || issuer).href : undefined
  55. };
  56. return metadata;
  57. };
  58. exports.createOAuthMetadata = createOAuthMetadata;
  59. /**
  60. * Installs standard MCP authorization server endpoints, including dynamic client registration and token revocation (if supported).
  61. * Also advertises standard authorization server metadata, for easier discovery of supported configurations by clients.
  62. * Note: if your MCP server is only a resource server and not an authorization server, use mcpAuthMetadataRouter instead.
  63. *
  64. * By default, rate limiting is applied to all endpoints to prevent abuse.
  65. *
  66. * This router MUST be installed at the application root, like so:
  67. *
  68. * const app = express();
  69. * app.use(mcpAuthRouter(...));
  70. */
  71. function mcpAuthRouter(options) {
  72. const oauthMetadata = (0, exports.createOAuthMetadata)(options);
  73. const router = express_1.default.Router();
  74. router.use(new URL(oauthMetadata.authorization_endpoint).pathname, (0, authorize_js_1.authorizationHandler)({ provider: options.provider, ...options.authorizationOptions }));
  75. router.use(new URL(oauthMetadata.token_endpoint).pathname, (0, token_js_1.tokenHandler)({ provider: options.provider, ...options.tokenOptions }));
  76. router.use(mcpAuthMetadataRouter({
  77. oauthMetadata,
  78. // Prefer explicit RS; otherwise fall back to AS baseUrl, then to issuer (back-compat)
  79. resourceServerUrl: options.resourceServerUrl ?? options.baseUrl ?? new URL(oauthMetadata.issuer),
  80. serviceDocumentationUrl: options.serviceDocumentationUrl,
  81. scopesSupported: options.scopesSupported,
  82. resourceName: options.resourceName
  83. }));
  84. if (oauthMetadata.registration_endpoint) {
  85. router.use(new URL(oauthMetadata.registration_endpoint).pathname, (0, register_js_1.clientRegistrationHandler)({
  86. clientsStore: options.provider.clientsStore,
  87. ...options.clientRegistrationOptions
  88. }));
  89. }
  90. if (oauthMetadata.revocation_endpoint) {
  91. router.use(new URL(oauthMetadata.revocation_endpoint).pathname, (0, revoke_js_1.revocationHandler)({ provider: options.provider, ...options.revocationOptions }));
  92. }
  93. return router;
  94. }
  95. function mcpAuthMetadataRouter(options) {
  96. checkIssuerUrl(new URL(options.oauthMetadata.issuer));
  97. const router = express_1.default.Router();
  98. const protectedResourceMetadata = {
  99. resource: options.resourceServerUrl.href,
  100. authorization_servers: [options.oauthMetadata.issuer],
  101. scopes_supported: options.scopesSupported,
  102. resource_name: options.resourceName,
  103. resource_documentation: options.serviceDocumentationUrl?.href
  104. };
  105. // Serve PRM at the path-specific URL per RFC 9728
  106. const rsPath = new URL(options.resourceServerUrl.href).pathname;
  107. router.use(`/.well-known/oauth-protected-resource${rsPath === '/' ? '' : rsPath}`, (0, metadata_js_1.metadataHandler)(protectedResourceMetadata));
  108. // Always add this for OAuth Authorization Server metadata per RFC 8414
  109. router.use('/.well-known/oauth-authorization-server', (0, metadata_js_1.metadataHandler)(options.oauthMetadata));
  110. return router;
  111. }
  112. /**
  113. * Helper function to construct the OAuth 2.0 Protected Resource Metadata URL
  114. * from a given server URL. This replaces the path with the standard metadata endpoint.
  115. *
  116. * @param serverUrl - The base URL of the protected resource server
  117. * @returns The URL for the OAuth protected resource metadata endpoint
  118. *
  119. * @example
  120. * getOAuthProtectedResourceMetadataUrl(new URL('https://api.example.com/mcp'))
  121. * // Returns: 'https://api.example.com/.well-known/oauth-protected-resource/mcp'
  122. */
  123. function getOAuthProtectedResourceMetadataUrl(serverUrl) {
  124. const u = new URL(serverUrl.href);
  125. const rsPath = u.pathname && u.pathname !== '/' ? u.pathname : '';
  126. return new URL(`/.well-known/oauth-protected-resource${rsPath}`, u).href;
  127. }
  128. //# sourceMappingURL=router.js.map