vme-mcp-server 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/README.md +42 -9
- package/dist/lib/api-utils.js +18 -5
- package/dist/lib/vm-parsing.js +27 -19
- package/dist/server.js +29 -3
- package/dist/tools/create-vm.js +25 -1
- package/dist/tools/discover-capabilities.js +1 -1
- package/dist/tools/get-resources.js +1 -1
- package/dist/tools/query-resources.js +142 -19
- package/package.json +1 -1
package/dist/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# VME MCP Server
|
2
2
|
|
3
|
-
Transform
|
3
|
+
Transform HPE VM Essentials infrastructure management into conversational AI interactions with Claude.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -15,8 +15,8 @@ npm install -g vme-mcp-server
|
|
15
15
|
Create a `.env` file:
|
16
16
|
|
17
17
|
```bash
|
18
|
-
#
|
19
|
-
VME_API_BASE_URL=https://your-vme-instance.com/api
|
18
|
+
# HPE VM Essentials Configuration
|
19
|
+
VME_API_BASE_URL=https://your-hpe-vme-instance.com/api
|
20
20
|
VME_API_TOKEN=your-bearer-token
|
21
21
|
|
22
22
|
# Privacy Controls (Optional)
|
@@ -72,7 +72,7 @@ Restart Claude Desktop. You should see the VME MCP Server tools available.
|
|
72
72
|
## Features
|
73
73
|
|
74
74
|
### Intelligent Resource Discovery
|
75
|
-
- Unified API consolidating multiple
|
75
|
+
- Unified API consolidating multiple HPE VM Essentials endpoints
|
76
76
|
- Intent-aware responses based on context
|
77
77
|
- Semantic organization of compute vs infrastructure resources
|
78
78
|
|
@@ -90,11 +90,44 @@ Restart Claude Desktop. You should see the VME MCP Server tools available.
|
|
90
90
|
|
91
91
|
## Configuration
|
92
92
|
|
93
|
+
### Environment Variable Loading (Global Install Support)
|
94
|
+
|
95
|
+
The server automatically searches for `.env` files in multiple locations:
|
96
|
+
|
97
|
+
1. **Current directory**: `./env` (for local development)
|
98
|
+
2. **Home directory**: `~/.env` (for global installs)
|
99
|
+
3. **Config directory**: `~/.config/vme-mcp-server/.env`
|
100
|
+
4. **Documents folder**: `~/Documents/.env.vme-mcp-server`
|
101
|
+
|
102
|
+
### Recommended Configuration Methods
|
103
|
+
|
104
|
+
**Method 1: Direct Environment Variables (Best for Global Install)**
|
105
|
+
|
106
|
+
Add to your Claude Desktop config (`~/.config/claude-desktop/config.json`):
|
107
|
+
|
108
|
+
```json
|
109
|
+
{
|
110
|
+
"mcpServers": {
|
111
|
+
"vme-mcp-server": {
|
112
|
+
"command": "vme-mcp-server",
|
113
|
+
"env": {
|
114
|
+
"VME_API_BASE_URL": "https://your-hpe-vme-instance.com/api",
|
115
|
+
"VME_API_TOKEN": "your-bearer-token"
|
116
|
+
}
|
117
|
+
}
|
118
|
+
}
|
119
|
+
}
|
120
|
+
```
|
121
|
+
|
122
|
+
**Method 2: .env File (Good for Local Development)**
|
123
|
+
|
124
|
+
Create `.env` file in any of the supported locations above.
|
125
|
+
|
93
126
|
### Required Environment Variables
|
94
127
|
|
95
128
|
| Variable | Description | Example |
|
96
129
|
|----------|-------------|---------|
|
97
|
-
| `VME_API_BASE_URL` |
|
130
|
+
| `VME_API_BASE_URL` | HPE VM Essentials API endpoint | `https://vme.company.com/api` |
|
98
131
|
| `VME_API_TOKEN` | Bearer token for authentication | `your-bearer-token` |
|
99
132
|
|
100
133
|
### Optional Environment Variables
|
@@ -117,10 +150,10 @@ Restart Claude Desktop. You should see the VME MCP Server tools available.
|
|
117
150
|
**"API connection failed"**
|
118
151
|
- Verify `VME_API_BASE_URL` is correct and accessible
|
119
152
|
- Check that `VME_API_TOKEN` has sufficient permissions
|
120
|
-
- Ensure network connectivity to
|
153
|
+
- Ensure network connectivity to HPE VM Essentials infrastructure
|
121
154
|
|
122
155
|
**"VM creation failed"**
|
123
|
-
- Verify user permissions in
|
156
|
+
- Verify user permissions in HPE VM Essentials for VM creation
|
124
157
|
- Check that specified service plans and templates exist
|
125
158
|
- Ensure target group/zone has available resources
|
126
159
|
|
@@ -128,7 +161,7 @@ Restart Claude Desktop. You should see the VME MCP Server tools available.
|
|
128
161
|
|
129
162
|
- Check the `.env.example` file for configuration templates
|
130
163
|
- Review error messages for specific guidance
|
131
|
-
- Ensure
|
164
|
+
- Ensure HPE VM Essentials infrastructure is accessible and properly configured
|
132
165
|
|
133
166
|
## Architecture
|
134
167
|
|
@@ -143,6 +176,6 @@ Built with TypeScript and the Model Context Protocol for seamless Claude integra
|
|
143
176
|
|
144
177
|
## Version
|
145
178
|
|
146
|
-
Current version: 0.1.
|
179
|
+
Current version: 0.1.9
|
147
180
|
|
148
181
|
For development documentation and contribution guidelines, see the project repository.
|
package/dist/lib/api-utils.js
CHANGED
@@ -13,19 +13,32 @@ exports.api = axios_1.default.create({
|
|
13
13
|
"Content-Type": "application/json"
|
14
14
|
}
|
15
15
|
});
|
16
|
-
// Helper function to get available cluster nodes
|
16
|
+
// Helper function to get available cluster nodes (hypervisor hosts, not VMs)
|
17
17
|
async function getClusterNodes() {
|
18
18
|
try {
|
19
|
+
// Get actual cluster hypervisor nodes from /clusters endpoint
|
19
20
|
const clusters = await exports.api.get("/clusters");
|
20
|
-
|
21
|
-
if (
|
22
|
-
|
21
|
+
let allNodes = [];
|
22
|
+
if (clusters.data.clusters) {
|
23
|
+
for (const cluster of clusters.data.clusters) {
|
24
|
+
if (cluster.servers && Array.isArray(cluster.servers)) {
|
25
|
+
const clusterNodes = cluster.servers.map((server) => server.id).filter((id) => id);
|
26
|
+
allNodes.push(...clusterNodes);
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
// Remove duplicates and sort
|
31
|
+
const uniqueNodes = [...new Set(allNodes)].sort((a, b) => a - b);
|
32
|
+
if (uniqueNodes.length > 0) {
|
33
|
+
console.log(`Found ${uniqueNodes.length} cluster hypervisor nodes: ${uniqueNodes.join(', ')}`);
|
34
|
+
return uniqueNodes;
|
23
35
|
}
|
24
36
|
}
|
25
37
|
catch (error) {
|
26
38
|
console.error('Error getting cluster nodes:', error);
|
27
39
|
}
|
28
|
-
// Fallback to known nodes if API fails
|
40
|
+
// Fallback to known hypervisor nodes if API fails
|
41
|
+
console.warn('Using fallback hypervisor nodes [1, 2, 3] - could not detect cluster hosts');
|
29
42
|
return [1, 2, 3];
|
30
43
|
}
|
31
44
|
exports.getClusterNodes = getClusterNodes;
|
package/dist/lib/vm-parsing.js
CHANGED
@@ -3,10 +3,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.calculateNodeAssignments = exports.parseVMNames = void 0;
|
4
4
|
// Helper function to parse VM name patterns
|
5
5
|
function parseVMNames(nameInput, count) {
|
6
|
-
// Handle range patterns like "
|
7
|
-
const
|
8
|
-
if (
|
9
|
-
const [, prefix1,
|
6
|
+
// Handle letter range patterns like "ws-a->ws-x" or "ws-a -> ws-x"
|
7
|
+
const letterRangeMatch = nameInput.match(/^(.+?)([a-z])\s*->\s*(.+?)([a-z])$/i);
|
8
|
+
if (letterRangeMatch) {
|
9
|
+
const [, prefix1, startLetter, prefix2, endLetter] = letterRangeMatch;
|
10
|
+
if (prefix1.trim() === prefix2.trim()) {
|
11
|
+
const basePrefix = prefix1.trim();
|
12
|
+
const startCode = startLetter.toLowerCase().charCodeAt(0);
|
13
|
+
const endCode = endLetter.toLowerCase().charCodeAt(0);
|
14
|
+
const names = [];
|
15
|
+
for (let i = startCode; i <= endCode; i++) {
|
16
|
+
const letter = String.fromCharCode(i);
|
17
|
+
names.push(`${basePrefix}${letter}`);
|
18
|
+
}
|
19
|
+
return names;
|
20
|
+
}
|
21
|
+
}
|
22
|
+
// Handle numeric range patterns like "web01->web03" or "web01 -> web03"
|
23
|
+
const numericRangeMatch = nameInput.match(/^(.+?)(\d+)\s*->\s*(.+?)(\d+)$/);
|
24
|
+
if (numericRangeMatch) {
|
25
|
+
const [, prefix1, start, prefix2, end] = numericRangeMatch;
|
10
26
|
if (prefix1.trim() === prefix2.trim()) {
|
11
27
|
const basePrefix = prefix1.trim();
|
12
28
|
const startNum = parseInt(start);
|
@@ -57,21 +73,13 @@ function calculateNodeAssignments(vmNames, nodes, distribution) {
|
|
57
73
|
nodeAssignments.push({ name: vmName, kvmHostId: nodeId });
|
58
74
|
});
|
59
75
|
}
|
60
|
-
else if (distribution &&
|
61
|
-
//
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
vmNames.forEach((vmName, index) => {
|
68
|
-
const nodeId = specifiedNodes[index % specifiedNodes.length];
|
69
|
-
nodeAssignments.push({ name: vmName, kvmHostId: nodeId });
|
70
|
-
});
|
71
|
-
}
|
72
|
-
else {
|
73
|
-
nodeAssignments = vmNames.map(vmName => ({ name: vmName }));
|
74
|
-
}
|
76
|
+
else if (distribution && !['auto', 'spread'].includes(distribution)) {
|
77
|
+
// Distribution is a node/hypervisor name - signal that it needs resolution
|
78
|
+
// Return assignments with the node name for the calling function to resolve
|
79
|
+
nodeAssignments = vmNames.map(vmName => ({
|
80
|
+
name: vmName,
|
81
|
+
nodeNameToResolve: distribution
|
82
|
+
}));
|
75
83
|
}
|
76
84
|
else {
|
77
85
|
// Auto-placement (no kvmHostId specified)
|
package/dist/server.js
CHANGED
@@ -9,15 +9,41 @@ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
9
9
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
10
10
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
11
11
|
const dotenv_1 = __importDefault(require("dotenv"));
|
12
|
+
const fs_1 = require("fs");
|
13
|
+
const path_1 = require("path");
|
14
|
+
const os_1 = require("os");
|
12
15
|
const index_js_2 = require("./tools/index.js");
|
13
|
-
// Load environment variables
|
14
|
-
|
16
|
+
// Load environment variables from multiple locations for global install support
|
17
|
+
function loadEnvironmentConfig() {
|
18
|
+
// Priority order for .env file locations:
|
19
|
+
const envPaths = [
|
20
|
+
// 1. Current working directory (for local development)
|
21
|
+
(0, path_1.join)(process.cwd(), '.env'),
|
22
|
+
// 2. Home directory (for global installs)
|
23
|
+
(0, path_1.join)((0, os_1.homedir)(), '.env'),
|
24
|
+
// 3. XDG config directory (Linux/Mac standard)
|
25
|
+
(0, path_1.join)((0, os_1.homedir)(), '.config', 'vme-mcp-server', '.env'),
|
26
|
+
// 4. User's Documents folder (Windows-friendly)
|
27
|
+
(0, path_1.join)((0, os_1.homedir)(), 'Documents', '.env.vme-mcp-server')
|
28
|
+
];
|
29
|
+
// Try to load .env from the first available location
|
30
|
+
for (const envPath of envPaths) {
|
31
|
+
if ((0, fs_1.existsSync)(envPath)) {
|
32
|
+
dotenv_1.default.config({ path: envPath });
|
33
|
+
console.error(`VME MCP: Loaded config from ${envPath}`);
|
34
|
+
return;
|
35
|
+
}
|
36
|
+
}
|
37
|
+
// If no .env file found, that's okay - environment variables may be set directly
|
38
|
+
console.error('VME MCP: No .env file found, using direct environment variables');
|
39
|
+
}
|
40
|
+
loadEnvironmentConfig();
|
15
41
|
// Disable TLS verification for VME API (if needed)
|
16
42
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
17
43
|
// Create VME MCP server with modular architecture
|
18
44
|
const server = new index_js_1.Server({
|
19
45
|
name: "vme-mcp-server",
|
20
|
-
version: "1.
|
46
|
+
version: "0.1.9",
|
21
47
|
}, {
|
22
48
|
capabilities: {
|
23
49
|
tools: {},
|
package/dist/tools/create-vm.js
CHANGED
@@ -6,7 +6,7 @@ const api_utils_js_1 = require("../lib/api-utils.js");
|
|
6
6
|
const name_resolver_js_1 = require("../lib/name-resolver.js");
|
7
7
|
exports.createVMTool = {
|
8
8
|
name: "create_vm",
|
9
|
-
description: "Provision a new virtual machine",
|
9
|
+
description: "Provision a new virtual machine. ⚡ TIP: Run discover_capabilities first to see available groups, zones, templates, and sizes in your environment.",
|
10
10
|
inputSchema: {
|
11
11
|
type: "object",
|
12
12
|
properties: {
|
@@ -71,6 +71,30 @@ async function handleCreateVM(args) {
|
|
71
71
|
const nodes = await (0, api_utils_js_1.getClusterNodes)();
|
72
72
|
// Calculate node assignments
|
73
73
|
const nodeAssignments = (0, vm_parsing_js_1.calculateNodeAssignments)(vmNames, nodes, distribution);
|
74
|
+
// Resolve any node names to IDs
|
75
|
+
for (const assignment of nodeAssignments) {
|
76
|
+
if (assignment.nodeNameToResolve) {
|
77
|
+
// Query servers to find node with matching name
|
78
|
+
try {
|
79
|
+
const servers = await api_utils_js_1.api.get("/servers");
|
80
|
+
if (servers.data.servers) {
|
81
|
+
const foundServer = servers.data.servers.find((server) => server.name?.toLowerCase() === assignment.nodeNameToResolve?.toLowerCase());
|
82
|
+
if (foundServer) {
|
83
|
+
assignment.kvmHostId = foundServer.id;
|
84
|
+
delete assignment.nodeNameToResolve; // Clean up
|
85
|
+
}
|
86
|
+
else {
|
87
|
+
// If no exact name match, log available servers for debugging
|
88
|
+
const availableNames = servers.data.servers.map((s) => s.name).filter(Boolean);
|
89
|
+
console.warn(`Node '${assignment.nodeNameToResolve}' not found. Available: ${availableNames.join(', ')}`);
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
catch (error) {
|
94
|
+
console.error(`Failed to resolve node name '${assignment.nodeNameToResolve}':`, error);
|
95
|
+
}
|
96
|
+
}
|
97
|
+
}
|
74
98
|
// Use name resolver to get IDs from actual VME environment
|
75
99
|
const groupId = await name_resolver_js_1.nameResolver.resolveNameToId('group', group);
|
76
100
|
const cloudId = await name_resolver_js_1.nameResolver.resolveNameToId('zone', location);
|
@@ -4,7 +4,7 @@ exports.handleDiscoverCapabilities = exports.discoverCapabilitiesTool = void 0;
|
|
4
4
|
const capability_discovery_js_1 = require("../lib/capability-discovery.js");
|
5
5
|
exports.discoverCapabilitiesTool = {
|
6
6
|
name: "discover_capabilities",
|
7
|
-
description: "Discover VME infrastructure capabilities
|
7
|
+
description: "⚡ RECOMMENDED FIRST STEP: Discover VME infrastructure capabilities to learn available resources. Run this tool at least once per session to cache environment data for optimal performance with other tools.",
|
8
8
|
inputSchema: {
|
9
9
|
type: "object",
|
10
10
|
properties: {
|
@@ -4,7 +4,7 @@ exports.handleGetResources = exports.getResourcesTool = void 0;
|
|
4
4
|
const api_utils_js_1 = require("../lib/api-utils.js");
|
5
5
|
exports.getResourcesTool = {
|
6
6
|
name: "get_resources",
|
7
|
-
description: "Discover and explore available VME infrastructure resources with intelligent filtering",
|
7
|
+
description: "Discover and explore available VME infrastructure resources with intelligent filtering. ⚡ TIP: Use discover_capabilities for comprehensive environment discovery first.",
|
8
8
|
inputSchema: {
|
9
9
|
type: "object",
|
10
10
|
properties: {
|
@@ -4,18 +4,24 @@ exports.handleQueryResources = exports.queryResourcesTool = void 0;
|
|
4
4
|
const api_utils_js_1 = require("../lib/api-utils.js");
|
5
5
|
exports.queryResourcesTool = {
|
6
6
|
name: "query_resources",
|
7
|
-
description: "Query actual infrastructure resources (VMs,
|
7
|
+
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.",
|
8
8
|
inputSchema: {
|
9
9
|
type: "object",
|
10
10
|
properties: {
|
11
11
|
natural_query: {
|
12
12
|
type: "string",
|
13
|
-
description: "Natural language query like 'Windows VMs', 'stopped VMs in production', '
|
13
|
+
description: "Natural language query like 'Windows VMs', 'stopped VMs in production', 'DNS servers', 'VMs named ns01', 'VMs on management network', 'any VMs?'"
|
14
14
|
},
|
15
15
|
limit: {
|
16
16
|
type: "number",
|
17
17
|
description: "Maximum number of results to return (default: 10, max: 100)",
|
18
18
|
default: 10
|
19
|
+
},
|
20
|
+
detail_level: {
|
21
|
+
type: "number",
|
22
|
+
description: "Information detail level: 1=basic (name, status, IP), 2=standard (+ network, plan), 3=full (all available fields)",
|
23
|
+
enum: [1, 2, 3],
|
24
|
+
default: 2
|
19
25
|
}
|
20
26
|
},
|
21
27
|
required: ["natural_query"]
|
@@ -25,9 +31,19 @@ function parseResourceQuery(query, limit = 10) {
|
|
25
31
|
const q = query.toLowerCase().trim();
|
26
32
|
// Determine resource type
|
27
33
|
let resource = 'instances'; // default
|
28
|
-
if (q.includes('
|
34
|
+
if (q.includes('cluster') && (q.includes('node') || q.includes('host'))) {
|
35
|
+
resource = 'cluster_nodes';
|
36
|
+
}
|
37
|
+
else if (q.includes('hypervisor') || q.includes('vme01') || q.includes('vme02') || q.includes('vme03')) {
|
38
|
+
resource = 'cluster_nodes';
|
39
|
+
}
|
40
|
+
else if (q.includes('host') || q.includes('server')) {
|
29
41
|
resource = 'hosts';
|
30
42
|
}
|
43
|
+
else if (q.includes('node') && !q.includes('cluster')) {
|
44
|
+
// When someone just says "nodes" without "cluster", assume they want cluster nodes
|
45
|
+
resource = 'cluster_nodes';
|
46
|
+
}
|
31
47
|
else if (q.includes('network')) {
|
32
48
|
resource = 'networks';
|
33
49
|
}
|
@@ -77,6 +93,19 @@ function parseResourceQuery(query, limit = 10) {
|
|
77
93
|
if (nameMatch) {
|
78
94
|
filters.name = nameMatch[1];
|
79
95
|
}
|
96
|
+
// Network patterns
|
97
|
+
if (q.includes('network') || q.includes('vlan')) {
|
98
|
+
const networkMatch = q.match(/on\s+["']?([a-zA-Z0-9\-_\s]+)["']?/);
|
99
|
+
if (networkMatch) {
|
100
|
+
filters.network = networkMatch[1].trim();
|
101
|
+
}
|
102
|
+
else if (q.includes('management')) {
|
103
|
+
filters.network = 'management';
|
104
|
+
}
|
105
|
+
else if (q.includes('compute')) {
|
106
|
+
filters.network = 'compute';
|
107
|
+
}
|
108
|
+
}
|
80
109
|
return { resource, action, filters, limit };
|
81
110
|
}
|
82
111
|
function filterResults(data, filters) {
|
@@ -110,10 +139,103 @@ function filterResults(data, filters) {
|
|
110
139
|
if (!itemName.includes(filters.name.toLowerCase()))
|
111
140
|
return false;
|
112
141
|
}
|
142
|
+
// Network filtering
|
143
|
+
if (filters.network) {
|
144
|
+
const networkName = item.interfaces?.[0]?.network?.name?.toLowerCase() || '';
|
145
|
+
const networkPool = item.interfaces?.[0]?.network?.pool?.name?.toLowerCase() || '';
|
146
|
+
const filterNetwork = filters.network.toLowerCase();
|
147
|
+
if (!networkName.includes(filterNetwork) && !networkPool.includes(filterNetwork)) {
|
148
|
+
return false;
|
149
|
+
}
|
150
|
+
}
|
113
151
|
return true;
|
114
152
|
});
|
115
153
|
}
|
116
|
-
function
|
154
|
+
function mapItemByDetailLevel(item, resource, detailLevel) {
|
155
|
+
// Level 1: Basic information only
|
156
|
+
const basicInfo = {
|
157
|
+
id: item.id,
|
158
|
+
name: item.name,
|
159
|
+
status: item.status
|
160
|
+
};
|
161
|
+
if (detailLevel === 1) {
|
162
|
+
return {
|
163
|
+
...basicInfo,
|
164
|
+
...(resource === 'instances' && {
|
165
|
+
ipAddress: item.connectionInfo?.[0]?.ip || item.externalIp || item.internalIp
|
166
|
+
})
|
167
|
+
};
|
168
|
+
}
|
169
|
+
// Level 2: Standard information (default)
|
170
|
+
const standardInfo = {
|
171
|
+
...basicInfo,
|
172
|
+
osType: item.osType,
|
173
|
+
group: item.group?.name || item.site?.name || 'unknown',
|
174
|
+
...(resource === 'instances' && {
|
175
|
+
plan: item.plan?.name,
|
176
|
+
powerState: item.powerState,
|
177
|
+
ipAddress: item.connectionInfo?.[0]?.ip || item.externalIp || item.internalIp,
|
178
|
+
networkName: item.interfaces?.[0]?.network?.name,
|
179
|
+
networkPool: item.interfaces?.[0]?.network?.pool?.name
|
180
|
+
})
|
181
|
+
};
|
182
|
+
if (detailLevel === 2) {
|
183
|
+
return standardInfo;
|
184
|
+
}
|
185
|
+
// Level 3: Full information
|
186
|
+
return {
|
187
|
+
...standardInfo,
|
188
|
+
...(resource === 'instances' && {
|
189
|
+
networkId: item.interfaces?.[0]?.network?.id,
|
190
|
+
interfaceCount: item.interfaces?.length || 0,
|
191
|
+
allInterfaces: item.interfaces?.map((iface) => ({
|
192
|
+
id: iface.id,
|
193
|
+
networkName: iface.network?.name,
|
194
|
+
ipAddress: iface.ipAddress,
|
195
|
+
ipMode: iface.ipMode
|
196
|
+
})) || [],
|
197
|
+
firewallEnabled: item.firewallEnabled,
|
198
|
+
networkLevel: item.networkLevel,
|
199
|
+
externalId: item.externalId,
|
200
|
+
hostname: item.hostname,
|
201
|
+
displayName: item.displayName,
|
202
|
+
maxMemory: item.maxMemory,
|
203
|
+
maxCores: item.maxCores,
|
204
|
+
dateCreated: item.dateCreated,
|
205
|
+
lastUpdated: item.lastUpdated
|
206
|
+
}),
|
207
|
+
...(resource === 'hosts' && {
|
208
|
+
serverType: item.serverType?.name,
|
209
|
+
zone: item.zone?.name,
|
210
|
+
powerState: item.powerState,
|
211
|
+
agentInstalled: item.agentInstalled,
|
212
|
+
maxMemory: item.maxMemory,
|
213
|
+
maxCores: item.maxCores,
|
214
|
+
freeMemory: item.freeMemory,
|
215
|
+
usedMemory: item.usedMemory
|
216
|
+
}),
|
217
|
+
...(resource === 'networks' && {
|
218
|
+
type: item.type?.name,
|
219
|
+
zone: item.zone?.name,
|
220
|
+
pool: item.pool?.name,
|
221
|
+
dhcpServer: item.dhcpServer,
|
222
|
+
dnsPrimary: item.dnsPrimary,
|
223
|
+
dnsSecondary: item.dnsSecondary,
|
224
|
+
gateway: item.gateway,
|
225
|
+
netmask: item.netmask,
|
226
|
+
cidr: item.cidr
|
227
|
+
}),
|
228
|
+
...(resource === 'cluster_nodes' && {
|
229
|
+
hostname: item.hostname,
|
230
|
+
serverType: item.serverType?.name || item.serverType,
|
231
|
+
powerState: item.powerState,
|
232
|
+
cpuCount: item.maxCpu,
|
233
|
+
memoryGB: item.maxMemory ? Math.floor(item.maxMemory / (1024 * 1024 * 1024)) : undefined,
|
234
|
+
vmCount: item.instances?.length || 0
|
235
|
+
})
|
236
|
+
};
|
237
|
+
}
|
238
|
+
function formatResponse(parsedQuery, results, totalCount, detailLevel = 2) {
|
117
239
|
const { action, resource, filters } = parsedQuery;
|
118
240
|
if (action === 'count') {
|
119
241
|
return {
|
@@ -153,24 +275,14 @@ function formatResponse(parsedQuery, results, totalCount) {
|
|
153
275
|
filters_applied: filters,
|
154
276
|
total_count: results.length,
|
155
277
|
returned_count: limitedResults.length,
|
156
|
-
|
157
|
-
|
158
|
-
name: item.name,
|
159
|
-
status: item.status,
|
160
|
-
osType: item.osType,
|
161
|
-
group: item.group?.name || item.site?.name || 'unknown',
|
162
|
-
...(resource === 'instances' && {
|
163
|
-
plan: item.plan?.name,
|
164
|
-
powerState: item.powerState,
|
165
|
-
ipAddress: item.externalIp || item.internalIp
|
166
|
-
})
|
167
|
-
})),
|
278
|
+
detail_level: detailLevel,
|
279
|
+
items: limitedResults.map(item => mapItemByDetailLevel(item, resource, detailLevel)),
|
168
280
|
query_summary: `Showing ${limitedResults.length} of ${results.length} ${resource} matching criteria`
|
169
281
|
};
|
170
282
|
}
|
171
283
|
async function handleQueryResources(args) {
|
172
284
|
try {
|
173
|
-
const { natural_query, limit = 10 } = args;
|
285
|
+
const { natural_query, limit = 10, detail_level = 2 } = args;
|
174
286
|
if (!natural_query || typeof natural_query !== 'string') {
|
175
287
|
return {
|
176
288
|
content: [
|
@@ -191,7 +303,8 @@ async function handleQueryResources(args) {
|
|
191
303
|
const endpointMap = {
|
192
304
|
'instances': '/instances',
|
193
305
|
'hosts': '/servers',
|
194
|
-
'networks': '/networks'
|
306
|
+
'networks': '/networks',
|
307
|
+
'cluster_nodes': '/clusters'
|
195
308
|
};
|
196
309
|
const endpoint = endpointMap[parsedQuery.resource];
|
197
310
|
// Query the VME API
|
@@ -207,6 +320,16 @@ async function handleQueryResources(args) {
|
|
207
320
|
else if (parsedQuery.resource === 'networks' && response.data.networks) {
|
208
321
|
rawData = response.data.networks;
|
209
322
|
}
|
323
|
+
else if (parsedQuery.resource === 'cluster_nodes' && response.data.clusters) {
|
324
|
+
// Extract hypervisor nodes from clusters
|
325
|
+
const allNodes = [];
|
326
|
+
for (const cluster of response.data.clusters) {
|
327
|
+
if (cluster.servers && Array.isArray(cluster.servers)) {
|
328
|
+
allNodes.push(...cluster.servers);
|
329
|
+
}
|
330
|
+
}
|
331
|
+
rawData = allNodes;
|
332
|
+
}
|
210
333
|
else {
|
211
334
|
// Fallback: try to find array in response
|
212
335
|
const possibleArrays = Object.values(response.data).filter(Array.isArray);
|
@@ -217,7 +340,7 @@ async function handleQueryResources(args) {
|
|
217
340
|
// Apply filters
|
218
341
|
const filteredResults = filterResults(rawData, parsedQuery.filters);
|
219
342
|
// Format response based on action
|
220
|
-
const formattedResponse = formatResponse(parsedQuery, filteredResults, rawData.length);
|
343
|
+
const formattedResponse = formatResponse(parsedQuery, filteredResults, rawData.length, detail_level);
|
221
344
|
return {
|
222
345
|
content: [
|
223
346
|
{
|