UNPKG

@aashari/mcp-server-aws-sso

Version:

Node.js/TypeScript MCP server for AWS Single Sign-On (SSO). Enables AI systems (LLMs) with tools to initiate SSO login (device auth flow), list accounts/roles, and securely execute AWS CLI commands using temporary credentials. Streamlines AI interaction w

204 lines (203 loc) 9.17 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.registerClient = registerClient; exports.startDeviceAuthorization = startDeviceAuthorization; exports.pollForToken = pollForToken; const logger_util_js_1 = require("../utils/logger.util.js"); const error_util_js_1 = require("../utils/error.util.js"); const ssoCache = __importStar(require("../utils/aws.sso.cache.util.js")); const vendor_aws_sso_auth_http_js_1 = require("./vendor.aws.sso.auth.http.js"); const logger = logger_util_js_1.Logger.forContext('services/vendor.aws.sso.auth.oauth.service.ts'); // Remove unused schemas and types - comment them out for now // const AuthErrorSchema = z.object({ // error: z.string(), // error_description: z.string().optional(), // }); // type AuthError = z.infer<typeof AuthErrorSchema>; /** * Register an OIDC client with AWS SSO * @param ssoConfig The AWS SSO configuration * @returns The client registration response */ async function registerClient(ssoConfig) { const methodLogger = logger.forMethod('registerClient'); methodLogger.debug('Registering OIDC client', { startUrl: ssoConfig.startUrl, region: ssoConfig.region, }); const registerUrl = `https://oidc.${ssoConfig.region}.amazonaws.com/client/register`; try { const response = await (0, vendor_aws_sso_auth_http_js_1.post)(registerUrl, { clientName: 'MCP AWS SSO CLI', clientType: 'public', scopes: ['sso:account:access', 'sso-directory:user:read'], grantTypes: ['urn:ietf:params:oauth:grant-type:device_code'], }); // Validate the response with Zod schema const validatedResponse = vendor_aws_sso_auth_http_js_1.ClientRegistrationResponseSchema.parse(response); methodLogger.debug('Client registered successfully', { clientId: validatedResponse.clientId, }); return { clientId: validatedResponse.clientId, clientSecret: validatedResponse.clientSecret, }; } catch (error) { methodLogger.error('Failed to register OIDC client', error); throw (0, error_util_js_1.createApiError)('Failed to register AWS SSO client', undefined, error); } } /** * Start the device authorization flow for AWS SSO * @param ssoConfig The AWS SSO configuration * @param clientInfo The client information (id and secret) from registration * @returns The device authorization response */ async function startDeviceAuthorization(ssoConfig, clientInfo) { const methodLogger = logger.forMethod('startDeviceAuthorization'); methodLogger.debug('Starting device authorization', { startUrl: ssoConfig.startUrl, }); const authUrl = `https://oidc.${ssoConfig.region}.amazonaws.com/device_authorization`; try { const response = await (0, vendor_aws_sso_auth_http_js_1.post)(authUrl, { clientId: clientInfo.clientId, clientSecret: clientInfo.clientSecret, startUrl: ssoConfig.startUrl, }); // Validate the response with Zod schema const validatedResponse = vendor_aws_sso_auth_http_js_1.DeviceAuthorizationResponseSchema.parse(response); methodLogger.debug('Device authorization started', { verificationUri: validatedResponse.verificationUri, userCode: validatedResponse.userCode, }); return validatedResponse; } catch (error) { methodLogger.error('Failed to start device authorization', error); throw (0, error_util_js_1.createApiError)('Failed to start AWS SSO device authorization', undefined, error); } } /** * Poll for an OIDC token using the device code flow * @param ssoConfig The AWS SSO configuration * @param clientInfo The client information (id and secret) from registration * @param deviceCode The device code from the device authorization response * @param _interval The polling interval in seconds (unused but kept for API compatibility) * @returns The token response or null if authorization is still pending */ async function pollForToken(ssoConfig, clientInfo, deviceCode, _interval) { const methodLogger = logger.forMethod('pollForToken'); methodLogger.debug('Polling for token'); const tokenUrl = `https://oidc.${ssoConfig.region}.amazonaws.com/token`; try { const response = await (0, vendor_aws_sso_auth_http_js_1.post)(tokenUrl, { grantType: 'urn:ietf:params:oauth:grant-type:device_code', deviceCode, clientId: clientInfo.clientId, clientSecret: clientInfo.clientSecret, }); // Validate the response with Zod schema const validatedResponse = vendor_aws_sso_auth_http_js_1.TokenResponseSchema.parse(response); methodLogger.debug('Token received'); // Handle naming inconsistencies in the AWS API const tokenResponse = { accessToken: validatedResponse.accessToken || validatedResponse.access_token || '', refreshToken: validatedResponse.refreshToken || validatedResponse.refresh_token || null, expiresIn: validatedResponse.expiresIn || validatedResponse.expires_in || 28800, // Default to 8 hours expiresAt: Math.floor(Date.now() / 1000) + (validatedResponse.expiresIn || validatedResponse.expires_in || 28800), }; // Cache the token await ssoCache.saveSsoToken({ accessToken: tokenResponse.accessToken, expiresAt: Math.floor(Date.now() / 1000) + (tokenResponse.expiresIn || 28800), region: ssoConfig.region, refreshToken: tokenResponse.refreshToken || '', tokenType: 'Bearer', expiresIn: tokenResponse.expiresIn || 28800, retrievedAt: Math.floor(Date.now() / 1000), }); return tokenResponse; } catch (error) { // Check if this is an authorization_pending error which is expected during polling if (error && typeof error === 'object' && 'error' in error && error.error === 'authorization_pending') { methodLogger.debug('Authorization pending, will poll again'); return null; // Signal that we should continue polling } // Check if this is a slow_down error if (error && typeof error === 'object' && 'error' in error && error.error === 'slow_down') { methodLogger.warn('Received slow_down error, retry mechanism will handle it'); throw error; // Let the retry mechanism handle it } // Check if this is an expired_token error if (error && typeof error === 'object' && 'error' in error && error.error === 'expired_token') { methodLogger.error('Device code has expired', error); throw (0, error_util_js_1.createApiError)('AWS SSO authorization session has expired. Please try again.', undefined, error); } // Check if this is an access_denied error if (error && typeof error === 'object' && 'error' in error && error.error === 'access_denied') { methodLogger.error('User denied authorization', error); throw (0, error_util_js_1.createApiError)('AWS SSO authorization was denied by the user.', undefined, error); } // For any other error, log and propagate methodLogger.error('Failed to poll for token', error); throw (0, error_util_js_1.createApiError)('Failed to get AWS SSO token', undefined, error); } }