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
JavaScript
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
};
}
}