UNPKG

vme-mcp-server

Version:

An intelligent Model Context Protocol (MCP) server that transforms HPE VM Essentials (VME) infrastructure management into natural language conversations. Provision VMs, manage resources, and control your infrastructure through simple English commands with

274 lines (273 loc) 13.1 kB
import axios from "axios"; // Lazy axios instance creation to ensure environment variables are loaded let _api = null; function createApiInstance() { if (!_api) { console.log('VME API Configuration:'); console.log(' Base URL:', process.env.VME_API_BASE_URL); console.log(' Token:', process.env.VME_API_TOKEN ? '***configured***' : 'NOT SET'); if (!process.env.VME_API_BASE_URL || !process.env.VME_API_TOKEN) { throw new Error('VME API credentials not configured. Please check your .env file.'); } _api = axios.create({ baseURL: process.env.VME_API_BASE_URL, headers: { Authorization: `Bearer ${process.env.VME_API_TOKEN}`, "Content-Type": "application/json" } }); } return _api; } export const api = { get: (url) => createApiInstance().get(url), post: (url, data) => createApiInstance().post(url, data), put: (url, data) => createApiInstance().put(url, data), delete: (url) => createApiInstance().delete(url) }; // Helper function to get available cluster nodes (hypervisor hosts, not VMs) export async function getClusterNodes() { try { // Get actual cluster hypervisor nodes from /clusters endpoint const clusters = await api.get("/clusters"); let allNodes = []; if (clusters.data.clusters) { for (const cluster of clusters.data.clusters) { if (cluster.servers && Array.isArray(cluster.servers)) { const clusterNodes = cluster.servers.map((server) => server.id).filter((id) => id); allNodes.push(...clusterNodes); } } } // Remove duplicates and sort const uniqueNodes = [...new Set(allNodes)].sort((a, b) => a - b); if (uniqueNodes.length > 0) { console.log(`Found ${uniqueNodes.length} cluster hypervisor nodes: ${uniqueNodes.join(', ')}`); return uniqueNodes; } } catch (error) { console.error('Error getting cluster nodes:', error); } // Fallback to known hypervisor nodes if API fails console.warn('Using fallback hypervisor nodes [1, 2, 3] - could not detect cluster hosts'); return [1, 2, 3]; } // Resolve user input parameters to VME API resource IDs export async function resolveInput({ group, cloud, template, size }) { const [groups, zones, instanceTypes, servicePlans] = await Promise.all([ api.get("/groups"), api.get("/zones"), api.get("/instance-types"), api.get("/service-plans") ]); // Find group (case-insensitive) const foundGroup = groups.data.groups.find((g) => g.name.toLowerCase() === group.toLowerCase()); // Find zone/cloud (case-insensitive, handle both terms) const foundZone = zones.data.zones.find((z) => z.name.toLowerCase() === cloud.toLowerCase()); // In HPE VM Essentials, all VMs use "HPE VM" instance type // The actual OS is determined by the image selection const instanceType = instanceTypes.data.instanceTypes.find((t) => t.name === 'HPE VM'); // Find appropriate image based on template using osType properties let selectedImage; const templateLower = template.toLowerCase(); // Get available images to find the right one const images = await api.get("/virtual-images"); if (templateLower.includes('ubuntu')) { // Find Ubuntu images using osType.category and prefer latest version const ubuntuImages = images.data.virtualImages.filter((img) => img.osType && img.osType.category === 'ubuntu').sort((a, b) => { // Sort by version descending (24.04 > 22.04 > 20.04) const aVersion = parseFloat(a.osType.osVersion || '0'); const bVersion = parseFloat(b.osType.osVersion || '0'); return bVersion - aVersion; }); selectedImage = ubuntuImages[0]; } else if (templateLower.includes('rocky')) { // Find Rocky Linux images and prefer latest version (Rocky 9 > Rocky 8) const rockyImages = images.data.virtualImages.filter((img) => img.osType && img.osType.category === 'rocky').sort((a, b) => { const aVersion = parseInt(a.osType.osVersion || '0'); const bVersion = parseInt(b.osType.osVersion || '0'); return bVersion - aVersion; // Latest first }); selectedImage = rockyImages[0]; } else if (templateLower.includes('centos')) { // Find CentOS images and prefer latest version (CentOS 9 > CentOS 8) const centosImages = images.data.virtualImages.filter((img) => img.osType && img.osType.category === 'centos').sort((a, b) => { const aVersion = parseInt(a.osType.osVersion || '0'); const bVersion = parseInt(b.osType.osVersion || '0'); return bVersion - aVersion; // Latest first }); selectedImage = centosImages[0]; } else if (templateLower.includes('debian')) { // Find Debian images and prefer latest version (Debian 12 > Debian 11) const debianImages = images.data.virtualImages.filter((img) => img.osType && img.osType.category === 'debian').sort((a, b) => { const aVersion = parseInt(a.osType.osVersion || '0'); const bVersion = parseInt(b.osType.osVersion || '0'); return bVersion - aVersion; // Latest first }); selectedImage = debianImages[0]; } else if (templateLower.includes('alma')) { // Find AlmaLinux images and prefer latest version (AlmaLinux 9 > AlmaLinux 8) const almaImages = images.data.virtualImages.filter((img) => img.osType && img.osType.category === 'almalinux').sort((a, b) => { const aVersion = parseInt(a.osType.osVersion || '0'); const bVersion = parseInt(b.osType.osVersion || '0'); return bVersion - aVersion; // Latest first }); selectedImage = almaImages[0]; } else if (templateLower.includes('rhel') || templateLower.includes('red hat')) { // Find RHEL images and prefer latest version const rhelImages = images.data.virtualImages.filter((img) => img.osType && img.osType.category === 'rhel').sort((a, b) => { const aVersion = parseInt(a.osType.osVersion || '0'); const bVersion = parseInt(b.osType.osVersion || '0'); return bVersion - aVersion; // Latest first }); selectedImage = rhelImages[0]; } // Default to latest Ubuntu if no specific match if (!selectedImage) { const ubuntuImages = images.data.virtualImages.filter((img) => img.osType && img.osType.category === 'ubuntu').sort((a, b) => { const aVersion = parseFloat(a.osType.osVersion || '0'); const bVersion = parseFloat(b.osType.osVersion || '0'); return bVersion - aVersion; }); selectedImage = ubuntuImages[0] || images.data.virtualImages[0]; } // Find service plan based on size (flexible matching) let plan; const sizeLower = size.toLowerCase(); if (sizeLower.includes('8gb') || sizeLower.includes('8 gb') || sizeLower.includes('medium')) { plan = servicePlans.data.servicePlans.find((p) => p.name.includes('2 CPU, 8GB Memory')); } else if (sizeLower.includes('4gb') || sizeLower.includes('4 gb') || sizeLower.includes('small')) { plan = servicePlans.data.servicePlans.find((p) => p.name.includes('1 CPU, 4GB Memory')); } // Fallback to any available plan if (!plan) { plan = servicePlans.data.servicePlans.find((p) => p.name.includes('CPU') && p.name.includes('Memory')); } return { groupId: foundGroup?.id || groups.data.groups[0]?.id, cloudId: foundZone?.id || zones.data.zones[0]?.id, instanceTypeId: instanceType?.id, servicePlanId: plan?.id, imageId: selectedImage?.id, // Return resolved names for better error reporting resolvedGroup: foundGroup?.name || groups.data.groups[0]?.name, resolvedCloud: foundZone?.name || zones.data.zones[0]?.name, resolvedInstanceType: instanceType?.name, resolvedPlan: plan?.name, resolvedImage: selectedImage?.osType?.name || selectedImage?.name, // Available options for error messages availableGroups: groups.data.groups.map((g) => g.name), availableZones: zones.data.zones.map((z) => z.name) }; } // Unified resource discovery function export async function getResources(type, intent, role) { try { // Fetch all resource types in parallel for comprehensive discovery const [groups, zones, instanceTypes, servicePlans, virtualImages, clusters] = await Promise.all([ api.get("/groups"), api.get("/zones"), api.get("/instance-types"), api.get("/service-plans"), api.get("/virtual-images"), api.get("/clusters") ]); const allResources = { groups: groups.data.groups || [], zones: zones.data.zones || [], instanceTypes: instanceTypes.data.instanceTypes || [], servicePlans: servicePlans.data.servicePlans || [], virtualImages: virtualImages.data.virtualImages || [], clusters: clusters.data.clusters || [] }; // Apply intelligent filtering based on type if (type) { const typeLower = type.toLowerCase(); if (typeLower.includes('compute') || typeLower.includes('vm')) { return { groups: allResources.groups, zones: allResources.zones, servicePlans: allResources.servicePlans, virtualImages: allResources.virtualImages, clusters: allResources.clusters, _filtered_for: 'compute/vm resources' }; } else if (typeLower.includes('network')) { return { zones: allResources.zones, clusters: allResources.clusters, _filtered_for: 'network resources' }; } else if (typeLower.includes('storage')) { return { servicePlans: allResources.servicePlans, zones: allResources.zones, _filtered_for: 'storage resources' }; } } // Apply intent-based filtering if (intent) { const intentLower = intent.toLowerCase(); if (intentLower.includes('create') || intentLower.includes('provision')) { // For creation intents, focus on templates and plans return { groups: allResources.groups, zones: allResources.zones, servicePlans: allResources.servicePlans, virtualImages: allResources.virtualImages.map((img) => ({ id: img.id, name: img.name, osType: img.osType, recommended: img.osType?.category === 'ubuntu' ? true : false })), _filtered_for: 'creation/provisioning intent' }; } else if (intentLower.includes('list') || intentLower.includes('show') || intentLower.includes('discover')) { // For discovery intents, return comprehensive view return { summary: { total_groups: allResources.groups.length, total_zones: allResources.zones.length, total_service_plans: allResources.servicePlans.length, total_os_templates: allResources.virtualImages.length, total_clusters: allResources.clusters.length }, ...allResources, _filtered_for: 'discovery/listing intent' }; } } // Default: return all resources with semantic organization return { compute: { groups: allResources.groups, servicePlans: allResources.servicePlans, virtualImages: allResources.virtualImages }, infrastructure: { zones: allResources.zones, clusters: allResources.clusters, instanceTypes: allResources.instanceTypes }, _organization: 'semantic grouping by function' }; } catch (error) { return { error: `Failed to fetch resources: ${error.response?.data?.message || error.message}`, available_endpoints: ['/groups', '/zones', '/service-plans', '/virtual-images', '/clusters'], fallback_suggestion: 'Try checking individual resource types with specific API endpoints' }; } }