@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
172 lines (171 loc) • 8.75 kB
JavaScript
"use strict";
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 error_types_util_js_1 = require("../utils/error-types.util.js");
const vendor_aws_sso_auth_service_js_1 = require("../services/vendor.aws.sso.auth.service.js");
const aws_sso_auth_formatter_js_1 = require("./aws.sso.auth.formatter.js");
const vendor_aws_sso_exec_service_js_1 = require("../services/vendor.aws.sso.exec.service.js");
const vendor_aws_sso_accounts_service_js_1 = require("../services/vendor.aws.sso.accounts.service.js");
const aws_sso_exec_formatter_js_1 = require("./aws.sso.exec.formatter.js");
/**
* AWS SSO Execution Controller Module
*
* Provides functionality for executing AWS CLI commands with temporary credentials
* obtained via AWS SSO. Handles credential retrieval, environment setup, and
* command execution with proper output formatting.
*/
// Create a module logger
const controllerLogger = logger_util_js_1.Logger.forContext('controllers/aws.sso.exec.controller.ts');
// Log module initialization
controllerLogger.debug('AWS SSO execution controller initialized');
/**
* Execute an AWS CLI command with temporary credentials from SSO
*
* Gets temporary AWS credentials for the specified account and role via SSO,
* then executes the AWS CLI command with those credentials in the environment.
* Handles authentication verification, command execution, and result formatting.
*
* @async
* @param {ExecCommandToolArgsType} options - Command execution options
* @param {string} options.accountId - AWS account ID to get credentials for
* @param {string} options.roleName - AWS role name to assume via SSO
* @param {string} [options.region] - AWS region to use for the command (optional)
* @param {string} options.command - AWS CLI command to execute as string
* @returns {Promise<ControllerResponse>} - Formatted command execution result
* @throws {Error} If authentication fails, command execution fails, or parameters are invalid
* @example
* // Execute an S3 list command
* const result = await executeCommand({
* accountId: "123456789012",
* roleName: "AdminAccess",
* region: "us-east-1",
* command: "aws s3 ls"
* });
*/
async function executeCommand(options) {
const execCommandLogger = logger_util_js_1.Logger.forContext('controllers/aws.sso.exec.controller.ts', 'executeCommand');
execCommandLogger.debug('Executing AWS CLI command', options);
try {
// Check if user is authenticated
const authStatus = await (0, vendor_aws_sso_auth_service_js_1.checkSsoAuthStatus)();
if (!authStatus.isAuthenticated) {
execCommandLogger.debug('User is not authenticated', {
errorMessage: authStatus.errorMessage,
});
// Return formatted auth required message
return {
content: (0, aws_sso_auth_formatter_js_1.formatAuthRequired)(),
};
}
// Validate command options
if (!options.accountId || !options.roleName || !options.command) {
throw new Error('Missing required parameters: accountId, roleName, and command are required');
}
// Log region usage
if (options.region) {
execCommandLogger.debug('Using explicitly provided region', {
region: options.region,
});
}
// Execute the command
execCommandLogger.debug('Executing command with environment', {
command: options.command,
env: {
AWS_REGION: options.region,
AWS_DEFAULT_REGION: options.region,
},
});
let result;
let suggestedRoles;
try {
// Execute the command via the service
result = await (0, vendor_aws_sso_exec_service_js_1.executeCommand)(options.accountId, options.roleName, options.command, options.region);
execCommandLogger.debug('Command execution completed by service', {
exitCode: result.exitCode,
stdoutLength: result.stdout.length,
stderrLength: result.stderr.length,
});
// Explicitly check for non-zero exit code even if service doesn't throw
if (result.exitCode !== 0) {
// Check for permission error indicators in stderr or stdout
const errorOutput = (result.stderr || '') + (result.stdout || ''); // Combine outputs as errors can be in stdout
const isPermissionError = /AccessDenied|UnauthorizedOperation|permission|denied/i.test(errorOutput);
if (isPermissionError) {
execCommandLogger.debug('Potential permission error detected based on output/exit code.', {
accountId: options.accountId,
exitCode: result.exitCode,
errorOutputSnippet: errorOutput.substring(0, 100),
});
try {
// Attempt to fetch roles for this account
const rolesResponse = await (0, vendor_aws_sso_accounts_service_js_1.listAccountRoles)({
accountId: options.accountId,
});
suggestedRoles = rolesResponse.roleList;
execCommandLogger.debug(`Found ${suggestedRoles?.length ?? 0} alternative roles for account ${options.accountId}.`);
}
catch (roleError) {
execCommandLogger.warn('Failed to fetch alternative roles after permission error', roleError);
// Continue without suggested roles
suggestedRoles = []; // Indicate that we tried but failed
}
}
// Note: We don't throw here; the formatter will handle displaying the error
}
}
catch (error) {
// Handle errors thrown by executeServiceCommand itself
execCommandLogger.error('Error during command execution service call', error);
// Check if this underlying error is a permission error
const errorMessage = error instanceof Error ? error.message : String(error);
const isPermissionError = /AccessDenied|UnauthorizedOperation|permission|denied/i.test(errorMessage);
if (isPermissionError) {
execCommandLogger.debug('Potential permission error detected from service error.', { accountId: options.accountId });
try {
const rolesResponse = await (0, vendor_aws_sso_accounts_service_js_1.listAccountRoles)({
accountId: options.accountId,
});
suggestedRoles = rolesResponse.roleList;
execCommandLogger.debug(`Found ${suggestedRoles?.length ?? 0} alternative roles for account ${options.accountId}.`);
}
catch (roleError) {
execCommandLogger.warn('Failed to fetch alternative roles after permission service error', roleError);
suggestedRoles = [];
}
// Construct a result object similar to what executeServiceCommand would return on failure
result = {
stdout: '',
stderr: errorMessage,
exitCode: 1, // Assume exit code 1 for service errors
};
}
else {
// If it's not a permission error caught here, re-throw for general handling
throw error;
}
}
// Format the result, passing suggestedRoles if available
const formattedContent = (0, aws_sso_exec_formatter_js_1.formatCommandResult)(options.command, result, {
accountId: options.accountId,
roleName: options.roleName,
region: options.region,
suggestedRoles: suggestedRoles,
});
return {
content: formattedContent,
};
}
catch (error) {
// Use throw instead of return
throw (0, error_handler_util_js_1.handleControllerError)(error, (0, error_types_util_js_1.buildErrorContext)('AWS Command', 'executing', 'controllers/aws.sso.exec.controller.ts@executeCommand', `${options.accountId}/${options.roleName}`, {
accountId: options.accountId,
roleName: options.roleName,
command: options.command,
region: options.region,
}));
}
}
exports.default = {
executeCommand,
};