auto-publishing-mcp-server
Version:
Enterprise-grade MCP Server for Auto-Publishing with pre-publish validation, multi-cloud deployment, and monitoring
882 lines (768 loc) • 30.3 kB
JavaScript
import { exec } from 'child_process';
import { promisify } from 'util';
import { promises as fs } from 'fs';
import path from 'path';
const execAsync = promisify(exec);
class DeploymentPipeline {
constructor() {
this.pipelines = new Map();
this.pipelineHistory = [];
this.configPath = '/tmp/deployment-pipeline-config.json';
this.pipelineStages = this.getDefaultPipelineStages();
this.loadConfiguration();
}
async loadConfiguration() {
try {
const config = await fs.readFile(this.configPath, 'utf8');
const parsedConfig = JSON.parse(config);
this.pipelines = new Map(Object.entries(parsedConfig.pipelines || {}));
this.pipelineHistory = parsedConfig.pipelineHistory || [];
} catch (error) {
console.log('No existing pipeline configuration found, starting fresh');
}
}
async saveConfiguration() {
const config = {
pipelines: Object.fromEntries(this.pipelines),
pipelineHistory: this.pipelineHistory,
lastUpdated: new Date().toISOString()
};
await fs.writeFile(this.configPath, JSON.stringify(config, null, 2));
}
getDefaultPipelineStages() {
return {
'pre-validation': {
name: 'Pre-Publish Validation',
description: 'Run comprehensive validation checks',
required: true,
order: 1,
tools: ['validation/run-pre-publish']
},
'security-scan': {
name: 'Security Scanning',
description: 'Scan for security vulnerabilities',
required: true,
order: 2,
tools: ['security/scan-source-code', 'security/scan-docker-image']
},
'build': {
name: 'Build Application',
description: 'Build the application',
required: true,
order: 3,
tools: ['docker/build']
},
'test': {
name: 'Run Tests',
description: 'Execute automated tests',
required: true,
order: 4,
tools: ['custom-test-runner']
},
'deploy-staging': {
name: 'Deploy to Staging',
description: 'Deploy to staging environment',
required: false,
order: 5,
tools: ['deploy/to-environment']
},
'integration-test': {
name: 'Integration Tests',
description: 'Run integration tests on staging',
required: false,
order: 6,
tools: ['custom-integration-test']
},
'deploy-production': {
name: 'Deploy to Production',
description: 'Deploy to production environment',
required: true,
order: 7,
tools: ['deploy/canary']
},
'post-deployment': {
name: 'Post-Deployment Checks',
description: 'Verify deployment success',
required: true,
order: 8,
tools: ['monitor/health-check', 'deploy/get-status']
}
};
}
async createPipeline(args) {
const {
name,
description = '',
stages = Object.keys(this.pipelineStages),
config = {},
environment = 'production',
autoApprove = false
} = args;
const pipelineId = `pipeline_${Date.now()}`;
const pipeline = {
id: pipelineId,
name,
description,
stages: stages.map(stageName => ({
...this.pipelineStages[stageName],
stageName,
status: 'pending',
startTime: null,
endTime: null,
output: '',
error: null
})),
config,
environment,
autoApprove,
status: 'created',
createdAt: new Date().toISOString(),
startedAt: null,
completedAt: null,
currentStage: 0,
totalStages: stages.length
};
this.pipelines.set(pipelineId, pipeline);
await this.saveConfiguration();
return {
success: true,
message: `Pipeline '${name}' created successfully`,
data: {
pipelineId: pipelineId,
pipeline: pipeline
}
};
}
async runPipeline(args) {
const {
pipelineId,
projectPath = '.',
skipStages = [],
stopOnFailure = true
} = args;
if (!this.pipelines.has(pipelineId)) {
return {
success: false,
message: `Pipeline '${pipelineId}' not found`
};
}
const pipeline = this.pipelines.get(pipelineId);
if (pipeline.status === 'running') {
return {
success: false,
message: `Pipeline '${pipelineId}' is already running`
};
}
console.log(`Starting pipeline: ${pipeline.name}`);
pipeline.status = 'running';
pipeline.startedAt = new Date().toISOString();
pipeline.currentStage = 0;
this.pipelines.set(pipelineId, pipeline);
await this.saveConfiguration();
try {
for (let i = 0; i < pipeline.stages.length; i++) {
const stage = pipeline.stages[i];
if (skipStages.includes(stage.stageName)) {
console.log(`Skipping stage: ${stage.name}`);
stage.status = 'skipped';
continue;
}
console.log(`Running stage ${i + 1}/${pipeline.stages.length}: ${stage.name}`);
pipeline.currentStage = i;
stage.status = 'running';
stage.startTime = new Date().toISOString();
this.pipelines.set(pipelineId, pipeline);
await this.saveConfiguration();
try {
const stageResult = await this.runPipelineStage(stage, projectPath, pipeline.config);
stage.status = stageResult.success ? 'passed' : 'failed';
stage.output = stageResult.output || '';
stage.error = stageResult.error || null;
stage.endTime = new Date().toISOString();
if (!stageResult.success) {
console.error(`Stage failed: ${stage.name} - ${stageResult.error}`);
if (stage.required && stopOnFailure) {
pipeline.status = 'failed';
pipeline.completedAt = new Date().toISOString();
this.pipelineHistory.push({
pipelineId: pipelineId,
status: 'failed',
failedStage: stage.stageName,
timestamp: new Date().toISOString()
});
this.pipelines.set(pipelineId, pipeline);
await this.saveConfiguration();
return {
success: false,
message: `Pipeline failed at stage: ${stage.name}`,
data: {
pipelineId: pipelineId,
failedStage: stage.stageName,
error: stageResult.error
}
};
}
}
this.pipelines.set(pipelineId, pipeline);
await this.saveConfiguration();
} catch (stageError) {
stage.status = 'failed';
stage.error = stageError.message;
stage.endTime = new Date().toISOString();
if (stage.required && stopOnFailure) {
pipeline.status = 'failed';
pipeline.completedAt = new Date().toISOString();
this.pipelines.set(pipelineId, pipeline);
await this.saveConfiguration();
return {
success: false,
message: `Pipeline failed at stage: ${stage.name}`,
data: {
pipelineId: pipelineId,
failedStage: stage.stageName,
error: stageError.message
}
};
}
}
}
pipeline.status = 'completed';
pipeline.completedAt = new Date().toISOString();
this.pipelineHistory.push({
pipelineId: pipelineId,
status: 'completed',
timestamp: new Date().toISOString(),
duration: Date.now() - new Date(pipeline.startedAt).getTime()
});
this.pipelines.set(pipelineId, pipeline);
await this.saveConfiguration();
return {
success: true,
message: `Pipeline '${pipeline.name}' completed successfully`,
data: {
pipelineId: pipelineId,
duration: Date.now() - new Date(pipeline.startedAt).getTime(),
stagesRun: pipeline.stages.length
}
};
} catch (error) {
pipeline.status = 'error';
pipeline.completedAt = new Date().toISOString();
this.pipelines.set(pipelineId, pipeline);
await this.saveConfiguration();
return {
success: false,
message: `Pipeline execution error: ${error.message}`,
data: { pipelineId: pipelineId }
};
}
}
async runPipelineStage(stage, projectPath, config) {
const results = [];
for (const toolName of stage.tools) {
try {
const result = await this.executeTool(toolName, stage, projectPath, config);
results.push(result);
if (!result.success) {
return {
success: false,
error: `Tool '${toolName}' failed: ${result.error}`,
output: results.map(r => r.output).join('\n')
};
}
} catch (error) {
return {
success: false,
error: `Tool execution failed: ${error.message}`,
output: results.map(r => r.output).join('\n')
};
}
}
return {
success: true,
output: results.map(r => r.output).join('\n'),
results: results
};
}
async executeTool(toolName, stage, projectPath, config) {
switch (toolName) {
case 'validation/run-pre-publish':
return await this.runPrePublishValidation(projectPath, config);
case 'security/scan-source-code':
return await this.runSecurityScan(projectPath, 'source');
case 'security/scan-docker-image':
return await this.runSecurityScan(projectPath, 'docker');
case 'docker/build':
return await this.runDockerBuild(projectPath, config);
case 'custom-test-runner':
return await this.runTests(projectPath, config);
case 'deploy/to-environment':
return await this.deployToEnvironment(projectPath, config, 'staging');
case 'custom-integration-test':
return await this.runIntegrationTests(projectPath, config);
case 'deploy/canary':
return await this.deployCanary(projectPath, config);
case 'monitor/health-check':
return await this.runHealthCheck(config);
case 'deploy/get-status':
return await this.getDeploymentStatus(config);
default:
throw new Error(`Unknown tool: ${toolName}`);
}
}
async runPrePublishValidation(projectPath, config) {
try {
// Mock validation - in real implementation, this would call the actual validation tool
console.log('Running pre-publish validation...');
// Simulate validation checks
await new Promise(resolve => setTimeout(resolve, 2000));
// Mock result
const validationPassed = Math.random() > 0.1; // 90% success rate for demo
if (validationPassed) {
return {
success: true,
output: 'Pre-publish validation passed successfully'
};
} else {
return {
success: false,
error: 'Validation failed: Found syntax errors in main.js:45',
output: 'Pre-publish validation failed'
};
}
} catch (error) {
return {
success: false,
error: error.message,
output: 'Pre-publish validation error'
};
}
}
async runSecurityScan(projectPath, type) {
try {
console.log(`Running security scan (${type})...`);
// Simulate security scan
await new Promise(resolve => setTimeout(resolve, 3000));
const scanPassed = Math.random() > 0.05; // 95% success rate for demo
if (scanPassed) {
return {
success: true,
output: `Security scan (${type}) completed - No critical issues found`
};
} else {
return {
success: false,
error: 'Security scan failed: Found high-severity vulnerability in dependency',
output: `Security scan (${type}) failed`
};
}
} catch (error) {
return {
success: false,
error: error.message,
output: `Security scan (${type}) error`
};
}
}
async runDockerBuild(projectPath, config) {
try {
console.log('Building Docker image...');
const imageName = config.imageName || 'app:latest';
// Simulate Docker build
await new Promise(resolve => setTimeout(resolve, 5000));
const buildSuccess = Math.random() > 0.02; // 98% success rate for demo
if (buildSuccess) {
return {
success: true,
output: `Docker image '${imageName}' built successfully`
};
} else {
return {
success: false,
error: 'Docker build failed: Missing dependency in Dockerfile',
output: 'Docker build failed'
};
}
} catch (error) {
return {
success: false,
error: error.message,
output: 'Docker build error'
};
}
}
async runTests(projectPath, config) {
try {
console.log('Running tests...');
// Simulate test execution
await new Promise(resolve => setTimeout(resolve, 4000));
const testsPass = Math.random() > 0.05; // 95% success rate for demo
if (testsPass) {
return {
success: true,
output: 'All tests passed (45/45)'
};
} else {
return {
success: false,
error: '3 tests failed',
output: 'Tests failed (42/45 passed)'
};
}
} catch (error) {
return {
success: false,
error: error.message,
output: 'Test execution error'
};
}
}
async deployToEnvironment(projectPath, config, environment) {
try {
console.log(`Deploying to ${environment}...`);
// Simulate deployment
await new Promise(resolve => setTimeout(resolve, 6000));
const deploySuccess = Math.random() > 0.03; // 97% success rate for demo
if (deploySuccess) {
return {
success: true,
output: `Successfully deployed to ${environment} environment`
};
} else {
return {
success: false,
error: `Deployment to ${environment} failed: Connection timeout`,
output: `Deployment to ${environment} failed`
};
}
} catch (error) {
return {
success: false,
error: error.message,
output: `Deployment to ${environment} error`
};
}
}
async runIntegrationTests(projectPath, config) {
try {
console.log('Running integration tests...');
// Simulate integration tests
await new Promise(resolve => setTimeout(resolve, 3000));
const testsPass = Math.random() > 0.1; // 90% success rate for demo
if (testsPass) {
return {
success: true,
output: 'Integration tests passed (12/12)'
};
} else {
return {
success: false,
error: '2 integration tests failed',
output: 'Integration tests failed (10/12 passed)'
};
}
} catch (error) {
return {
success: false,
error: error.message,
output: 'Integration test error'
};
}
}
async deployCanary(projectPath, config) {
try {
console.log('Deploying canary release...');
// Simulate canary deployment
await new Promise(resolve => setTimeout(resolve, 4000));
const deploySuccess = Math.random() > 0.02; // 98% success rate for demo
if (deploySuccess) {
return {
success: true,
output: 'Canary deployment completed - 10% traffic routed to new version'
};
} else {
return {
success: false,
error: 'Canary deployment failed: Health checks failing',
output: 'Canary deployment failed'
};
}
} catch (error) {
return {
success: false,
error: error.message,
output: 'Canary deployment error'
};
}
}
async runHealthCheck(config) {
try {
console.log('Running health checks...');
// Simulate health check
await new Promise(resolve => setTimeout(resolve, 1000));
const healthy = Math.random() > 0.01; // 99% success rate for demo
if (healthy) {
return {
success: true,
output: 'All health checks passed'
};
} else {
return {
success: false,
error: 'Health check failed: Database connection timeout',
output: 'Health checks failed'
};
}
} catch (error) {
return {
success: false,
error: error.message,
output: 'Health check error'
};
}
}
async getDeploymentStatus(config) {
try {
console.log('Checking deployment status...');
// Simulate status check
await new Promise(resolve => setTimeout(resolve, 500));
return {
success: true,
output: 'Deployment status: Running, 3 replicas healthy'
};
} catch (error) {
return {
success: false,
error: error.message,
output: 'Status check error'
};
}
}
async getPipelineStatus(args) {
const { pipelineId } = args;
if (!this.pipelines.has(pipelineId)) {
return {
success: false,
message: `Pipeline '${pipelineId}' not found`
};
}
const pipeline = this.pipelines.get(pipelineId);
return {
success: true,
data: {
pipelineId: pipelineId,
name: pipeline.name,
status: pipeline.status,
currentStage: pipeline.currentStage,
totalStages: pipeline.totalStages,
startedAt: pipeline.startedAt,
completedAt: pipeline.completedAt,
stages: pipeline.stages.map(stage => ({
name: stage.name,
status: stage.status,
startTime: stage.startTime,
endTime: stage.endTime,
error: stage.error
}))
}
};
}
async listPipelines(args) {
const { status = 'all', limit = 50 } = args;
let pipelines = Array.from(this.pipelines.values());
if (status !== 'all') {
pipelines = pipelines.filter(p => p.status === status);
}
// Sort by creation date (newest first)
pipelines.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
// Limit results
pipelines = pipelines.slice(0, limit);
return {
success: true,
data: {
pipelines: pipelines.map(p => ({
id: p.id,
name: p.name,
status: p.status,
environment: p.environment,
createdAt: p.createdAt,
startedAt: p.startedAt,
completedAt: p.completedAt,
currentStage: p.currentStage,
totalStages: p.totalStages
})),
totalCount: pipelines.length
}
};
}
async stopPipeline(args) {
const { pipelineId, reason = 'Manual stop' } = args;
if (!this.pipelines.has(pipelineId)) {
return {
success: false,
message: `Pipeline '${pipelineId}' not found`
};
}
const pipeline = this.pipelines.get(pipelineId);
if (pipeline.status !== 'running') {
return {
success: false,
message: `Pipeline '${pipelineId}' is not running`
};
}
pipeline.status = 'stopped';
pipeline.completedAt = new Date().toISOString();
// Mark current stage as stopped
if (pipeline.currentStage < pipeline.stages.length) {
const currentStage = pipeline.stages[pipeline.currentStage];
if (currentStage.status === 'running') {
currentStage.status = 'stopped';
currentStage.endTime = new Date().toISOString();
currentStage.error = reason;
}
}
this.pipelineHistory.push({
pipelineId: pipelineId,
status: 'stopped',
reason: reason,
timestamp: new Date().toISOString()
});
this.pipelines.set(pipelineId, pipeline);
await this.saveConfiguration();
return {
success: true,
message: `Pipeline '${pipeline.name}' stopped successfully`,
data: {
pipelineId: pipelineId,
reason: reason
}
};
}
getToolDefinitions() {
return [
{
name: 'pipeline/create',
description: 'Create a new deployment pipeline',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Pipeline name'
},
description: {
type: 'string',
description: 'Pipeline description'
},
stages: {
type: 'array',
items: { type: 'string' },
description: 'Pipeline stages to include',
default: ['pre-validation', 'security-scan', 'build', 'test', 'deploy-production', 'post-deployment']
},
config: {
type: 'object',
description: 'Pipeline configuration',
default: {}
},
environment: {
type: 'string',
description: 'Target environment',
default: 'production'
},
autoApprove: {
type: 'boolean',
description: 'Auto-approve pipeline execution',
default: false
}
},
required: ['name']
}
},
{
name: 'pipeline/run',
description: 'Execute a deployment pipeline',
inputSchema: {
type: 'object',
properties: {
pipelineId: {
type: 'string',
description: 'Pipeline ID to execute'
},
projectPath: {
type: 'string',
description: 'Project directory path',
default: '.'
},
skipStages: {
type: 'array',
items: { type: 'string' },
description: 'Stages to skip',
default: []
},
stopOnFailure: {
type: 'boolean',
description: 'Stop pipeline on first failure',
default: true
}
},
required: ['pipelineId']
}
},
{
name: 'pipeline/status',
description: 'Get pipeline execution status',
inputSchema: {
type: 'object',
properties: {
pipelineId: {
type: 'string',
description: 'Pipeline ID to check'
}
},
required: ['pipelineId']
}
},
{
name: 'pipeline/list',
description: 'List deployment pipelines',
inputSchema: {
type: 'object',
properties: {
status: {
type: 'string',
enum: ['all', 'created', 'running', 'completed', 'failed', 'stopped'],
description: 'Filter by pipeline status',
default: 'all'
},
limit: {
type: 'number',
description: 'Maximum number of pipelines to return',
default: 50
}
}
}
},
{
name: 'pipeline/stop',
description: 'Stop a running pipeline',
inputSchema: {
type: 'object',
properties: {
pipelineId: {
type: 'string',
description: 'Pipeline ID to stop'
},
reason: {
type: 'string',
description: 'Reason for stopping',
default: 'Manual stop'
}
},
required: ['pipelineId']
}
}
];
}
}
export default DeploymentPipeline;