UNPKG

@scarlet-mesh/mcp-products

Version:

Comprehensive Red Hat Product Lifecycle Management MCP Server with advanced features for roadmap analysis, migration planning, compliance reporting, portfolio optimization, and cost analysis

768 lines (767 loc) 30.5 kB
#!/usr/bin/env node import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; // Create server instance const server = new McpServer({ name: 'product-lifecycle-api', version: '1.0.0', capabilities: { resources: {}, tools: {}, }, }); const productData = await fetch(`https://access.redhat.com/product-life-cycles/api/v1/products/`) .then((res) => res.json()) .then((data) => data.data); // Helper function to load product data function loadProductData() { // Sample data - replace with your full dataset return productData; } // Helper function to get product by name function getProductByName(name) { const products = loadProductData(); return (products.find((p) => p.name.toLowerCase() === name.toLowerCase() || p.former_names.some((fn) => fn.toLowerCase() === name.toLowerCase())) || null); } // Helper function to format dates nicely function formatDate(dateString) { const date = new Date(dateString); return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric', }); } // Helper function to determine product status based on dates function getLifecycleStatus(version) { const now = new Date(); // Find GA date const gaPhase = version.phases.find((p) => p.name === 'General availability'); if (!gaPhase || new Date(gaPhase.date) > now) { return 'Future Release'; } // Find EOL date const eolPhase = version.phases.find((p) => p.name === 'End of Life'); if (eolPhase && new Date(eolPhase.date) <= now) { return 'End of Life'; } // Find Full Support end date const fullSupportPhase = version.phases.find((p) => p.name === 'Full support'); if (fullSupportPhase && new Date(fullSupportPhase.date) <= now) { return 'Maintenance Support'; } return 'Full Support'; } // Helper function to get time remaining in current phase function getTimeRemaining(version) { const now = new Date(); const status = getLifecycleStatus(version); if (status === 'End of Life') { return 'No support remaining'; } if (status === 'Future Release') { const gaPhase = version.phases.find((p) => p.name === 'General availability'); if (gaPhase) { const gaDate = new Date(gaPhase.date); const daysUntilGA = Math.ceil((gaDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)); return `${daysUntilGA} days until General Availability`; } return 'Release date undetermined'; } // For Full Support or Maintenance Support, calculate time to next phase const nextPhase = status === 'Full Support' ? version.phases.find((p) => p.name === 'Full support') : version.phases.find((p) => p.name === 'End of Life'); if (nextPhase) { const nextDate = new Date(nextPhase.date); const daysRemaining = Math.ceil((nextDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)); return `${daysRemaining} days remaining`; } return 'Unknown'; } // Register tool: Get all products server.tool('get-all-products', 'Get a list of all available products', {}, async () => { const products = loadProductData(); if (!products || products.length === 0) { return { content: [ { type: 'text', text: 'No product data available.', }, ], }; } const productNames = products.map((p) => p.name).join(', '); return { content: [ { type: 'text', text: `Available products: ${productNames}`, }, ], }; }); // Register tool: Get product info by name server.tool('get-product-info', 'Get detailed information about a specific product', { productName: z.string().describe('The name of the product to look up'), }, async ({ productName }) => { const product = getProductByName(productName); if (!product) { return { content: [ { type: 'text', text: `Product "${productName}" not found. Please check the name and try again.`, }, ], }; } // Build the response text const responseLines = [ `# ${product.name} Product Lifecycle Information`, '', product.former_names.length > 0 ? `Also known as: ${product.former_names.join(', ')}` : '', '', '## Versions', '', ]; product.versions.forEach((version) => { const status = getLifecycleStatus(version); const timeRemaining = getTimeRemaining(version); responseLines.push(`### ${version.name} (${status})`); responseLines.push(''); version.phases.forEach((phase) => { responseLines.push(`- **${phase.name}**: ${formatDate(phase.date)}`); }); responseLines.push(`- **Current Status**: ${status}`); responseLines.push(`- **Time Remaining**: ${timeRemaining}`); responseLines.push(''); }); if (product.footnote) { responseLines.push(`**Note**: ${product.footnote}`); responseLines.push(''); } if (product.link) { responseLines.push(`For more information, visit: ${product.link}`); } return { content: [ { type: 'text', text: responseLines.join('\n'), }, ], }; }); // Register tool: Find versions by support status server.tool('find-versions-by-status', 'Find product versions with a specific support status', { productName: z.string().describe('The name of the product to look up'), status: z .enum([ 'Full Support', 'Maintenance Support', 'End of Life', 'EOL', 'General Availability', 'GA', 'Future Release', ]) .describe('The support status to filter by'), }, async ({ productName, status }) => { const product = getProductByName(productName); if (!product) { return { content: [ { type: 'text', text: `Product "${productName}" not found. Please check the name and try again.`, }, ], }; } const matchingVersions = product.versions.filter((version) => { const versionStatus = getLifecycleStatus(version); return versionStatus === status; }); if (matchingVersions.length === 0) { return { content: [ { type: 'text', text: `No versions of ${product.name} with status "${status}" found.`, }, ], }; } const responseLines = [ `# ${product.name} Versions with Status: ${status}`, '', ]; matchingVersions.forEach((version) => { const timeRemaining = getTimeRemaining(version); responseLines.push(`## ${version.name}`); responseLines.push(''); version.phases.forEach((phase) => { responseLines.push(`- **${phase.name}**: ${formatDate(phase.date)}`); }); responseLines.push(`- **Time Remaining**: ${timeRemaining}`); responseLines.push(''); }); return { content: [ { type: 'text', text: responseLines.join('\n'), }, ], }; }); // Register tool: Find versions expiring within time period server.tool('find-expiring-versions', 'Find product versions expiring within a specified number of days', { productName: z.string().describe('The name of the product to look up'), days: z .number() .describe('Number of days to look ahead for expiring versions'), }, async ({ productName, days }) => { const product = getProductByName(productName); if (!product) { return { content: [ { type: 'text', text: `Product "${productName}" not found. Please check the name and try again.`, }, ], }; } const now = new Date(); const futureDate = new Date(now.getTime() + days * 24 * 60 * 60 * 1000); const expiringVersions = product.versions.filter((version) => { // Check if Full Support or End of Life phases are within the time window return version.phases.some((phase) => { if (phase.name === 'Full support' || phase.name === 'End of Life') { const phaseDate = new Date(phase.date); return phaseDate > now && phaseDate <= futureDate; } return false; }); }); if (expiringVersions.length === 0) { return { content: [ { type: 'text', text: `No versions of ${product.name} are expiring within the next ${days} days.`, }, ], }; } const responseLines = [ `# ${product.name} Versions Expiring Within ${days} Days`, '', ]; expiringVersions.forEach((version) => { const status = getLifecycleStatus(version); responseLines.push(`## ${version.name} (Currently: ${status})`); responseLines.push(''); version.phases.forEach((phase) => { const phaseDate = new Date(phase.date); if (phaseDate > now && phaseDate <= futureDate) { const daysUntil = Math.ceil((phaseDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)); responseLines.push(`- **${phase.name}**: ${formatDate(phase.date)} (in ${daysUntil} days)`); } else { responseLines.push(`- **${phase.name}**: ${formatDate(phase.date)}`); } }); responseLines.push(''); }); return { content: [ { type: 'text', text: responseLines.join('\n'), }, ], }; }); // NEW ADVANCED TOOLS START HERE // Register tool: Product roadmap analysis server.tool('get-product-roadmap', 'Get a comprehensive roadmap view of all product versions with timeline visualization', { productName: z.string().describe('The name of the product to analyze'), timeframeMonths: z.number().optional().describe('Number of months ahead to analyze (default: 24)'), }, async ({ productName, timeframeMonths = 24 }) => { const product = getProductByName(productName); if (!product) { return { content: [ { type: 'text', text: `Product "${productName}" not found.`, }, ], }; } const now = new Date(); const futureDate = new Date(); futureDate.setMonth(futureDate.getMonth() + timeframeMonths); const roadmapEvents = []; product.versions.forEach((version) => { version.phases.forEach((phase) => { const phaseDate = new Date(phase.date); if (phaseDate >= now && phaseDate <= futureDate) { roadmapEvents.push({ version: version.name, phase: phase.name, date: phaseDate, dateString: phase.date, monthsFromNow: Math.round((phaseDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24 * 30)) }); } }); }); // Sort by date roadmapEvents.sort((a, b) => a.date.getTime() - b.date.getTime()); const responseLines = [ `# ${product.name} - Product Roadmap (${timeframeMonths} months)`, '', `## Upcoming Lifecycle Events`, '' ]; if (roadmapEvents.length === 0) { responseLines.push(`No lifecycle events scheduled for the next ${timeframeMonths} months.`); } else { roadmapEvents.forEach((event) => { responseLines.push(`### ${formatDate(event.dateString)} (${event.monthsFromNow} months)`); responseLines.push(`- **Version**: ${event.version}`); responseLines.push(`- **Event**: ${event.phase}`); responseLines.push(''); }); } return { content: [ { type: 'text', text: responseLines.join('\n'), }, ], }; }); // Register tool: Migration planning server.tool('get-migration-plan', 'Generate a migration plan from one product version to recommended newer versions', { productName: z.string().describe('The name of the product'), currentVersion: z.string().describe('The current version to migrate from'), urgencyLevel: z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Migration urgency level'), }, async ({ productName, currentVersion, urgencyLevel = 'medium' }) => { const product = getProductByName(productName); if (!product) { return { content: [ { type: 'text', text: `Product "${productName}" not found.`, }, ], }; } const sourceVersion = product.versions.find(v => v.name.toLowerCase().includes(currentVersion.toLowerCase()) || currentVersion.toLowerCase().includes(v.name.toLowerCase())); if (!sourceVersion) { return { content: [ { type: 'text', text: `Version "${currentVersion}" not found for ${productName}.`, }, ], }; } const sourceStatus = getLifecycleStatus(sourceVersion); const now = new Date(); // Find recommended target versions const recommendedVersions = product.versions .filter(v => { const status = getLifecycleStatus(v); return status === 'Full Support' || status === 'General Availability'; }) .map(v => ({ version: v, status: getLifecycleStatus(v), timeRemaining: getTimeRemaining(v) })) .sort((a, b) => { // Prefer Full Support over GA, then by time remaining if (a.status !== b.status) { return a.status === 'Full Support' ? -1 : 1; } return 0; }); const responseLines = [ `# Migration Plan: ${product.name}`, '', `## Current Version Analysis`, `- **Version**: ${sourceVersion.name}`, `- **Current Status**: ${sourceStatus}`, `- **Time Remaining**: ${getTimeRemaining(sourceVersion)}`, '', `## Migration Urgency: ${urgencyLevel.toUpperCase()}`, '' ]; if (sourceStatus === 'End of Life') { responseLines.push('🚨 **CRITICAL**: Current version is End of Life - immediate migration required!'); } else if (sourceStatus === 'Maintenance Support') { responseLines.push('⚠️ **WARNING**: Current version is in Maintenance Support - migration recommended'); } else { responseLines.push('✅ **STATUS**: Current version has active support'); } responseLines.push('', '## Recommended Target Versions', ''); if (recommendedVersions.length === 0) { responseLines.push('❌ No actively supported versions available for migration.'); } else { recommendedVersions.slice(0, 3).forEach((rec, index) => { responseLines.push(`### Option ${index + 1}: ${rec.version.name}`); responseLines.push(`- **Status**: ${rec.status}`); responseLines.push(`- **Support Duration**: ${rec.timeRemaining}`); responseLines.push(`- **Recommendation**: ${index === 0 ? 'Primary choice' : index === 1 ? 'Alternative' : 'Fallback'}`); responseLines.push(''); }); } // Add urgency-based timeline const urgencyTimelines = { low: '6-12 months', medium: '3-6 months', high: '1-3 months', critical: 'Immediate (within 30 days)' }; responseLines.push('## Recommended Timeline'); responseLines.push(`Based on **${urgencyLevel}** urgency: ${urgencyTimelines[urgencyLevel]}`); return { content: [ { type: 'text', text: responseLines.join('\n'), }, ], }; }); // Register tool: Compliance and risk assessment server.tool('get-compliance-report', 'Generate a compliance and risk assessment report for product versions in use', { productVersions: z.array(z.object({ productName: z.string(), versionName: z.string() })).describe('Array of product and version pairs to assess'), complianceFramework: z.enum(['general', 'pci-dss', 'hipaa', 'sox', 'iso27001']).optional().describe('Compliance framework context'), }, async ({ productVersions, complianceFramework = 'general' }) => { const assessments = []; for (const pv of productVersions) { const product = getProductByName(pv.productName); if (!product) { assessments.push({ productName: pv.productName, versionName: pv.versionName, status: 'UNKNOWN', risk: 'HIGH', reason: 'Product not found in lifecycle database' }); continue; } const version = product.versions.find(v => v.name.toLowerCase().includes(pv.versionName.toLowerCase()) || pv.versionName.toLowerCase().includes(v.name.toLowerCase())); if (!version) { assessments.push({ productName: pv.productName, versionName: pv.versionName, status: 'UNKNOWN', risk: 'HIGH', reason: 'Version not found in lifecycle database' }); continue; } const status = getLifecycleStatus(version); const timeRemaining = getTimeRemaining(version); let risk = 'LOW'; let reason = 'Version has active support'; if (status === 'End of Life') { risk = 'CRITICAL'; reason = 'Version is End of Life - no security updates'; } else if (status === 'Maintenance Support') { risk = 'HIGH'; reason = 'Version in maintenance mode - limited updates'; } else if (status === 'Future Release') { risk = 'MEDIUM'; reason = 'Version not yet released - may have stability issues'; } else if (timeRemaining.includes('days remaining') && parseInt(timeRemaining) < 180) { risk = 'MEDIUM'; reason = 'Version support ending within 6 months'; } assessments.push({ productName: pv.productName, versionName: pv.versionName, status, risk, reason, timeRemaining }); } const responseLines = [ `# Compliance Risk Assessment Report`, `**Framework**: ${complianceFramework.toUpperCase()}`, `**Generated**: ${new Date().toISOString().split('T')[0]}`, '', '## Summary', '' ]; const riskCounts = { CRITICAL: assessments.filter(a => a.risk === 'CRITICAL').length, HIGH: assessments.filter(a => a.risk === 'HIGH').length, MEDIUM: assessments.filter(a => a.risk === 'MEDIUM').length, LOW: assessments.filter(a => a.risk === 'LOW').length }; responseLines.push(`- 🔴 **Critical Risk**: ${riskCounts.CRITICAL} products`); responseLines.push(`- 🟡 **High Risk**: ${riskCounts.HIGH} products`); responseLines.push(`- 🟠 **Medium Risk**: ${riskCounts.MEDIUM} products`); responseLines.push(`- 🟢 **Low Risk**: ${riskCounts.LOW} products`); responseLines.push(''); responseLines.push('## Detailed Assessment', ''); assessments.forEach((assessment) => { const riskEmoji = { CRITICAL: '🔴', HIGH: '🟡', MEDIUM: '🟠', LOW: '🟢' }; responseLines.push(`### ${assessment.productName} ${assessment.versionName} ${riskEmoji[assessment.risk] || '⚪'}`); responseLines.push(`- **Status**: ${assessment.status}`); responseLines.push(`- **Risk Level**: ${assessment.risk}`); responseLines.push(`- **Reason**: ${assessment.reason}`); if (assessment.timeRemaining) { responseLines.push(`- **Time Remaining**: ${assessment.timeRemaining}`); } responseLines.push(''); }); // Add compliance-specific recommendations if (complianceFramework !== 'general') { responseLines.push('## Compliance Framework Considerations', ''); const frameworkGuidance = { 'pci-dss': 'PCI DSS requires supported software versions with security patches. End-of-life versions pose compliance risks.', 'hipaa': 'HIPAA requires administrative safeguards including software updates. Unsupported versions may violate requirements.', 'sox': 'SOX requires IT controls including patch management. Document all version decisions and upgrade plans.', 'iso27001': 'ISO 27001 requires asset management and vulnerability management. Maintain software inventory and update schedules.' }; responseLines.push(frameworkGuidance[complianceFramework] || ''); } return { content: [ { type: 'text', text: responseLines.join('\n'), }, ], }; }); // Register tool: Portfolio optimization analysis server.tool('analyze-portfolio-optimization', 'Analyze an entire product portfolio and provide optimization recommendations', { portfolio: z.array(z.object({ productName: z.string(), versions: z.array(z.string()), criticality: z.enum(['low', 'medium', 'high', 'critical']).optional() })).describe('Portfolio of products with their versions and criticality'), optimizationGoal: z.enum(['cost', 'risk', 'performance', 'compliance']).optional().describe('Primary optimization goal'), }, async ({ portfolio, optimizationGoal = 'risk' }) => { const portfolioAnalysis = []; let totalProducts = 0; let riskScore = 0; for (const portfolioItem of portfolio) { const product = getProductByName(portfolioItem.productName); if (!product) continue; const productAnalysis = { productName: portfolioItem.productName, criticality: portfolioItem.criticality || 'medium', versions: [], recommendations: [], riskScore: 0 }; for (const versionName of portfolioItem.versions) { const version = product.versions.find(v => v.name.toLowerCase().includes(versionName.toLowerCase())); if (version) { const status = getLifecycleStatus(version); const timeRemaining = getTimeRemaining(version); let versionRisk = 0; if (status === 'End of Life') versionRisk = 4; else if (status === 'Maintenance Support') versionRisk = 3; else if (status === 'Full Support' && timeRemaining.includes('days remaining') && parseInt(timeRemaining) < 365) versionRisk = 2; else versionRisk = 1; productAnalysis.versions.push({ name: versionName, status, timeRemaining, riskScore: versionRisk }); productAnalysis.riskScore += versionRisk; } } // Generate recommendations based on optimization goal if (optimizationGoal === 'risk') { if (productAnalysis.riskScore > 6) { productAnalysis.recommendations.push('HIGH PRIORITY: Immediate migration required'); } else if (productAnalysis.riskScore > 3) { productAnalysis.recommendations.push('MEDIUM PRIORITY: Plan migration within 6 months'); } } else if (optimizationGoal === 'cost') { if (productAnalysis.versions.length > 2) { productAnalysis.recommendations.push('CONSOLIDATION: Reduce version diversity to lower maintenance costs'); } } portfolioAnalysis.push(productAnalysis); totalProducts++; riskScore += productAnalysis.riskScore; } const averageRisk = totalProducts > 0 ? (riskScore / totalProducts).toFixed(2) : '0'; const responseLines = [ `# Portfolio Optimization Analysis`, `**Optimization Goal**: ${optimizationGoal.toUpperCase()}`, `**Total Products**: ${totalProducts}`, `**Average Risk Score**: ${averageRisk}/4.0`, '', '## Executive Summary', '' ]; // Risk assessment if (parseFloat(averageRisk) >= 3.0) { responseLines.push('🔴 **HIGH RISK PORTFOLIO** - Immediate action required'); } else if (parseFloat(averageRisk) >= 2.0) { responseLines.push('🟡 **MEDIUM RISK PORTFOLIO** - Migration planning recommended'); } else { responseLines.push('🟢 **LOW RISK PORTFOLIO** - Good lifecycle management'); } responseLines.push('', '## Product-by-Product Analysis', ''); portfolioAnalysis.forEach((analysis) => { responseLines.push(`### ${analysis.productName} (${analysis.criticality.toUpperCase()} criticality)`); responseLines.push(`**Risk Score**: ${analysis.riskScore}/4.0`); responseLines.push(''); analysis.versions.forEach((v) => { responseLines.push(`- **${v.name}**: ${v.status} (${v.timeRemaining})`); }); if (analysis.recommendations.length > 0) { responseLines.push(''); responseLines.push('**Recommendations**:'); analysis.recommendations.forEach((rec) => { responseLines.push(`- ${rec}`); }); } responseLines.push(''); }); return { content: [ { type: 'text', text: responseLines.join('\n'), }, ], }; }); // Register tool: Cost impact analysis server.tool('calculate-lifecycle-costs', 'Calculate the cost impact of different lifecycle decisions', { scenarios: z.array(z.object({ name: z.string(), productName: z.string(), currentVersion: z.string(), targetVersion: z.string().optional(), migrationTimeMonths: z.number().optional(), systemCount: z.number().optional() })).describe('Different scenarios to analyze'), costFactors: z.object({ supportCostPerYear: z.number().optional(), migrationCostPerSystem: z.number().optional(), downtimeCostPerHour: z.number().optional(), securityIncidentCost: z.number().optional() }).optional().describe('Cost factors for analysis') }, async ({ scenarios, costFactors = {} }) => { const { supportCostPerYear = 10000, migrationCostPerSystem = 5000, downtimeCostPerHour = 1000, securityIncidentCost = 100000 } = costFactors; const responseLines = [ `# Lifecycle Cost Impact Analysis`, '', '## Cost Assumptions', `- Support cost per year: $${supportCostPerYear.toLocaleString()}`, `- Migration cost per system: $${migrationCostPerSystem.toLocaleString()}`, `- Downtime cost per hour: $${downtimeCostPerHour.toLocaleString()}`, `- Security incident cost: $${securityIncidentCost.toLocaleString()}`, '', '## Scenario Analysis', '' ]; scenarios.forEach((scenario) => { const product = getProductByName(scenario.productName); if (!product) return; const currentVersion = product.versions.find(v => v.name.toLowerCase().includes(scenario.currentVersion.toLowerCase())); if (!currentVersion) return; const currentStatus = getLifecycleStatus(currentVersion); const systemCount = scenario.systemCount || 1; const migrationTime = scenario.migrationTimeMonths || 3; let riskMultiplier = 1; if (currentStatus === 'End of Life') riskMultiplier = 5; else if (currentStatus === 'Maintenance Support') riskMultiplier = 2; const annualSupportCost = supportCostPerYear * systemCount; const migrationCost = migrationCostPerSystem * systemCount; const estimatedDowntimeHours = migrationTime * 4; // 4 hours per month during migration const downtimeCost = estimatedDowntimeHours * downtimeCostPerHour; const securityRiskCost = securityIncidentCost * (riskMultiplier - 1) * 0.1; // 10% chance per risk level const totalCost = annualSupportCost + migrationCost + downtimeCost + securityRiskCost; responseLines.push(`### ${scenario.name}`); responseLines.push(`**Product**: ${scenario.productName} ${scenario.currentVersion}`); responseLines.push(`**Current Status**: ${currentStatus}`); responseLines.push(`**Systems**: ${systemCount}`); responseLines.push(''); responseLines.push('**Cost Breakdown**:'); responseLines.push(`- Annual Support: $${annualSupportCost.toLocaleString()}`); responseLines.push(`- Migration: $${migrationCost.toLocaleString()}`); responseLines.push(`- Downtime: $${downtimeCost.toLocaleString()}`); responseLines.push(`- Security Risk: $${securityRiskCost.toLocaleString()}`); responseLines.push(`- **Total Year 1**: $${totalCost.toLocaleString()}`); responseLines.push(''); }); return { content: [ { type: 'text', text: responseLines.join('\n'), }, ], }; }); async function main() { // Replace with your complete product data // productData = loadFullProductData(); const transport = new StdioServerTransport(); await server.connect(transport); console.error('Product Lifecycle MCP Server running on stdio'); } main().catch((error) => { console.error('Fatal error in main():', error); process.exit(1); });