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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # VME MCP Server
2
2
 
3
- Transform VMware infrastructure management into conversational AI interactions with Claude.
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
- # VMware Configuration
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 VMware endpoints
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` | VMware vCenter API endpoint | `https://vcenter.company.com/api` |
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 VMware infrastructure
153
+ - Ensure network connectivity to HPE VM Essentials infrastructure
121
154
 
122
155
  **"VM creation failed"**
123
- - Verify user permissions in VMware for VM creation
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 VMware infrastructure is accessible and properly configured
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.5
179
+ Current version: 0.1.9
147
180
 
148
181
  For development documentation and contribution guidelines, see the project repository.
@@ -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
- const prodCluster = clusters.data.clusters.find((cluster) => cluster.name === "prod01");
21
- if (prodCluster && prodCluster.servers) {
22
- return prodCluster.servers.map((server) => server.id);
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;
@@ -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 "web01->web03" or "web01 -> web03"
7
- const rangeMatch = nameInput.match(/^(.+?)(\d+)\s*->\s*(.+?)(\d+)$/);
8
- if (rangeMatch) {
9
- const [, prefix1, start, prefix2, end] = rangeMatch;
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 && distribution !== 'auto') {
61
- // Parse specific node assignments like "1,2,3" or "node1,node2,node3"
62
- const specifiedNodes = distribution.split(',').map((n) => {
63
- const trimmed = n.trim();
64
- return trimmed.startsWith('node') ? parseInt(trimmed.replace('node', '')) : parseInt(trimmed);
65
- }).filter((n) => !isNaN(n));
66
- if (specifiedNodes.length > 0) {
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
- dotenv_1.default.config();
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.5.0",
46
+ version: "0.1.9",
21
47
  }, {
22
48
  capabilities: {
23
49
  tools: {},
@@ -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 with intelligent filtering and caching",
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, hosts, networks) with natural language. Use discover_capabilities first to learn available OS types, groups, and statuses in your environment.",
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', 'any VMs?', 'Ubuntu hosts', 'VMs in development group'"
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('host') || q.includes('server') || q.includes('node')) {
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 formatResponse(parsedQuery, results, totalCount) {
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
- items: limitedResults.map(item => ({
157
- id: item.id,
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
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vme-mcp-server",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "",
5
5
  "main": "dist/server.js",
6
6
  "bin": {