@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
240 lines (239 loc) • 10.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const logger_util_js_1 = require("../utils/logger.util.js");
const error_handler_util_js_1 = require("../utils/error-handler.util.js");
const vendor_aws_sso_auth_service_js_1 = require("../services/vendor.aws.sso.auth.service.js");
const vendor_aws_sso_accounts_service_js_1 = require("../services/vendor.aws.sso.accounts.service.js");
const aws_sso_cache_util_js_1 = require("../utils/aws.sso.cache.util.js");
const aws_sso_accounts_formatter_js_1 = require("./aws.sso.accounts.formatter.js");
/**
* AWS SSO Accounts Controller Module
*
* Provides functionality for listing and managing AWS SSO accounts and roles.
* Handles retrieving account information, listing available roles, and formatting
* the results for display. All operations require valid AWS SSO authentication.
*/
// Create a module logger
const controllerLogger = logger_util_js_1.Logger.forContext('controllers/aws.sso.accounts.controller.ts');
// Log module initialization
controllerLogger.debug('AWS SSO accounts controller initialized');
/**
* List all AWS accounts and their roles
*
* Retrieves and formats all available AWS accounts and the roles the user
* can assume in each account via AWS SSO. Groups roles by account for better organization.
*
* @async
* @returns {Promise<ControllerResponse>} Response with comprehensive formatted list of accounts and roles
* @throws {Error} If account listing fails or authentication is required
* @example
* // List all accounts and their roles
* const result = await listAccounts();
*/
async function listAccounts() {
const listLogger = logger_util_js_1.Logger.forContext('controllers/aws.sso.accounts.controller.ts', 'listAccounts');
listLogger.debug('Listing AWS SSO accounts and roles');
try {
// First check if we have a valid token
const cachedToken = await (0, vendor_aws_sso_auth_service_js_1.getCachedSsoToken)();
if (!cachedToken) {
// No token found, user needs to authenticate
listLogger.debug('No SSO token found, authentication required');
return {
content: (0, aws_sso_accounts_formatter_js_1.formatAuthRequired)(),
metadata: {
authenticated: false,
},
};
}
// Verify token has an access token
if (!cachedToken.accessToken || cachedToken.accessToken.trim() === '') {
listLogger.error('Cached token has empty access token');
// Clear invalid token and ask for re-authentication
try {
await (0, aws_sso_cache_util_js_1.clearSsoToken)();
listLogger.debug('Cleared invalid empty token');
}
catch (clearError) {
listLogger.error('Error clearing invalid token', clearError);
}
return {
content: (0, aws_sso_accounts_formatter_js_1.formatAuthRequired)(),
metadata: {
authenticated: false,
},
};
}
// We have a token, validate it's not expired
const now = Math.floor(Date.now() / 1000);
if (cachedToken.expiresAt <= now) {
listLogger.debug('SSO token is expired, authentication required');
// Clear expired token
try {
await (0, aws_sso_cache_util_js_1.clearSsoToken)();
listLogger.debug('Cleared expired token');
}
catch (clearError) {
listLogger.error('Error clearing expired token', clearError);
}
return {
content: (0, aws_sso_accounts_formatter_js_1.formatAuthRequired)(),
metadata: {
authenticated: false,
},
};
}
// Format expiration date for display
let expiresDate = 'Unknown';
try {
const expirationDate = new Date(cachedToken.expiresAt * 1000);
expiresDate = expirationDate.toLocaleString();
}
catch (error) {
listLogger.error('Error formatting expiration date', error);
}
// Get accounts with roles
listLogger.debug('Getting AWS accounts with roles');
try {
// Check if we already have cached account roles data
const cacheUtil = await import('../utils/aws.sso.cache.util.js');
const cachedAccounts = await cacheUtil.getAccountRolesFromCache();
if (cachedAccounts && cachedAccounts.length > 0) {
listLogger.debug('Using account roles from cache');
// Map the cached accounts to the format expected by the formatter
// The cache uses {account: {...}, roles: [...]} format, but we need it flattened
const formattedAccountsWithRoles = cachedAccounts.map((item) => ({
...item.account,
roles: item.roles,
timestamp: Date.now(),
}));
return {
content: (0, aws_sso_accounts_formatter_js_1.formatAccountsAndRoles)(expiresDate, formattedAccountsWithRoles),
metadata: {
authenticated: true,
accounts: formattedAccountsWithRoles.map((account) => ({
accountId: account.accountId,
accountName: account.accountName,
accountEmail: account.emailAddress, // Note: different property name in cache
roles: account.roles,
})),
},
};
}
// If no cached data or cache is empty, get accounts with roles from API
listLogger.debug('No cached account roles, fetching from AWS SSO API');
// Get accounts with roles
const accountsWithRoles = await (0, vendor_aws_sso_accounts_service_js_1.getAccountsWithRoles)({
// Use the access token from the cached token
// The vendor implementation expects a params object, not just the token
});
if (accountsWithRoles.length === 0) {
listLogger.debug('No accounts found');
return {
content: (0, aws_sso_accounts_formatter_js_1.formatNoAccounts)(),
metadata: {
authenticated: true,
accounts: [],
},
};
}
// Save accounts with roles to MCP cache
try {
await cacheUtil.saveAccountRolesToCache(accountsWithRoles);
listLogger.debug('Saved accounts with roles to MCP cache');
}
catch (cacheError) {
listLogger.error('Error saving to MCP cache', cacheError);
// Continue even if caching fails
}
// Format the accounts with roles for the response
// The vendor implementation returns a different format compared to the non-vendor one
const formattedAccountsWithRoles = accountsWithRoles.map((account) => ({
...account,
timestamp: Date.now(),
}));
// Return accounts and roles
return {
content: (0, aws_sso_accounts_formatter_js_1.formatAccountsAndRoles)(expiresDate, formattedAccountsWithRoles),
metadata: {
authenticated: true,
accounts: formattedAccountsWithRoles.map((account) => ({
accountId: account.accountId,
accountName: account.accountName,
accountEmail: account.accountEmail,
roles: account.roles,
})),
},
};
}
catch (error) {
// If we get an error about invalid/expired token, show auth required message
if (error instanceof Error &&
(error.message.includes('invalid') ||
error.message.includes('expired') ||
error.message.includes('session'))) {
listLogger.debug('Token validation failed, authentication required', error);
return {
content: (0, aws_sso_accounts_formatter_js_1.formatAuthRequired)(),
metadata: {
authenticated: false,
},
};
}
// For other errors, pass through to general error handler
throw error;
}
}
catch (error) {
return (0, error_handler_util_js_1.handleControllerError)(error, {
entityType: 'AWS SSO Accounts',
operation: 'listing',
source: 'controllers/aws.sso.accounts.controller.ts@listAccounts',
});
}
}
/**
* List roles available for a specified AWS account
*
* Retrieves and formats all IAM roles the authenticated user can assume
* in a specific AWS account via SSO.
*
* @async
* @param {ListRolesOptions} params - Role listing parameters
* @returns {Promise<ControllerResponse>} Response with formatted list of available roles
* @throws {Error} If role listing fails or authentication is invalid
* @example
* // List roles for account 123456789012
* const result = await listRoles({
* accessToken: "token-value",
* accountId: "123456789012"
* });
*/
async function listRoles(params) {
const listRolesLogger = logger_util_js_1.Logger.forContext('controllers/aws.sso.accounts.controller.ts', 'listRoles');
listRolesLogger.debug(`Listing roles for account ${params.accountId}`);
try {
// Get roles for account
const roles = await (0, vendor_aws_sso_accounts_service_js_1.listAccountRoles)({
accountId: params.accountId,
});
return {
content: (0, aws_sso_accounts_formatter_js_1.formatAccountRoles)(params.accountId, roles.roleList),
metadata: {
roles: roles.roleList,
},
};
}
catch (error) {
return (0, error_handler_util_js_1.handleControllerError)(error, {
entityType: 'AWS Account Roles',
entityId: params.accountId,
operation: 'listing',
source: 'controllers/aws.sso.accounts.controller.ts@listRoles',
});
}
}
exports.default = {
listAccounts,
listRoles,
};