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

368 lines (367 loc) 14.2 kB
import { api } from "../lib/api-utils.js"; export const queryResourcesTool = { name: "query_resources", description: "Query actual infrastructure resources (VMs, cluster nodes, networks) with natural language. Supports network placement queries. ⚡ TIP: Run discover_capabilities first to learn available OS types, groups, and statuses in your environment.", inputSchema: { type: "object", properties: { natural_query: { type: "string", description: "Natural language query like 'Windows VMs', 'stopped VMs in production', 'DNS servers', 'VMs named ns01', 'VMs on management network', 'any VMs?'" }, limit: { type: "number", description: "Maximum number of results to return (default: 10, max: 100)", default: 10 }, detail_level: { type: "number", description: "Information detail level: 1=basic (name, status, IP), 2=standard (+ network, plan), 3=full (all available fields)", enum: [1, 2, 3], default: 2 } }, required: ["natural_query"] } }; function parseResourceQuery(query, limit = 10) { const q = query.toLowerCase().trim(); // Determine resource type let resource = 'instances'; // default if (q.includes('cluster') && (q.includes('node') || q.includes('host'))) { resource = 'cluster_nodes'; } else if (q.includes('hypervisor') || q.includes('vme01') || q.includes('vme02') || q.includes('vme03')) { resource = 'cluster_nodes'; } else if (q.includes('host') || q.includes('server')) { resource = 'hosts'; } else if (q.includes('node') && !q.includes('cluster')) { // When someone just says "nodes" without "cluster", assume they want cluster nodes resource = 'cluster_nodes'; } else if (q.includes('network')) { resource = 'networks'; } // Determine action let action = 'list'; if (q.includes('how many') || q.includes('count')) { action = 'count'; } else if (q.includes('any ') || q.includes('summary')) { action = 'summary'; } // Extract filters const filters = {}; // OS Type patterns if (q.includes('windows')) filters.osType = 'windows'; if (q.includes('linux')) filters.osType = 'linux'; if (q.includes('ubuntu')) filters.osType = 'ubuntu'; if (q.includes('centos')) filters.osType = 'centos'; if (q.includes('rocky')) filters.osType = 'rocky'; if (q.includes('debian')) filters.osType = 'debian'; if (q.includes('rhel')) filters.osType = 'rhel'; // Status patterns if (q.includes('running')) filters.status = 'running'; if (q.includes('stopped')) filters.status = 'stopped'; if (q.includes('failed') || q.includes('error')) filters.status = 'failed'; if (q.includes('provisioning')) filters.status = 'provisioning'; // Group patterns if (q.includes('production') || q.includes('prod')) filters.group = 'production'; if (q.includes('development') || q.includes('dev')) filters.group = 'development'; if (q.includes('test') || q.includes('testing')) filters.group = 'test'; // Name patterns (exact matches) const nameMatch = q.match(/named?\s+["']?([a-zA-Z0-9\-_]+)["']?/); if (nameMatch) { filters.name = nameMatch[1]; } // Network patterns if (q.includes('network') || q.includes('vlan')) { const networkMatch = q.match(/on\s+["']?([a-zA-Z0-9\-_\s]+)["']?/); if (networkMatch) { filters.network = networkMatch[1].trim(); } else if (q.includes('management')) { filters.network = 'management'; } else if (q.includes('compute')) { filters.network = 'compute'; } } return { resource, action, filters, limit }; } function filterResults(data, filters) { return data.filter(item => { // OS Type filtering if (filters.osType) { const itemOS = item.osType?.toLowerCase() || ''; const filterOS = filters.osType.toLowerCase(); if (filterOS === 'windows' && !itemOS.includes('windows')) return false; if (filterOS === 'linux' && !itemOS.includes('linux') && !itemOS.includes('ubuntu') && !itemOS.includes('centos') && !itemOS.includes('rocky') && !itemOS.includes('debian') && !itemOS.includes('rhel')) return false; if (filterOS !== 'windows' && filterOS !== 'linux' && !itemOS.includes(filterOS)) return false; } // Status filtering if (filters.status) { const itemStatus = item.status?.toLowerCase() || ''; if (!itemStatus.includes(filters.status.toLowerCase())) return false; } // Group filtering if (filters.group) { const itemGroup = item.group?.name?.toLowerCase() || item.site?.name?.toLowerCase() || ''; if (!itemGroup.includes(filters.group.toLowerCase())) return false; } // Name filtering if (filters.name) { const itemName = item.name?.toLowerCase() || ''; if (!itemName.includes(filters.name.toLowerCase())) return false; } // Network filtering if (filters.network) { const networkName = item.interfaces?.[0]?.network?.name?.toLowerCase() || ''; const networkPool = item.interfaces?.[0]?.network?.pool?.name?.toLowerCase() || ''; const filterNetwork = filters.network.toLowerCase(); if (!networkName.includes(filterNetwork) && !networkPool.includes(filterNetwork)) { return false; } } return true; }); } function mapItemByDetailLevel(item, resource, detailLevel) { // Level 1: Basic information only const basicInfo = { id: item.id, name: item.name, status: item.status }; if (detailLevel === 1) { return { ...basicInfo, ...(resource === 'instances' && { ipAddress: item.connectionInfo?.[0]?.ip || item.externalIp || item.internalIp }) }; } // Level 2: Standard information (default) const standardInfo = { ...basicInfo, osType: item.osType, group: item.group?.name || item.site?.name || 'unknown', ...(resource === 'instances' && { plan: item.plan?.name, powerState: item.powerState, ipAddress: item.connectionInfo?.[0]?.ip || item.externalIp || item.internalIp, networkName: item.interfaces?.[0]?.network?.name, networkPool: item.interfaces?.[0]?.network?.pool?.name }) }; if (detailLevel === 2) { return standardInfo; } // Level 3: Full information return { ...standardInfo, ...(resource === 'instances' && { networkId: item.interfaces?.[0]?.network?.id, interfaceCount: item.interfaces?.length || 0, allInterfaces: item.interfaces?.map((iface) => ({ id: iface.id, networkName: iface.network?.name, ipAddress: iface.ipAddress, ipMode: iface.ipMode })) || [], firewallEnabled: item.firewallEnabled, networkLevel: item.networkLevel, externalId: item.externalId, hostname: item.hostname, displayName: item.displayName, maxMemory: item.maxMemory, maxCores: item.maxCores, dateCreated: item.dateCreated, lastUpdated: item.lastUpdated }), ...(resource === 'hosts' && { serverType: item.serverType?.name, zone: item.zone?.name, powerState: item.powerState, agentInstalled: item.agentInstalled, maxMemory: item.maxMemory, maxCores: item.maxCores, freeMemory: item.freeMemory, usedMemory: item.usedMemory }), ...(resource === 'networks' && { type: item.type?.name, zone: item.zone?.name, pool: item.pool?.name, dhcpServer: item.dhcpServer, dnsPrimary: item.dnsPrimary, dnsSecondary: item.dnsSecondary, gateway: item.gateway, netmask: item.netmask, cidr: item.cidr }), ...(resource === 'cluster_nodes' && { hostname: item.hostname, serverType: item.serverType?.name || item.serverType, powerState: item.powerState, cpuCount: item.maxCpu, memoryGB: item.maxMemory ? Math.floor(item.maxMemory / (1024 * 1024 * 1024)) : undefined, vmCount: item.instances?.length || 0 }) }; } function formatResponse(parsedQuery, results, totalCount, detailLevel = 2) { const { action, resource, filters } = parsedQuery; if (action === 'count') { return { action: 'count', resource: resource, filters_applied: filters, total_count: results.length, query_summary: `Found ${results.length} ${resource} matching criteria` }; } if (action === 'summary') { const statusCounts = {}; const osCounts = {}; results.forEach(item => { const status = item.status || 'unknown'; const os = item.osType || 'unknown'; statusCounts[status] = (statusCounts[status] || 0) + 1; osCounts[os] = (osCounts[os] || 0) + 1; }); return { action: 'summary', resource: resource, filters_applied: filters, total_count: results.length, summary: { by_status: statusCounts, by_os_type: osCounts }, query_summary: `Found ${results.length} ${resource} matching criteria` }; } // Default: list action const limitedResults = results.slice(0, parsedQuery.limit); return { action: 'list', resource: resource, filters_applied: filters, total_count: results.length, returned_count: limitedResults.length, detail_level: detailLevel, items: limitedResults.map(item => mapItemByDetailLevel(item, resource, detailLevel)), query_summary: `Showing ${limitedResults.length} of ${results.length} ${resource} matching criteria` }; } export async function handleQueryResources(args) { try { const { natural_query, limit = 10, detail_level = 2 } = args; if (!natural_query || typeof natural_query !== 'string') { return { content: [ { type: "text", text: JSON.stringify({ error: "Invalid input", message: "natural_query parameter is required and must be a string", examples: ["any VMs?", "Windows VMs", "stopped VMs in production", "Ubuntu hosts"] }, null, 2) } ], isError: true }; } const parsedQuery = parseResourceQuery(natural_query, Math.min(limit, 100)); // Map resource type to API endpoint const endpointMap = { 'instances': '/instances', 'hosts': '/servers', 'networks': '/networks', 'cluster_nodes': '/clusters' }; const endpoint = endpointMap[parsedQuery.resource]; // Query the VME API const response = await api.get(endpoint); // Extract data array from response let rawData = []; if (parsedQuery.resource === 'instances' && response.data.instances) { rawData = response.data.instances; } else if (parsedQuery.resource === 'hosts' && response.data.servers) { rawData = response.data.servers; } else if (parsedQuery.resource === 'networks' && response.data.networks) { rawData = response.data.networks; } else if (parsedQuery.resource === 'cluster_nodes' && response.data.clusters) { // Extract hypervisor nodes from clusters const allNodes = []; for (const cluster of response.data.clusters) { if (cluster.servers && Array.isArray(cluster.servers)) { allNodes.push(...cluster.servers); } } rawData = allNodes; } else { // Fallback: try to find array in response const possibleArrays = Object.values(response.data).filter(Array.isArray); if (possibleArrays.length > 0) { rawData = possibleArrays[0]; } } // Apply filters const filteredResults = filterResults(rawData, parsedQuery.filters); // Format response based on action const formattedResponse = formatResponse(parsedQuery, filteredResults, rawData.length, detail_level); return { content: [ { type: "text", text: JSON.stringify(formattedResponse, null, 2) } ], isError: false }; } catch (error) { return { content: [ { type: "text", text: JSON.stringify({ error: "Resource query failed", message: error.message, natural_query: args?.natural_query || "unknown", suggestion: "Check VME API connectivity or try discover_capabilities first to see available resources" }, null, 2) } ], isError: true }; } }