cloudagent-deploy
Version:
CloudAgent Deploy - MCP Server for CloudFormation deployments via backend API
290 lines • 10.6 kB
JavaScript
export class BackendApiClient {
config;
constructor(config) {
this.config = config;
}
/**
* Validate a CloudFormation template via backend API
*/
async validateTemplate(template) {
try {
const response = await this.makeRequest('/cfn/validate', 'POST', {
template
});
return {
valid: response.valid || false,
errors: response.errors || [],
warnings: response.warnings || [],
message: response.message
};
}
catch (error) {
return {
valid: false,
errors: [`Backend validation error: ${error.message}`],
warnings: []
};
}
}
/**
* Deploy a CloudFormation stack via backend API
*/
async deployStack(params) {
try {
const payload = {};
if (params.template !== undefined)
payload.template = params.template;
if (params.templateType !== undefined)
payload.templateType = params.templateType;
if (params.appName !== undefined)
payload.appName = params.appName;
if (params.description !== undefined)
payload.description = params.description;
if (params.metadata !== undefined)
payload.metadata = params.metadata;
if (params.stackName !== undefined)
payload.stackName = params.stackName;
if (params.parameters !== undefined)
payload.parameters = params.parameters;
if (params.tags !== undefined)
payload.tags = params.tags;
if (params.capabilities !== undefined)
payload.capabilities = params.capabilities;
if (params.targetRegion !== undefined)
payload.targetRegion = params.targetRegion;
const response = await this.makeRequest('/cfn/deploy', 'POST', payload);
// Check if this is a region selection error
if (response.allowedRegions) {
return {
success: false,
stackName: response.stackName || params.stackName || '',
status: 'REGION_SELECTION_REQUIRED',
requiresRegionSelection: true,
allowedRegions: response.allowedRegions,
regionSelectionMessage: response.message,
error: response.error
};
}
// Override success determination for in-progress statuses
const status = response.status || 'UNKNOWN';
const isInProgress = [
'CREATE_IN_PROGRESS',
'UPDATE_IN_PROGRESS',
'DELETE_IN_PROGRESS'
].includes(status);
// If stack is in progress, that means deployment started successfully
const actualSuccess = response.success || isInProgress;
return {
success: actualSuccess,
stackId: response.stackId,
stackName: response.stackName || params.stackName || '',
status: status,
outputs: response.outputs,
events: response.events,
error: response.error
};
}
catch (error) {
return {
success: false,
stackName: params.stackName || '',
status: 'FAILED',
error: `Backend deployment error: ${error.message}`
};
}
}
/**
* Start an intent-driven deploy (interactive): returns NEEDS_INPUT with questions
*/
async startDeployInteractive() {
return this.makeRequest('/cfn/deploy', 'POST', {});
}
/**
* Continue an intent-driven deploy with answers (correlationId, appName, intent, targetRegion)
*/
async continueDeployInteractive(payload) {
return this.makeRequest('/cfn/deploy', 'POST', payload);
}
/**
* Delete a CloudFormation stack via backend API
*/
async deleteStack(stackName) {
try {
const response = await this.makeRequest('/cfn/delete', 'POST', {
stackName
});
// Override success determination for in-progress statuses
const status = response.status || 'UNKNOWN';
const isInProgress = [
'DELETE_IN_PROGRESS'
].includes(status);
// If stack deletion is in progress, that means deletion started successfully
const actualSuccess = response.success || isInProgress;
return {
success: actualSuccess,
stackName,
status: status,
error: response.error
};
}
catch (error) {
return {
success: false,
stackName,
status: 'DELETE_FAILED',
error: `Backend deletion error: ${error.message}`
};
}
}
/**
* Get stack status via backend API
*/
async getStackStatus(stackName, includeEvents = false) {
try {
const queryParams = `?stackName=${encodeURIComponent(stackName)}&includeEvents=${includeEvents}`;
const response = await this.makeRequest('/cfn/status', 'GET', null, queryParams);
return {
success: response.success,
stackId: response.stackId,
stackName: response.stackName,
status: response.status,
outputs: response.outputs,
error: response.error,
failureDetails: response.failureAnalysis,
rollbackInfo: response.rollbackInfo,
recentEvents: response.recentEvents
};
}
catch (error) {
return {
success: false,
status: 'ERROR',
error: `Backend status error: ${error.message}`
};
}
}
/**
* List stacks via backend API
*/
async listStacks() {
try {
const response = await this.makeRequest('/cfn/list', 'GET');
return response.stacks || [];
}
catch (error) {
console.error('Backend list error:', error);
return [];
}
}
/**
* Get user profile to determine deployment preferences
*/
async getUserProfile() {
try {
const response = await this.makeRequest('/user/profile', 'GET');
return {
deploymentType: response.deploymentType || 'FRESH',
allowedSharedStacks: response.allowedSharedStacks || []
};
}
catch (error) {
// Default to FRESH if can't determine
console.warn('Could not determine user deployment type, defaulting to FRESH');
return { deploymentType: 'FRESH' };
}
}
/**
* Generate presigned URL for S3 operations via backend API
*/
async getPresignedUrl(params) {
try {
const response = await this.makeRequest('/storage/presigned-url', 'POST', {
appName: params.appName, // Changed from stackName to appName
objectKey: params.objectKey,
operation: params.operation,
expirationMinutes: params.expirationMinutes || 60
});
return {
success: true,
presignedUrl: response.presignedUrl,
bucketName: response.bucketName,
objectKey: response.objectKey,
operation: response.operation,
expiresAt: response.expiresAt,
websiteUrl: response.websiteUrl
};
}
catch (error) {
return {
success: false,
error: `Backend presigned URL error: ${error.message}`
};
}
}
/**
* Generate batch presigned URLs for multiple file operations
*/
async getBatchPresignedUrls(params) {
try {
const response = await this.makeRequest('/storage/batch-presigned-urls', 'POST', {
appName: params.appName,
files: params.files,
operation: params.operation,
expirationMinutes: params.expirationMinutes || 60
});
return {
success: response.success || false,
urls: response.urls,
bucketName: response.bucketName,
websiteUrl: response.websiteUrl,
totalFiles: response.totalFiles,
successfulUrls: response.successfulUrls,
errors: response.errors,
error: response.error
};
}
catch (error) {
return {
success: false,
error: `Backend batch presigned URL error: ${error.message}`
};
}
}
/**
* Make HTTP request to backend API
*/
async makeRequest(endpoint, method, body, queryParams) {
const url = `${this.config.apiEndpoint}${endpoint}${queryParams || ''}`;
const options = {
method,
headers: {
'Content-Type': 'application/json',
'x-mcp-api-key': this.config.apiKey // Fixed: Use correct header name
}
};
if (body && method !== 'GET') {
options.body = JSON.stringify(body);
}
const response = await fetch(url, options);
if (!response.ok) {
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
let errorBody = null;
try {
errorBody = await response.json();
if (errorBody.error) {
errorMessage = errorBody.error;
}
}
catch {
// Use default error message if can't parse response
}
// Special handling for region selection errors - don't throw, return the error body
if (response.status === 400 && errorBody?.allowedRegions) {
return errorBody; // Return the region selection response instead of throwing
}
throw new Error(errorMessage);
}
return await response.json();
}
}
//# sourceMappingURL=backend-client.js.map