@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
266 lines (265 loc) • 12.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getCachedCredentials = void 0;
exports.listSsoAccounts = listSsoAccounts;
exports.listAccountRoles = listAccountRoles;
exports.getAwsCredentials = getAwsCredentials;
exports.getAccountsWithRoles = getAccountsWithRoles;
/**
* AWS SSO accounts vendor service
*/
const logger_util_js_1 = require("../utils/logger.util.js");
const error_util_js_1 = require("../utils/error.util.js");
const aws_sso_cache_util_js_1 = require("../utils/aws.sso.cache.util.js");
Object.defineProperty(exports, "getCachedCredentials", { enumerable: true, get: function () { return aws_sso_cache_util_js_1.getCachedCredentials; } });
const vendor_aws_sso_auth_service_js_1 = require("./vendor.aws.sso.auth.service.js");
const client_sso_1 = require("@aws-sdk/client-sso");
const logger = logger_util_js_1.Logger.forContext('services/vendor.aws.sso.accounts.service.ts');
/**
* List AWS SSO accounts for the authenticated user
*
* Retrieves the list of AWS accounts that the user has access to via SSO.
* Requires an active SSO token.
*
* @param {ListAccountsParams} [params={}] - Optional parameters for customizing the request
* @param {number} [params.maxResults] - Maximum number of accounts to return
* @param {string} [params.nextToken] - Pagination token for subsequent requests
* @returns {Promise<ListAccountsResponse>} List of AWS SSO accounts and pagination token if available
* @throws {Error} If SSO token is missing or API request fails
*/
async function listSsoAccounts(params = {}) {
const methodLogger = logger.forMethod('listSsoAccounts');
methodLogger.debug('Listing AWS SSO accounts', params);
// Get SSO token
const token = await (0, vendor_aws_sso_auth_service_js_1.getCachedSsoToken)();
if (!token) {
throw (0, error_util_js_1.createAuthMissingError)('No SSO token found. Please login first.');
}
try {
// Use AWS SDK to list accounts instead of direct API call
const region = token.region || 'us-east-1';
// Create SSO client with proper region
const ssoClient = new client_sso_1.SSOClient({
region: region,
maxAttempts: 3,
});
// Configure command with proper parameters
const command = new client_sso_1.ListAccountsCommand({
accessToken: token.accessToken,
maxResults: params.maxResults,
nextToken: params.nextToken,
});
// Execute command to get accounts
methodLogger.debug('Requesting accounts list using AWS SDK');
const response = await ssoClient.send(command);
// Convert response to expected format with type handling
const accountList = (response.accountList || []).map((account) => ({
accountId: account.accountId || '',
accountName: account.accountName || '',
accountEmail: account.emailAddress,
}));
const result = {
accountList,
nextToken: response.nextToken,
};
methodLogger.debug(`Retrieved ${result.accountList.length} accounts${result.nextToken ? ' with pagination token' : ''}`);
return result;
}
catch (error) {
methodLogger.error('Failed to list accounts', error);
throw (0, error_util_js_1.createApiError)(`Failed to list AWS accounts: ${error instanceof Error ? error.message : String(error)}`, undefined, error);
}
}
/**
* List roles for a specific AWS SSO account
*
* Retrieves the list of roles that the user can assume in the specified AWS account.
* Requires an active SSO token.
*
* @param {ListAccountRolesParams} params - Parameters for the request
* @param {string} params.accountId - AWS account ID to list roles for
* @param {number} [params.maxResults] - Maximum number of roles to return
* @param {string} [params.nextToken] - Pagination token for subsequent requests
* @returns {Promise<ListAccountRolesResponse>} List of AWS SSO roles and pagination token if available
* @throws {Error} If SSO token is missing or API request fails
*/
async function listAccountRoles(params) {
const methodLogger = logger.forMethod('listAccountRoles');
methodLogger.debug('Listing AWS SSO account roles', params);
// Validate required parameters
if (!params.accountId) {
throw new Error('Account ID is required');
}
// Get SSO token
const token = await (0, vendor_aws_sso_auth_service_js_1.getCachedSsoToken)();
if (!token) {
throw (0, error_util_js_1.createAuthMissingError)('No SSO token found. Please login first.');
}
try {
// Use AWS SDK to list roles instead of direct API call
const region = token.region || 'us-east-1';
// Create SSO client with proper region
const ssoClient = new client_sso_1.SSOClient({
region: region,
maxAttempts: 3,
});
// Configure command with proper parameters
const command = new client_sso_1.ListAccountRolesCommand({
accessToken: token.accessToken,
accountId: params.accountId,
maxResults: params.maxResults,
nextToken: params.nextToken,
});
// Execute command to get roles
methodLogger.debug('Requesting roles list using AWS SDK');
const response = await ssoClient.send(command);
// Convert response to expected format
const result = {
roleList: response.roleList || [],
nextToken: response.nextToken,
};
methodLogger.debug(`Retrieved ${result.roleList.length} roles for account ${params.accountId}${result.nextToken ? ' with pagination token' : ''}`);
return result;
}
catch (error) {
methodLogger.error('Failed to list roles', error);
throw (0, error_util_js_1.createApiError)(`Failed to list roles for account ${params.accountId}: ${error instanceof Error ? error.message : String(error)}`, undefined, error);
}
}
/**
* Get temporary AWS credentials for a role via SSO
*
* Retrieves temporary AWS credentials for the specified account and role.
* Requires an active SSO token.
*
* @param {GetCredentialsParams} params - Parameters for the request
* @param {string} params.accountId - AWS account ID
* @param {string} params.roleName - Role name to assume
* @param {string} [params.region] - Optional AWS region override
* @returns {Promise<AwsCredentials>} Temporary AWS credentials
* @throws {Error} If SSO token is missing or API request fails
*/
async function getAwsCredentials(params) {
const methodLogger = logger.forMethod('getAwsCredentials');
methodLogger.debug('Getting AWS credentials', {
accountId: params.accountId,
roleName: params.roleName,
});
// Validate required parameters
if (!params.accountId || !params.roleName) {
throw new Error('Account ID and role name are required');
}
// First, check if we have cached credentials
const cachedCreds = await (0, aws_sso_cache_util_js_1.getCachedCredentials)(params.accountId, params.roleName);
if (cachedCreds) {
const now = new Date();
// Allow a 5-minute buffer before expiration
const expiration = new Date(cachedCreds.expiration);
const bufferMs = 5 * 60 * 1000; // 5 minutes in milliseconds
if (expiration.getTime() - now.getTime() > bufferMs) {
methodLogger.debug('Using cached credentials', {
accountId: params.accountId,
roleName: params.roleName,
expiration: expiration.toISOString(),
});
// Ensure we have the right type
const credentials = {
accessKeyId: cachedCreds.accessKeyId,
secretAccessKey: cachedCreds.secretAccessKey,
sessionToken: cachedCreds.sessionToken,
expiration: new Date(cachedCreds.expiration),
};
return credentials;
}
methodLogger.debug('Cached credentials are expiring soon, refreshing', {
expiration: expiration.toISOString(),
});
}
// Get SSO token
const token = await (0, vendor_aws_sso_auth_service_js_1.getCachedSsoToken)();
if (!token) {
throw (0, error_util_js_1.createAuthMissingError)('No SSO token found. Please login first.');
}
try {
// Use AWS SDK to get credentials instead of direct API call
const region = params.region || token.region || 'us-east-1';
// Create SSO client with proper region
const ssoClient = new client_sso_1.SSOClient({
region: region,
maxAttempts: 3,
});
// Configure command with proper parameters
const command = new client_sso_1.GetRoleCredentialsCommand({
accessToken: token.accessToken,
accountId: params.accountId,
roleName: params.roleName,
});
// Execute command to get credentials
methodLogger.debug('Requesting temporary credentials using AWS SDK');
const response = await ssoClient.send(command);
if (!response.roleCredentials) {
throw new Error('No credentials returned from AWS SSO');
}
// Create credentials object from response
const credentials = {
accessKeyId: response.roleCredentials.accessKeyId,
secretAccessKey: response.roleCredentials.secretAccessKey,
sessionToken: response.roleCredentials.sessionToken,
expiration: new Date(response.roleCredentials.expiration),
};
// Cache the credentials
await (0, aws_sso_cache_util_js_1.saveCachedCredentials)(params.accountId, params.roleName, credentials);
return credentials;
}
catch (error) {
methodLogger.error('Failed to get AWS credentials', error);
throw (0, error_util_js_1.createApiError)(`Failed to get AWS credentials: ${error instanceof Error ? error.message : String(error)}`, undefined, error);
}
}
/**
* Get all AWS accounts with their available roles
*
* Retrieves a combined view of all accounts and their roles that the user has access to.
* This is a convenience function that combines listSsoAccounts and listAccountRoles.
*
* @param {ListAccountsParams} [params={}] - Optional parameters for customizing the request
* @param {number} [params.maxResults] - Maximum number of accounts to return
* @param {string} [params.nextToken] - Pagination token for subsequent requests
* @returns {Promise<AwsSsoAccountWithRoles[]>} List of AWS accounts with their roles
* @throws {Error} If SSO token is missing or API request fails
*/
async function getAccountsWithRoles(params = {}) {
const methodLogger = logger.forMethod('getAccountsWithRoles');
methodLogger.debug('Getting all AWS SSO accounts with roles', params);
// Get accounts
const accountsResponse = await listSsoAccounts(params);
const accounts = accountsResponse.accountList;
// Get roles for each account
const accountsWithRoles = [];
for (const account of accounts) {
try {
const rolesResponse = await listAccountRoles({
accountId: account.accountId,
});
accountsWithRoles.push({
...account,
roles: rolesResponse.roleList.map((role) => ({
accountId: account.accountId,
roleName: role.roleName || '',
roleArn: role.roleArn ||
`arn:aws:iam::${account.accountId}:role/${role.roleName || ''}`,
})),
});
}
catch (error) {
methodLogger.warn(`Error getting roles for account ${account.accountId}`, error);
// Include account with empty roles array
accountsWithRoles.push({
...account,
roles: [],
});
}
}
methodLogger.debug(`Retrieved ${accountsWithRoles.length} accounts with roles`);
return accountsWithRoles;
}