auto-publishing-mcp-server
Version:
Enterprise-grade MCP Server for Auto-Publishing with pre-publish validation, multi-cloud deployment, and monitoring
421 lines (370 loc) • 13.9 kB
JavaScript
/**
* Microsoft Azure Integration Tools
* Deploy and manage applications on Azure Container Instances, AKS, and Azure Functions
*/
import { execSync } from 'child_process';
import fs from 'fs/promises';
import path from 'path';
export class AzureTools {
constructor(config = {}) {
this.subscriptionId = config.subscriptionId || process.env.AZURE_SUBSCRIPTION_ID;
this.resourceGroup = config.resourceGroup || process.env.AZURE_RESOURCE_GROUP || 'autopublishing-rg';
this.location = config.location || process.env.AZURE_LOCATION || 'eastus';
this.tenantId = config.tenantId || process.env.AZURE_TENANT_ID;
}
/**
* Deploy to Azure Container Instances (ACI)
*/
async deployToACI(args) {
const {
containerName,
image,
cpu = 1,
memory = 1.5,
port = 3000,
environment = {},
dnsNameLabel,
restartPolicy = 'Always'
} = args;
if (!containerName || !image) {
throw new Error('containerName and image are required');
}
try {
// Build az container create command
let createCmd = `az container create --resource-group ${this.resourceGroup} --name ${containerName} --image ${image} --cpu ${cpu} --memory ${memory} --restart-policy ${restartPolicy} --location ${this.location}`;
// Add port mapping
createCmd += ` --ports ${port}`;
// Add DNS name label if provided
if (dnsNameLabel) {
createCmd += ` --dns-name-label ${dnsNameLabel}`;
}
// Add environment variables
if (Object.keys(environment).length > 0) {
const envVars = Object.entries(environment).map(([key, value]) => `${key}=${value}`).join(' ');
createCmd += ` --environment-variables ${envVars}`;
}
// Execute deployment
const output = execSync(createCmd, { encoding: 'utf8' });
const result = JSON.parse(output);
return {
output: `Successfully deployed ${containerName} to Azure Container Instances`,
data: {
containerName,
resourceGroup: this.resourceGroup,
location: this.location,
image,
fqdn: result.ipAddress?.fqdn,
ipAddress: result.ipAddress?.ip,
status: result.instanceView?.currentState?.state || 'Unknown'
}
};
} catch (error) {
throw new Error(`ACI deployment failed: ${error.message}`);
}
}
/**
* Deploy to Azure Kubernetes Service (AKS)
*/
async deployToAKS(args) {
const {
clusterName,
appName,
image,
replicas = 1,
port = 3000,
environment = {},
exposeService = true,
serviceType = 'LoadBalancer'
} = args;
if (!clusterName || !appName || !image) {
throw new Error('clusterName, appName, and image are required');
}
try {
// Get AKS credentials
execSync(`az aks get-credentials --resource-group ${this.resourceGroup} --name ${clusterName}`);
// Create deployment
execSync(`kubectl create deployment ${appName} --image=${image} --replicas=${replicas}`);
// Add environment variables if provided
if (Object.keys(environment).length > 0) {
for (const [key, value] of Object.entries(environment)) {
execSync(`kubectl set env deployment/${appName} ${key}=${value}`);
}
}
// Expose service if requested
let externalIp = null;
if (exposeService) {
execSync(`kubectl expose deployment ${appName} --type=${serviceType} --port=80 --target-port=${port}`);
if (serviceType === 'LoadBalancer') {
// Wait for external IP
console.log('Waiting for external IP...');
for (let i = 0; i < 30; i++) {
try {
const serviceOutput = execSync(`kubectl get service ${appName} -o jsonpath='{.status.loadBalancer.ingress[0].ip}'`, { encoding: 'utf8' });
if (serviceOutput && serviceOutput !== '') {
externalIp = serviceOutput;
break;
}
} catch {
// Continue waiting
}
await new Promise(resolve => setTimeout(resolve, 10000)); // Wait 10 seconds
}
}
}
return {
output: `Successfully deployed ${appName} to AKS cluster ${clusterName}`,
data: {
clusterName,
resourceGroup: this.resourceGroup,
appName,
replicas,
externalIp,
serviceType,
status: 'deployed'
}
};
} catch (error) {
throw new Error(`AKS deployment failed: ${error.message}`);
}
}
/**
* Deploy Azure Function
*/
async deployToAzureFunction(args) {
const {
functionAppName,
sourceDir = '.',
runtime = 'node',
runtimeVersion = '18',
storageAccount,
environment = {}
} = args;
if (!functionAppName || !storageAccount) {
throw new Error('functionAppName and storageAccount are required');
}
try {
// Create function app if it doesn't exist
try {
execSync(`az functionapp show --name ${functionAppName} --resource-group ${this.resourceGroup}`, { encoding: 'utf8' });
} catch {
// Function app doesn't exist, create it
execSync(`az functionapp create --resource-group ${this.resourceGroup} --consumption-plan-location ${this.location} --runtime ${runtime} --runtime-version ${runtimeVersion} --functions-version 4 --name ${functionAppName} --storage-account ${storageAccount}`);
}
// Set environment variables
if (Object.keys(environment).length > 0) {
const envSettings = Object.entries(environment).map(([key, value]) => `${key}=${value}`).join(' ');
execSync(`az functionapp config appsettings set --name ${functionAppName} --resource-group ${this.resourceGroup} --settings ${envSettings}`);
}
// Deploy function code
execSync(`cd ${sourceDir} && func azure functionapp publish ${functionAppName}`);
// Get function app details
const output = execSync(`az functionapp show --name ${functionAppName} --resource-group ${this.resourceGroup}`, { encoding: 'utf8' });
const functionApp = JSON.parse(output);
return {
output: `Successfully deployed Azure Function ${functionAppName}`,
data: {
functionAppName,
resourceGroup: this.resourceGroup,
location: this.location,
runtime: `${runtime} ${runtimeVersion}`,
url: functionApp.defaultHostName ? `https://${functionApp.defaultHostName}` : null,
status: functionApp.state
}
};
} catch (error) {
throw new Error(`Azure Function deployment failed: ${error.message}`);
}
}
/**
* Get Azure service status
*/
async getServiceStatus(args) {
const { serviceType, serviceName, clusterName } = args;
try {
switch (serviceType) {
case 'aci':
return await this.getACIStatus(serviceName);
case 'aks':
return await this.getAKSServiceStatus(serviceName, clusterName);
case 'function':
return await this.getAzureFunctionStatus(serviceName);
default:
throw new Error(`Unsupported service type: ${serviceType}`);
}
} catch (error) {
throw new Error(`Failed to get service status: ${error.message}`);
}
}
/**
* Get ACI container status
*/
async getACIStatus(containerName) {
const output = execSync(`az container show --resource-group ${this.resourceGroup} --name ${containerName}`, { encoding: 'utf8' });
const container = JSON.parse(output);
return {
output: `ACI container ${containerName} status`,
data: {
containerName: container.name,
resourceGroup: this.resourceGroup,
location: container.location,
state: container.instanceView?.currentState?.state,
ipAddress: container.ipAddress?.ip,
fqdn: container.ipAddress?.fqdn,
cpu: container.containers[0]?.resources?.requests?.cpu,
memory: container.containers[0]?.resources?.requests?.memoryInGB,
restartCount: container.instanceView?.currentState?.restartCount || 0
}
};
}
/**
* Get AKS service status
*/
async getAKSServiceStatus(serviceName, clusterName) {
// Get AKS credentials first
execSync(`az aks get-credentials --resource-group ${this.resourceGroup} --name ${clusterName}`);
const deploymentOutput = execSync(`kubectl get deployment ${serviceName} -o json`, { encoding: 'utf8' });
const deployment = JSON.parse(deploymentOutput);
let serviceInfo = null;
try {
const serviceOutput = execSync(`kubectl get service ${serviceName} -o json`, { encoding: 'utf8' });
serviceInfo = JSON.parse(serviceOutput);
} catch {
// Service might not exist
}
return {
output: `AKS deployment ${serviceName} status`,
data: {
serviceName,
clusterName,
resourceGroup: this.resourceGroup,
replicas: {
desired: deployment.spec.replicas,
available: deployment.status.availableReplicas || 0,
ready: deployment.status.readyReplicas || 0
},
status: deployment.status.conditions?.[0]?.type || 'Unknown',
externalIp: serviceInfo?.status?.loadBalancer?.ingress?.[0]?.ip,
serviceType: serviceInfo?.spec?.type
}
};
}
/**
* Get Azure Function status
*/
async getAzureFunctionStatus(functionAppName) {
const output = execSync(`az functionapp show --name ${functionAppName} --resource-group ${this.resourceGroup}`, { encoding: 'utf8' });
const functionApp = JSON.parse(output);
return {
output: `Azure Function ${functionAppName} status`,
data: {
functionAppName: functionApp.name,
resourceGroup: this.resourceGroup,
location: functionApp.location,
state: functionApp.state,
url: functionApp.defaultHostName ? `https://${functionApp.defaultHostName}` : null,
runtime: functionApp.siteConfig?.linuxFxVersion || functionApp.siteConfig?.windowsFxVersion,
lastModified: functionApp.lastModifiedTimeUtc
}
};
}
/**
* Scale Azure services
*/
async scaleService(args) {
const { serviceType, serviceName, replicas, clusterName } = args;
try {
switch (serviceType) {
case 'aks':
// Get AKS credentials first
execSync(`az aks get-credentials --resource-group ${this.resourceGroup} --name ${clusterName}`);
execSync(`kubectl scale deployment ${serviceName} --replicas=${replicas}`);
break;
case 'function':
// Azure Functions auto-scale, but we can set the plan
console.log('Azure Functions auto-scale based on demand');
break;
default:
throw new Error(`Scaling not supported for service type: ${serviceType}`);
}
return {
output: `Successfully scaled ${serviceName} to ${replicas} replicas`,
data: {
serviceName,
serviceType,
replicas,
timestamp: new Date().toISOString()
}
};
} catch (error) {
throw new Error(`Failed to scale service: ${error.message}`);
}
}
/**
* Create Azure resources
*/
async setupResources(args) {
const {
createResourceGroup = true,
createAKSCluster = false,
clusterName = 'autopublishing-aks',
createStorageAccount = false,
storageAccountName = 'autopublishingstorage'
} = args;
try {
const results = [];
if (createResourceGroup) {
// Create resource group
execSync(`az group create --name ${this.resourceGroup} --location ${this.location}`);
results.push(`Created resource group ${this.resourceGroup}`);
}
if (createAKSCluster) {
// Create AKS cluster
execSync(`az aks create --resource-group ${this.resourceGroup} --name ${clusterName} --node-count 3 --node-vm-size Standard_B2s --location ${this.location} --enable-addons monitoring --generate-ssh-keys`);
results.push(`Created AKS cluster ${clusterName}`);
}
if (createStorageAccount) {
// Create storage account for Azure Functions
execSync(`az storage account create --name ${storageAccountName} --location ${this.location} --resource-group ${this.resourceGroup} --sku Standard_LRS`);
results.push(`Created storage account ${storageAccountName}`);
}
return {
output: 'Azure resources setup completed',
data: {
subscriptionId: this.subscriptionId,
resourceGroup: this.resourceGroup,
location: this.location,
actions: results
}
};
} catch (error) {
throw new Error(`Resource setup failed: ${error.message}`);
}
}
/**
* Build and push container image to Azure Container Registry
*/
async buildAndPushImage(args) {
const { registryName, imageName, sourceDir = '.', tag = 'latest' } = args;
if (!registryName || !imageName) {
throw new Error('registryName and imageName are required');
}
try {
// Login to ACR
execSync(`az acr login --name ${registryName}`);
const fullImageName = `${registryName}.azurecr.io/${imageName}:${tag}`;
// Build and push image
execSync(`az acr build --registry ${registryName} --image ${imageName}:${tag} ${sourceDir}`);
return {
output: `Successfully built and pushed image ${fullImageName}`,
data: {
imageName: fullImageName,
registry: `${registryName}.azurecr.io`,
tag
}
};
} catch (error) {
throw new Error(`Image build failed: ${error.message}`);
}
}
}
export default AzureTools;