@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
JavaScript
;
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);
}
}