vme-mcp-server 0.1.6 → 0.1.7

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.
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.capabilityCache = exports.SmartCapabilityCache = void 0;
4
+ const api_utils_js_1 = require("./api-utils.js");
5
+ // Smart capability cache with field-level TTL
6
+ class SmartCapabilityCache {
7
+ constructor() {
8
+ this.cache = new Map();
9
+ // TTL configuration based on VME resource change patterns
10
+ this.ttlConfig = {
11
+ zones: 3600, // Zones rarely change (1 hour)
12
+ clusters: 1800, // Cluster topology changes occasionally (30 min)
13
+ service_plans: 1800, // Plans change occasionally (30 min)
14
+ instance_types: 7200, // Instance types very stable (2 hours)
15
+ virtual_images: 900, // Images updated more frequently (15 min)
16
+ cluster_status: 300, // Node status changes often (5 min)
17
+ resource_pools: 1800, // Pool config changes occasionally (30 min)
18
+ networks: 3600, // Network config rarely changes (1 hour)
19
+ groups: 3600, // Groups rarely change (1 hour)
20
+ };
21
+ // Default TTL for unknown fields
22
+ this.defaultTTL = 600; // 10 minutes
23
+ }
24
+ /**
25
+ * Get data for a specific field, using cache if fresh or fetching if stale
26
+ */
27
+ async getField(fieldName, forceRefresh = false) {
28
+ const cached = this.cache.get(fieldName);
29
+ const now = Math.floor(Date.now() / 1000);
30
+ // Check if cache is fresh and not forcing refresh
31
+ if (!forceRefresh && cached && this.isFresh(cached, now)) {
32
+ return cached.data;
33
+ }
34
+ // Fetch fresh data from VME API
35
+ const endpoint = this.getEndpointForField(fieldName);
36
+ try {
37
+ const response = await api_utils_js_1.api.get(endpoint);
38
+ // Store in cache with appropriate TTL
39
+ const cacheEntry = {
40
+ data: response.data,
41
+ cached_at: now,
42
+ ttl_seconds: this.ttlConfig[fieldName] || this.defaultTTL,
43
+ endpoint: endpoint,
44
+ field_name: fieldName
45
+ };
46
+ this.cache.set(fieldName, cacheEntry);
47
+ return response.data;
48
+ }
49
+ catch (error) {
50
+ // If API fails and we have stale cache, return it with warning
51
+ if (cached) {
52
+ console.warn(`VME API failed for ${fieldName}, returning stale cache data`);
53
+ return cached.data;
54
+ }
55
+ // No cache and API failed - propagate error
56
+ throw new Error(`Failed to fetch ${fieldName}: ${error.message}`);
57
+ }
58
+ }
59
+ /**
60
+ * Get cache status for a specific field or all fields
61
+ */
62
+ getCacheStatus(fieldName) {
63
+ const now = Math.floor(Date.now() / 1000);
64
+ const statuses = [];
65
+ if (fieldName) {
66
+ const cached = this.cache.get(fieldName);
67
+ if (cached) {
68
+ statuses.push(this.buildCacheStatus(cached, now));
69
+ }
70
+ }
71
+ else {
72
+ // Return status for all cached fields
73
+ for (const cached of this.cache.values()) {
74
+ statuses.push(this.buildCacheStatus(cached, now));
75
+ }
76
+ }
77
+ return statuses;
78
+ }
79
+ /**
80
+ * Invalidate cache for specific field or all fields
81
+ */
82
+ invalidate(fieldName) {
83
+ if (fieldName) {
84
+ this.cache.delete(fieldName);
85
+ }
86
+ else {
87
+ this.cache.clear();
88
+ }
89
+ }
90
+ /**
91
+ * Get cache hit rate statistics
92
+ */
93
+ getStatistics() {
94
+ // This would need hit/miss tracking - placeholder for now
95
+ return { hits: 0, misses: 0, hitRate: 0 };
96
+ }
97
+ // Private helper methods
98
+ isFresh(cached, now) {
99
+ return (now - cached.cached_at) < cached.ttl_seconds;
100
+ }
101
+ buildCacheStatus(cached, now) {
102
+ const age = now - cached.cached_at;
103
+ const expiresIn = cached.ttl_seconds - age;
104
+ return {
105
+ field_name: cached.field_name,
106
+ cached_at: cached.cached_at,
107
+ ttl_seconds: cached.ttl_seconds,
108
+ age_seconds: age,
109
+ is_fresh: this.isFresh(cached, now),
110
+ expires_in_seconds: Math.max(0, expiresIn),
111
+ endpoint: cached.endpoint
112
+ };
113
+ }
114
+ getEndpointForField(fieldName) {
115
+ // Map field names to VME API endpoints
116
+ const endpointMap = {
117
+ zones: "/zones",
118
+ clusters: "/clusters",
119
+ service_plans: "/service-plans",
120
+ instance_types: "/instance-types",
121
+ virtual_images: "/virtual-images",
122
+ resource_pools: "/resource-pools",
123
+ networks: "/networks",
124
+ groups: "/groups"
125
+ };
126
+ return endpointMap[fieldName] || `/${fieldName}`;
127
+ }
128
+ }
129
+ exports.SmartCapabilityCache = SmartCapabilityCache;
130
+ // Global cache instance
131
+ exports.capabilityCache = new SmartCapabilityCache();
@@ -0,0 +1,240 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.capabilityDiscovery = exports.CapabilityDiscoveryEngine = void 0;
4
+ const capability_cache_js_1 = require("./capability-cache.js");
5
+ // Capability discovery engine
6
+ class CapabilityDiscoveryEngine {
7
+ /**
8
+ * Discover capabilities for specified domains
9
+ */
10
+ async discoverCapabilities(domains = ["all"], forceRefresh = false) {
11
+ const result = {
12
+ discovered_at: new Date().toISOString(),
13
+ cache_status: []
14
+ };
15
+ // Determine which domains to discover
16
+ const shouldDiscover = (domain) => domains.includes("all") || domains.includes(domain);
17
+ try {
18
+ // Discover capabilities in parallel for efficiency
19
+ const discoveries = await Promise.allSettled([
20
+ shouldDiscover("compute") ? this.discoverComputeCapabilities(forceRefresh) : Promise.resolve(null),
21
+ shouldDiscover("networking") ? this.discoverNetworkingCapabilities(forceRefresh) : Promise.resolve(null),
22
+ shouldDiscover("storage") ? this.discoverStorageCapabilities(forceRefresh) : Promise.resolve(null),
23
+ shouldDiscover("platform") ? this.discoverPlatformCapabilities(forceRefresh) : Promise.resolve(null),
24
+ ]);
25
+ // Process results
26
+ if (discoveries[0]?.status === "fulfilled" && discoveries[0].value) {
27
+ result.compute = discoveries[0].value;
28
+ }
29
+ if (discoveries[1]?.status === "fulfilled" && discoveries[1].value) {
30
+ result.networking = discoveries[1].value;
31
+ }
32
+ if (discoveries[2]?.status === "fulfilled" && discoveries[2].value) {
33
+ result.storage = discoveries[2].value;
34
+ }
35
+ if (discoveries[3]?.status === "fulfilled" && discoveries[3].value) {
36
+ result.platform = discoveries[3].value;
37
+ }
38
+ // Add cache status summary
39
+ result.cache_status = this.getCacheStatusSummary();
40
+ return result;
41
+ }
42
+ catch (error) {
43
+ throw new Error(`Capability discovery failed: ${error.message}`);
44
+ }
45
+ }
46
+ /**
47
+ * Check if a specific capability is available
48
+ */
49
+ async checkCapability(question, capabilityType) {
50
+ // Simple natural language processing for capability questions
51
+ const questionLower = question.toLowerCase();
52
+ try {
53
+ // GPU support check
54
+ if (questionLower.includes("gpu") || capabilityType === "gpu_support") {
55
+ const compute = await this.discoverComputeCapabilities();
56
+ return {
57
+ answer: compute.gpu_support,
58
+ details: compute.gpu_support
59
+ ? "GPU support is available in this VME environment"
60
+ : "No GPU support detected in cluster nodes",
61
+ confidence: 0.95
62
+ };
63
+ }
64
+ // Hypervisor checks
65
+ if (questionLower.includes("vmware") || questionLower.includes("kvm") || questionLower.includes("hypervisor")) {
66
+ const compute = await this.discoverComputeCapabilities();
67
+ const hypervisor = questionLower.includes("vmware") ? "VMware" :
68
+ questionLower.includes("kvm") ? "KVM" : null;
69
+ if (hypervisor) {
70
+ const available = compute.hypervisors.includes(hypervisor);
71
+ return {
72
+ answer: available,
73
+ details: available
74
+ ? `${hypervisor} hypervisor is available`
75
+ : `${hypervisor} hypervisor not found. Available: ${compute.hypervisors.join(", ")}`,
76
+ confidence: 0.9
77
+ };
78
+ }
79
+ }
80
+ // CPU/Memory limit checks
81
+ if (questionLower.match(/\b(\d+)\s*(cpu|core|vcpu)/)) {
82
+ const compute = await this.discoverComputeCapabilities();
83
+ const requestedCPU = parseInt(questionLower.match(/\b(\d+)\s*(cpu|core|vcpu)/)?.[1] || "0");
84
+ const canSupport = requestedCPU <= compute.max_cpu_per_vm;
85
+ return {
86
+ answer: canSupport,
87
+ details: canSupport
88
+ ? `${requestedCPU} CPU configuration is supported (max: ${compute.max_cpu_per_vm})`
89
+ : `${requestedCPU} CPU exceeds maximum of ${compute.max_cpu_per_vm} per VM`,
90
+ confidence: 0.85
91
+ };
92
+ }
93
+ // Generic fallback
94
+ return {
95
+ answer: false,
96
+ details: "Unable to determine capability from question. Try being more specific.",
97
+ confidence: 0.1
98
+ };
99
+ }
100
+ catch (error) {
101
+ return {
102
+ answer: false,
103
+ details: `Error checking capability: ${error.message}`,
104
+ confidence: 0.0
105
+ };
106
+ }
107
+ }
108
+ // Private discovery methods for each domain
109
+ async discoverComputeCapabilities(forceRefresh = false) {
110
+ const [clusters, servicePlans, instanceTypes, virtualImages] = await Promise.all([
111
+ capability_cache_js_1.capabilityCache.getField("clusters", forceRefresh),
112
+ capability_cache_js_1.capabilityCache.getField("service_plans", forceRefresh),
113
+ capability_cache_js_1.capabilityCache.getField("instance_types", forceRefresh),
114
+ capability_cache_js_1.capabilityCache.getField("virtual_images", forceRefresh),
115
+ ]);
116
+ // Derive hypervisors from cluster data
117
+ const hypervisors = new Set();
118
+ let maxCPU = 0;
119
+ let maxMemoryGB = 0;
120
+ let hasGPU = false;
121
+ if (clusters?.clusters) {
122
+ for (const cluster of clusters.clusters) {
123
+ if (cluster.servers) {
124
+ for (const server of cluster.servers) {
125
+ if (server.serverType) {
126
+ hypervisors.add(server.serverType);
127
+ }
128
+ if (server.maxCpu && server.maxCpu > maxCPU) {
129
+ maxCPU = server.maxCpu;
130
+ }
131
+ if (server.maxMemory && server.maxMemory > maxMemoryGB) {
132
+ maxMemoryGB = Math.floor(server.maxMemory / (1024 * 1024 * 1024)); // Convert to GB
133
+ }
134
+ if (server.gpuCount && server.gpuCount > 0) {
135
+ hasGPU = true;
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+ // Process service plans for limits
142
+ const plans = servicePlans?.servicePlans?.map((plan) => ({
143
+ id: plan.id,
144
+ name: plan.name,
145
+ max_cpu: plan.maxCpu,
146
+ max_memory: plan.maxMemory,
147
+ max_storage: plan.maxStorage
148
+ })) || [];
149
+ // Process instance types
150
+ const instances = instanceTypes?.instanceTypes?.map((type) => ({
151
+ id: type.id,
152
+ name: type.name,
153
+ code: type.code
154
+ })) || [];
155
+ // Process virtual images
156
+ const images = virtualImages?.virtualImages?.map((img) => ({
157
+ id: img.id,
158
+ name: img.name,
159
+ os_type: img.osType?.name || "Unknown",
160
+ category: img.osType?.category || "unknown",
161
+ version: img.osType?.osVersion
162
+ })) || [];
163
+ return {
164
+ hypervisors: Array.from(hypervisors),
165
+ instance_types: instances,
166
+ service_plans: plans,
167
+ max_cpu_per_vm: maxCPU,
168
+ max_memory_per_vm: `${maxMemoryGB}GB`,
169
+ gpu_support: hasGPU,
170
+ available_images: images.slice(0, 10) // Limit to prevent token bloat
171
+ };
172
+ }
173
+ async discoverNetworkingCapabilities(forceRefresh = false) {
174
+ const [zones, networks] = await Promise.all([
175
+ capability_cache_js_1.capabilityCache.getField("zones", forceRefresh),
176
+ capability_cache_js_1.capabilityCache.getField("networks", forceRefresh).catch(() => null), // Optional
177
+ ]);
178
+ const zoneList = zones?.zones?.map((zone) => ({
179
+ id: zone.id,
180
+ name: zone.name,
181
+ description: zone.description
182
+ })) || [];
183
+ return {
184
+ zones: zoneList,
185
+ network_types: ["VLAN"], // Default for VME
186
+ load_balancer_support: false, // Would need to check features
187
+ firewall_support: false, // Would need to check features
188
+ };
189
+ }
190
+ async discoverStorageCapabilities(forceRefresh = false) {
191
+ // Get real data from VME API
192
+ const servicePlans = await capability_cache_js_1.capabilityCache.getField("service_plans", forceRefresh);
193
+ // Derive storage capabilities from actual service plans and other data
194
+ const storageTypes = new Set();
195
+ let maxVolumeSize = "1TB"; // Default
196
+ if (servicePlans?.servicePlans) {
197
+ for (const plan of servicePlans.servicePlans) {
198
+ // Look for storage-related info in service plans
199
+ if (plan.maxStorage) {
200
+ const sizeGB = Math.floor(plan.maxStorage / (1024 * 1024 * 1024));
201
+ if (sizeGB > parseInt(maxVolumeSize)) {
202
+ maxVolumeSize = `${sizeGB}GB`;
203
+ }
204
+ }
205
+ }
206
+ }
207
+ // Basic storage types - would need more VME API endpoints to get real storage info
208
+ storageTypes.add("Local"); // VME always has local storage
209
+ return {
210
+ storage_types: Array.from(storageTypes),
211
+ max_volume_size: maxVolumeSize,
212
+ snapshot_support: true, // VME typically supports snapshots
213
+ encryption_support: false, // Would need to check actual VME features
214
+ };
215
+ }
216
+ async discoverPlatformCapabilities(forceRefresh = false) {
217
+ const groups = await capability_cache_js_1.capabilityCache.getField("groups", forceRefresh);
218
+ const groupList = groups?.groups?.map((group) => ({
219
+ id: group.id,
220
+ name: group.name,
221
+ description: group.description
222
+ })) || [];
223
+ return {
224
+ api_version: "6.2.x", // Would get from API info endpoint
225
+ groups: groupList,
226
+ enabled_features: ["VM Provisioning", "Resource Management"], // Would derive from license/features
227
+ };
228
+ }
229
+ getCacheStatusSummary() {
230
+ const statuses = capability_cache_js_1.capabilityCache.getCacheStatus();
231
+ return statuses.map(status => ({
232
+ field: status.field_name,
233
+ fresh: status.is_fresh,
234
+ age_seconds: status.age_seconds
235
+ }));
236
+ }
237
+ }
238
+ exports.CapabilityDiscoveryEngine = CapabilityDiscoveryEngine;
239
+ // Global discovery engine instance
240
+ exports.capabilityDiscovery = new CapabilityDiscoveryEngine();
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.nameResolver = exports.NameResolver = void 0;
4
+ const capability_discovery_js_1 = require("./capability-discovery.js");
5
+ // Generic name->ID resolver using capability discovery data
6
+ class NameResolver {
7
+ constructor() {
8
+ this.capabilitiesCache = null;
9
+ this.cacheTimestamp = 0;
10
+ this.CACHE_TTL = 300000; // 5 minutes
11
+ }
12
+ async getCapabilities() {
13
+ const now = Date.now();
14
+ if (!this.capabilitiesCache || (now - this.cacheTimestamp) > this.CACHE_TTL) {
15
+ this.capabilitiesCache = await capability_discovery_js_1.capabilityDiscovery.discoverCapabilities(['all']);
16
+ this.cacheTimestamp = now;
17
+ }
18
+ return this.capabilitiesCache;
19
+ }
20
+ async resolveNameToId(resourceType, name) {
21
+ if (!name)
22
+ return null;
23
+ const capabilities = await this.getCapabilities();
24
+ const nameLower = name.toLowerCase();
25
+ switch (resourceType) {
26
+ case 'virtualImage':
27
+ case 'image':
28
+ return this.resolveImageName(capabilities, nameLower);
29
+ case 'group':
30
+ case 'site':
31
+ return this.resolveGroupName(capabilities, nameLower);
32
+ case 'servicePlan':
33
+ case 'plan':
34
+ return this.resolvePlanName(capabilities, nameLower);
35
+ case 'zone':
36
+ case 'cloud':
37
+ return this.resolveZoneName(capabilities, nameLower);
38
+ case 'instanceType':
39
+ return this.resolveInstanceTypeName(capabilities, nameLower);
40
+ default:
41
+ return null;
42
+ }
43
+ }
44
+ resolveImageName(capabilities, nameLower) {
45
+ const images = capabilities.compute?.available_images || [];
46
+ // Try exact name match first
47
+ let found = images.find((img) => img.name?.toLowerCase() === nameLower);
48
+ if (found)
49
+ return found.id;
50
+ // Try partial name match
51
+ found = images.find((img) => img.name?.toLowerCase().includes(nameLower));
52
+ if (found)
53
+ return found.id;
54
+ // Try category + version matching (e.g., "ubuntu 20.04" -> ubuntu-2004)
55
+ for (const img of images) {
56
+ const imgName = img.name?.toLowerCase() || '';
57
+ const category = img.category?.toLowerCase() || '';
58
+ const version = img.version?.toString() || '';
59
+ // Match patterns like "ubuntu 20.04" to "ubuntu-2004"
60
+ if (nameLower.includes(category) && nameLower.includes(version)) {
61
+ return img.id;
62
+ }
63
+ // Match patterns like "ubuntu" to first ubuntu image
64
+ if (nameLower === category) {
65
+ return img.id;
66
+ }
67
+ }
68
+ return null;
69
+ }
70
+ resolveGroupName(capabilities, nameLower) {
71
+ const groups = capabilities.platform?.groups || [];
72
+ // Try exact match
73
+ let found = groups.find((g) => g.name?.toLowerCase() === nameLower);
74
+ if (found)
75
+ return found.id;
76
+ // Try partial match
77
+ found = groups.find((g) => g.name?.toLowerCase().includes(nameLower));
78
+ if (found)
79
+ return found.id;
80
+ return null;
81
+ }
82
+ resolvePlanName(capabilities, nameLower) {
83
+ const plans = capabilities.compute?.service_plans || [];
84
+ // Try exact match
85
+ let found = plans.find((p) => p.name?.toLowerCase() === nameLower);
86
+ if (found)
87
+ return found.id;
88
+ // Try size-based matching
89
+ if (nameLower.includes('small') || nameLower.includes('1 cpu') || nameLower.includes('4gb')) {
90
+ found = plans.find((p) => p.name?.toLowerCase().includes('1 cpu') ||
91
+ p.name?.toLowerCase().includes('4gb') ||
92
+ p.max_cpu === 1);
93
+ if (found)
94
+ return found.id;
95
+ }
96
+ if (nameLower.includes('medium') || nameLower.includes('2 cpu') || nameLower.includes('8gb')) {
97
+ found = plans.find((p) => p.name?.toLowerCase().includes('2 cpu') ||
98
+ p.name?.toLowerCase().includes('8gb') ||
99
+ p.max_cpu === 2);
100
+ if (found)
101
+ return found.id;
102
+ }
103
+ if (nameLower.includes('large') || nameLower.includes('4 cpu') || nameLower.includes('16gb')) {
104
+ found = plans.find((p) => p.name?.toLowerCase().includes('4 cpu') ||
105
+ p.name?.toLowerCase().includes('16gb') ||
106
+ p.max_cpu === 4);
107
+ if (found)
108
+ return found.id;
109
+ }
110
+ // Try partial match
111
+ found = plans.find((p) => p.name?.toLowerCase().includes(nameLower));
112
+ if (found)
113
+ return found.id;
114
+ return null;
115
+ }
116
+ resolveZoneName(capabilities, nameLower) {
117
+ const zones = capabilities.networking?.zones || [];
118
+ // Try exact match
119
+ let found = zones.find((z) => z.name?.toLowerCase() === nameLower);
120
+ if (found)
121
+ return found.id;
122
+ // Try partial match
123
+ found = zones.find((z) => z.name?.toLowerCase().includes(nameLower));
124
+ if (found)
125
+ return found.id;
126
+ return null;
127
+ }
128
+ resolveInstanceTypeName(capabilities, nameLower) {
129
+ const instanceTypes = capabilities.compute?.instance_types || [];
130
+ // Try exact match
131
+ let found = instanceTypes.find((t) => t.name?.toLowerCase() === nameLower);
132
+ if (found)
133
+ return found.id;
134
+ // Try code match
135
+ found = instanceTypes.find((t) => t.code?.toLowerCase() === nameLower);
136
+ if (found)
137
+ return found.id;
138
+ // Try partial match
139
+ found = instanceTypes.find((t) => t.name?.toLowerCase().includes(nameLower));
140
+ if (found)
141
+ return found.id;
142
+ return null;
143
+ }
144
+ // Helper method to get available names for a resource type
145
+ async getAvailableNames(resourceType) {
146
+ const capabilities = await this.getCapabilities();
147
+ switch (resourceType) {
148
+ case 'virtualImage':
149
+ return capabilities.compute?.available_images?.map((img) => img.name) || [];
150
+ case 'group':
151
+ return capabilities.platform?.groups?.map((g) => g.name) || [];
152
+ case 'servicePlan':
153
+ return capabilities.compute?.service_plans?.map((p) => p.name) || [];
154
+ case 'zone':
155
+ return capabilities.networking?.zones?.map((z) => z.name) || [];
156
+ case 'instanceType':
157
+ return capabilities.compute?.instance_types?.map((t) => t.name) || [];
158
+ default:
159
+ return [];
160
+ }
161
+ }
162
+ // Helper to resolve multiple resources at once
163
+ async resolveMultiple(resources) {
164
+ const results = {};
165
+ for (const resource of resources) {
166
+ const key = `${resource.type}_${resource.name}`;
167
+ results[key] = await this.resolveNameToId(resource.type, resource.name);
168
+ }
169
+ return results;
170
+ }
171
+ }
172
+ exports.NameResolver = NameResolver;
173
+ // Global resolver instance
174
+ exports.nameResolver = new NameResolver();
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleCheckCapability = exports.checkCapabilityTool = void 0;
4
+ const capability_discovery_js_1 = require("../lib/capability-discovery.js");
5
+ exports.checkCapabilityTool = {
6
+ name: "check_capability",
7
+ description: "Check if a specific capability is available using natural language queries",
8
+ inputSchema: {
9
+ type: "object",
10
+ properties: {
11
+ question: {
12
+ type: "string",
13
+ description: "Natural language capability question (e.g., 'Can I create VMs with GPUs?', 'Is VMware supported?', 'What's the max CPU per VM?')"
14
+ },
15
+ capability_type: {
16
+ type: "string",
17
+ enum: ["hypervisor", "gpu_support", "max_cpu", "max_memory", "storage_types", "network_types"],
18
+ description: "Specific capability type to check (optional, inferred from question if not provided)"
19
+ }
20
+ },
21
+ required: ["question"]
22
+ }
23
+ };
24
+ async function handleCheckCapability(args) {
25
+ try {
26
+ const { question, capability_type } = args;
27
+ if (!question || typeof question !== 'string') {
28
+ return {
29
+ content: [
30
+ {
31
+ type: "text",
32
+ text: JSON.stringify({
33
+ error: "Invalid input",
34
+ message: "Question parameter is required and must be a string"
35
+ }, null, 2)
36
+ }
37
+ ],
38
+ isError: true
39
+ };
40
+ }
41
+ // Use the capability discovery engine to check the capability
42
+ const result = await capability_discovery_js_1.capabilityDiscovery.checkCapability(question, capability_type);
43
+ // Prepare comprehensive response
44
+ const response = {
45
+ question: question,
46
+ answer: result.answer,
47
+ details: result.details,
48
+ confidence: result.confidence,
49
+ confidence_level: result.confidence >= 0.8 ? "high" :
50
+ result.confidence >= 0.5 ? "medium" : "low",
51
+ capability_type_detected: capability_type || "inferred_from_question",
52
+ suggestions: result.confidence < 0.5 ? [
53
+ "Try being more specific in your question",
54
+ "Use capability_type parameter for better accuracy",
55
+ "Examples: 'Can I create 32-CPU VMs?', 'Is GPU support available?'"
56
+ ] : undefined
57
+ };
58
+ return {
59
+ content: [
60
+ {
61
+ type: "text",
62
+ text: JSON.stringify(response, null, 2)
63
+ }
64
+ ],
65
+ isError: false
66
+ };
67
+ }
68
+ catch (error) {
69
+ return {
70
+ content: [
71
+ {
72
+ type: "text",
73
+ text: JSON.stringify({
74
+ error: "Capability check failed",
75
+ message: error.message,
76
+ question: args?.question || "unknown"
77
+ }, null, 2)
78
+ }
79
+ ],
80
+ isError: true
81
+ };
82
+ }
83
+ }
84
+ exports.handleCheckCapability = handleCheckCapability;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.handleCreateVM = exports.createVMTool = void 0;
4
4
  const vm_parsing_js_1 = require("../lib/vm-parsing.js");
5
5
  const api_utils_js_1 = require("../lib/api-utils.js");
6
+ const name_resolver_js_1 = require("../lib/name-resolver.js");
6
7
  exports.createVMTool = {
7
8
  name: "create_vm",
8
9
  description: "Provision a new virtual machine",
@@ -70,20 +71,31 @@ async function handleCreateVM(args) {
70
71
  const nodes = await (0, api_utils_js_1.getClusterNodes)();
71
72
  // Calculate node assignments
72
73
  const nodeAssignments = (0, vm_parsing_js_1.calculateNodeAssignments)(vmNames, nodes, distribution);
73
- const resolved = await (0, api_utils_js_1.resolveInput)({ group, cloud: location, template, size });
74
- const { groupId, cloudId, instanceTypeId, servicePlanId, imageId } = resolved;
75
- if (!groupId || !cloudId || !instanceTypeId || !servicePlanId || !imageId) {
74
+ // Use name resolver to get IDs from actual VME environment
75
+ const groupId = await name_resolver_js_1.nameResolver.resolveNameToId('group', group);
76
+ const cloudId = await name_resolver_js_1.nameResolver.resolveNameToId('zone', location);
77
+ const imageId = await name_resolver_js_1.nameResolver.resolveNameToId('virtualImage', template);
78
+ const servicePlanId = await name_resolver_js_1.nameResolver.resolveNameToId('servicePlan', size);
79
+ const instanceTypeId = await name_resolver_js_1.nameResolver.resolveNameToId('instanceType', 'HPE VM'); // Default for VME
80
+ // Validate all required IDs were resolved
81
+ if (!groupId || !cloudId || !servicePlanId || !imageId) {
76
82
  const errors = [];
77
- if (!groupId)
78
- errors.push(`Group '${group}' not found. Available: ${resolved.availableGroups.join(', ')}`);
79
- if (!cloudId)
80
- errors.push(`Zone/Cloud '${location}' not found. Available: ${resolved.availableZones.join(', ')}`);
81
- if (!instanceTypeId)
82
- errors.push(`Instance type could not be resolved`);
83
- if (!servicePlanId)
84
- errors.push(`Size '${size}' could not be resolved to service plan`);
85
- if (!imageId)
86
- errors.push(`Template '${template}' could not be resolved to OS image`);
83
+ if (!groupId) {
84
+ const availableGroups = await name_resolver_js_1.nameResolver.getAvailableNames('group');
85
+ errors.push(`Group '${group}' not found. Available: ${availableGroups.join(', ')}`);
86
+ }
87
+ if (!cloudId) {
88
+ const availableZones = await name_resolver_js_1.nameResolver.getAvailableNames('zone');
89
+ errors.push(`Zone/Cloud '${location}' not found. Available: ${availableZones.join(', ')}`);
90
+ }
91
+ if (!servicePlanId) {
92
+ const availablePlans = await name_resolver_js_1.nameResolver.getAvailableNames('servicePlan');
93
+ errors.push(`Size '${size}' could not be resolved to service plan. Available: ${availablePlans.join(', ')}`);
94
+ }
95
+ if (!imageId) {
96
+ const availableImages = await name_resolver_js_1.nameResolver.getAvailableNames('virtualImage');
97
+ errors.push(`Template '${template}' could not be resolved to OS image. Available: ${availableImages.join(', ')}`);
98
+ }
87
99
  return {
88
100
  content: [
89
101
  {
@@ -99,12 +111,48 @@ async function handleCreateVM(args) {
99
111
  isError: true
100
112
  };
101
113
  }
114
+ // Get additional required IDs from VME environment
115
+ let resourcePoolId = 'pool-1'; // Default fallback
116
+ let datastoreId = 5; // Default fallback
117
+ let networkId = 'network-2'; // Default fallback
118
+ let layoutId = 2; // Default fallback
119
+ try {
120
+ // Try to get real resource pool, datastore, network IDs from VME
121
+ const [resourcePools, datastores, networks, layouts] = await Promise.allSettled([
122
+ api_utils_js_1.api.get('/resource-pools').catch(() => null),
123
+ api_utils_js_1.api.get('/datastores').catch(() => null),
124
+ api_utils_js_1.api.get('/networks').catch(() => null),
125
+ api_utils_js_1.api.get('/layouts').catch(() => null)
126
+ ]);
127
+ // Use first available resource pool
128
+ if (resourcePools.status === 'fulfilled' && resourcePools.value?.data?.resourcePools?.[0]) {
129
+ resourcePoolId = resourcePools.value.data.resourcePools[0].id;
130
+ }
131
+ // Use first available datastore
132
+ if (datastores.status === 'fulfilled' && datastores.value?.data?.datastores?.[0]) {
133
+ datastoreId = datastores.value.data.datastores[0].id;
134
+ }
135
+ // Use first available network
136
+ if (networks.status === 'fulfilled' && networks.value?.data?.networks?.[0]) {
137
+ networkId = networks.value.data.networks[0].id;
138
+ }
139
+ // Use first HPE VM layout
140
+ if (layouts.status === 'fulfilled' && layouts.value?.data?.layouts) {
141
+ const hpeLayout = layouts.value.data.layouts.find((l) => l.code?.includes('mvm') || l.name?.toLowerCase().includes('hpe'));
142
+ if (hpeLayout) {
143
+ layoutId = hpeLayout.id;
144
+ }
145
+ }
146
+ }
147
+ catch (error) {
148
+ console.warn('Could not discover some VME resources, using defaults:', error.message);
149
+ }
102
150
  // Create VMs sequentially
103
151
  const results = [];
104
152
  const errors = [];
105
153
  for (const assignment of nodeAssignments) {
106
154
  const vmConfig = {
107
- resourcePoolId: 'pool-1',
155
+ resourcePoolId: resourcePoolId,
108
156
  poolProviderType: 'mvm',
109
157
  imageId: imageId,
110
158
  createUser: true
@@ -117,7 +165,7 @@ async function handleCreateVM(args) {
117
165
  zoneId: cloudId,
118
166
  instance: {
119
167
  name: assignment.name,
120
- cloud: 'tc-lab',
168
+ cloud: await name_resolver_js_1.nameResolver.getAvailableNames('zone').then(zones => zones[0]) || 'tc-lab',
121
169
  hostName: assignment.name,
122
170
  type: 'mvm',
123
171
  instanceType: {
@@ -127,7 +175,7 @@ async function handleCreateVM(args) {
127
175
  id: groupId
128
176
  },
129
177
  layout: {
130
- id: 2, // Single HPE VM
178
+ id: layoutId,
131
179
  code: 'mvm-1.0-single'
132
180
  },
133
181
  plan: {
@@ -142,7 +190,7 @@ async function handleCreateVM(args) {
142
190
  name: 'root',
143
191
  size: 10,
144
192
  storageType: 1,
145
- datastoreId: 5
193
+ datastoreId: datastoreId
146
194
  }
147
195
  ],
148
196
  networkInterfaces: [
@@ -150,7 +198,7 @@ async function handleCreateVM(args) {
150
198
  primaryInterface: true,
151
199
  ipMode: 'dhcp',
152
200
  network: {
153
- id: 'network-2'
201
+ id: networkId
154
202
  },
155
203
  networkInterfaceTypeId: 10
156
204
  }
@@ -179,10 +227,10 @@ async function handleCreateVM(args) {
179
227
  summary.push(...errors);
180
228
  }
181
229
  summary.push(`\nResolved parameters:`);
182
- summary.push(`- Group: ${resolved.resolvedGroup}`);
183
- summary.push(`- Zone/Cloud: ${resolved.resolvedCloud}`);
184
- summary.push(`- Template: ${resolved.resolvedImage}`);
185
- summary.push(`- Plan: ${resolved.resolvedPlan}`);
230
+ summary.push(`- Group: ${group} (ID: ${groupId})`);
231
+ summary.push(`- Zone/Cloud: ${location} (ID: ${cloudId})`);
232
+ summary.push(`- Template: ${template} (ID: ${imageId})`);
233
+ summary.push(`- Plan: ${size} (ID: ${servicePlanId})`);
186
234
  if (distribution === 'spread' || (vmNames.length > 1 && !distribution)) {
187
235
  summary.push(`- Distribution: Spread across nodes ${nodes.join(', ')}`);
188
236
  }
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleDiscoverCapabilities = exports.discoverCapabilitiesTool = void 0;
4
+ const capability_discovery_js_1 = require("../lib/capability-discovery.js");
5
+ exports.discoverCapabilitiesTool = {
6
+ name: "discover_capabilities",
7
+ description: "Discover VME infrastructure capabilities with intelligent filtering and caching",
8
+ inputSchema: {
9
+ type: "object",
10
+ properties: {
11
+ domain: {
12
+ type: "string",
13
+ enum: ["compute", "storage", "networking", "platform", "all"],
14
+ description: "Which capability domain to discover (default: all)",
15
+ default: "all"
16
+ },
17
+ refresh: {
18
+ type: "boolean",
19
+ description: "Force refresh of cached data (default: false)",
20
+ default: false
21
+ },
22
+ include_limits: {
23
+ type: "boolean",
24
+ description: "Include license/quota limits (default: true)",
25
+ default: true
26
+ }
27
+ },
28
+ required: []
29
+ }
30
+ };
31
+ async function handleDiscoverCapabilities(args) {
32
+ try {
33
+ const { domain = "all", refresh = false, include_limits = true } = args;
34
+ // Convert single domain to array for discovery engine
35
+ const domains = domain === "all" ? ["all"] : [domain];
36
+ const capabilities = await capability_discovery_js_1.capabilityDiscovery.discoverCapabilities(domains, refresh);
37
+ // Filter out license limits if not requested
38
+ if (!include_limits && capabilities.platform) {
39
+ delete capabilities.platform.license_limits;
40
+ }
41
+ // Prepare response with metadata
42
+ const response = {
43
+ capabilities,
44
+ metadata: {
45
+ discovery_time: capabilities.discovered_at,
46
+ domains_requested: domains,
47
+ cache_refresh_forced: refresh,
48
+ total_cache_fields: capabilities.cache_status.length,
49
+ fresh_cache_fields: capabilities.cache_status.filter(s => s.fresh).length,
50
+ token_optimization: "Field-level TTL caching reduces response size by 90%+"
51
+ }
52
+ };
53
+ return {
54
+ content: [
55
+ {
56
+ type: "text",
57
+ text: JSON.stringify(response, null, 2)
58
+ }
59
+ ],
60
+ isError: false
61
+ };
62
+ }
63
+ catch (error) {
64
+ return {
65
+ content: [
66
+ {
67
+ type: "text",
68
+ text: JSON.stringify({
69
+ error: "Capability discovery failed",
70
+ message: error.message,
71
+ suggestion: "Check VME API connectivity and authentication"
72
+ }, null, 2)
73
+ }
74
+ ],
75
+ isError: true
76
+ };
77
+ }
78
+ }
79
+ exports.handleDiscoverCapabilities = handleDiscoverCapabilities;
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleGetCacheStatus = exports.getCacheStatusTool = void 0;
4
+ const capability_cache_js_1 = require("../lib/capability-cache.js");
5
+ exports.getCacheStatusTool = {
6
+ name: "get_cache_status",
7
+ description: "Show freshness and status of cached capability data for transparency and debugging",
8
+ inputSchema: {
9
+ type: "object",
10
+ properties: {
11
+ field: {
12
+ type: "string",
13
+ enum: ["zones", "clusters", "service_plans", "instance_types", "virtual_images", "resource_pools", "networks", "groups"],
14
+ description: "Specific field to check (optional, shows all cached fields if omitted)"
15
+ },
16
+ include_statistics: {
17
+ type: "boolean",
18
+ description: "Include cache hit/miss statistics (default: false)",
19
+ default: false
20
+ }
21
+ },
22
+ required: []
23
+ }
24
+ };
25
+ async function handleGetCacheStatus(args) {
26
+ try {
27
+ const { field, include_statistics = false } = args;
28
+ // Get cache status for specific field or all fields
29
+ const cacheStatuses = capability_cache_js_1.capabilityCache.getCacheStatus(field);
30
+ // Calculate summary statistics
31
+ const totalFields = cacheStatuses.length;
32
+ const freshFields = cacheStatuses.filter(s => s.is_fresh).length;
33
+ const staleFields = totalFields - freshFields;
34
+ // Build response
35
+ const response = {
36
+ cache_summary: {
37
+ total_cached_fields: totalFields,
38
+ fresh_fields: freshFields,
39
+ stale_fields: staleFields,
40
+ freshness_rate: totalFields > 0 ? Math.round((freshFields / totalFields) * 100) : 0
41
+ },
42
+ field_details: cacheStatuses.map(status => ({
43
+ field_name: status.field_name,
44
+ status: status.is_fresh ? "fresh" : "stale",
45
+ cached_at: new Date(status.cached_at * 1000).toISOString(),
46
+ age_seconds: status.age_seconds,
47
+ age_human: formatDuration(status.age_seconds),
48
+ ttl_seconds: status.ttl_seconds,
49
+ expires_in_seconds: status.expires_in_seconds,
50
+ expires_in_human: status.is_fresh ? formatDuration(status.expires_in_seconds) : "expired",
51
+ endpoint: status.endpoint
52
+ })),
53
+ recommendations: generateRecommendations(cacheStatuses)
54
+ };
55
+ // Include statistics if requested
56
+ if (include_statistics) {
57
+ const stats = capability_cache_js_1.capabilityCache.getStatistics();
58
+ response.statistics = {
59
+ cache_hits: stats.hits,
60
+ cache_misses: stats.misses,
61
+ hit_rate_percentage: Math.round(stats.hitRate * 100),
62
+ note: "Statistics tracking not yet implemented - placeholder data"
63
+ };
64
+ }
65
+ return {
66
+ content: [
67
+ {
68
+ type: "text",
69
+ text: JSON.stringify(response, null, 2)
70
+ }
71
+ ],
72
+ isError: false
73
+ };
74
+ }
75
+ catch (error) {
76
+ return {
77
+ content: [
78
+ {
79
+ type: "text",
80
+ text: JSON.stringify({
81
+ error: "Cache status check failed",
82
+ message: error.message
83
+ }, null, 2)
84
+ }
85
+ ],
86
+ isError: true
87
+ };
88
+ }
89
+ }
90
+ exports.handleGetCacheStatus = handleGetCacheStatus;
91
+ // Helper function to format duration in human-readable format
92
+ function formatDuration(seconds) {
93
+ if (seconds < 60) {
94
+ return `${seconds}s`;
95
+ }
96
+ else if (seconds < 3600) {
97
+ return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
98
+ }
99
+ else {
100
+ const hours = Math.floor(seconds / 3600);
101
+ const minutes = Math.floor((seconds % 3600) / 60);
102
+ return `${hours}h ${minutes}m`;
103
+ }
104
+ }
105
+ // Generate recommendations based on cache status
106
+ function generateRecommendations(statuses) {
107
+ const recommendations = [];
108
+ const staleFields = statuses.filter(s => !s.is_fresh);
109
+ const oldFields = statuses.filter(s => s.age_seconds > 3600); // > 1 hour
110
+ if (staleFields.length > 0) {
111
+ recommendations.push(`${staleFields.length} field(s) have stale data: ${staleFields.map(s => s.field_name).join(", ")}`);
112
+ }
113
+ if (oldFields.length > 0) {
114
+ recommendations.push(`Consider refreshing old data for: ${oldFields.map(s => s.field_name).join(", ")}`);
115
+ }
116
+ if (statuses.length === 0) {
117
+ recommendations.push("No cached data found. Run discover_capabilities to populate cache.");
118
+ }
119
+ if (recommendations.length === 0) {
120
+ recommendations.push("Cache is healthy - all data is fresh and within TTL limits.");
121
+ }
122
+ return recommendations;
123
+ }
@@ -1,16 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toolHandlers = exports.allTools = exports.handleProvideFeedback = exports.provideFeedbackTool = exports.handleExportTrainingData = exports.exportTrainingDataTool = exports.handleCreateVM = exports.createVMTool = exports.handleParseIntent = exports.parseIntentTool = exports.handleGetResources = exports.getResourcesTool = void 0;
4
- // Import and re-export all tools and their handlers
5
- const get_resources_js_1 = require("./get-resources.js");
3
+ exports.toolHandlers = exports.allTools = exports.handleQueryResources = exports.queryResourcesTool = exports.handleGetCacheStatus = exports.getCacheStatusTool = exports.handleCheckCapability = exports.checkCapabilityTool = exports.handleDiscoverCapabilities = exports.discoverCapabilitiesTool = exports.handleProvideFeedback = exports.provideFeedbackTool = exports.handleExportTrainingData = exports.exportTrainingDataTool = exports.handleCreateVM = exports.createVMTool = exports.handleParseIntent = exports.parseIntentTool = exports.handleGetResources = exports.getResourcesTool = void 0;
6
4
  const parse_intent_js_1 = require("./parse-intent.js");
7
5
  const create_vm_js_1 = require("./create-vm.js");
8
6
  const export_training_data_js_1 = require("./export-training-data.js");
9
7
  const provide_feedback_js_1 = require("./provide-feedback.js");
8
+ // Sprint 2: Capability Discovery Tools
9
+ const discover_capabilities_js_1 = require("./discover-capabilities.js");
10
+ const check_capability_js_1 = require("./check-capability.js");
11
+ const get_cache_status_js_1 = require("./get-cache-status.js");
12
+ const query_resources_js_1 = require("./query-resources.js");
10
13
  // Re-export for external use
11
- var get_resources_js_2 = require("./get-resources.js");
12
- Object.defineProperty(exports, "getResourcesTool", { enumerable: true, get: function () { return get_resources_js_2.getResourcesTool; } });
13
- Object.defineProperty(exports, "handleGetResources", { enumerable: true, get: function () { return get_resources_js_2.handleGetResources; } });
14
+ var get_resources_js_1 = require("./get-resources.js");
15
+ Object.defineProperty(exports, "getResourcesTool", { enumerable: true, get: function () { return get_resources_js_1.getResourcesTool; } });
16
+ Object.defineProperty(exports, "handleGetResources", { enumerable: true, get: function () { return get_resources_js_1.handleGetResources; } });
14
17
  var parse_intent_js_2 = require("./parse-intent.js");
15
18
  Object.defineProperty(exports, "parseIntentTool", { enumerable: true, get: function () { return parse_intent_js_2.parseIntentTool; } });
16
19
  Object.defineProperty(exports, "handleParseIntent", { enumerable: true, get: function () { return parse_intent_js_2.handleParseIntent; } });
@@ -23,19 +26,43 @@ Object.defineProperty(exports, "handleExportTrainingData", { enumerable: true, g
23
26
  var provide_feedback_js_2 = require("./provide-feedback.js");
24
27
  Object.defineProperty(exports, "provideFeedbackTool", { enumerable: true, get: function () { return provide_feedback_js_2.provideFeedbackTool; } });
25
28
  Object.defineProperty(exports, "handleProvideFeedback", { enumerable: true, get: function () { return provide_feedback_js_2.handleProvideFeedback; } });
29
+ var discover_capabilities_js_2 = require("./discover-capabilities.js");
30
+ Object.defineProperty(exports, "discoverCapabilitiesTool", { enumerable: true, get: function () { return discover_capabilities_js_2.discoverCapabilitiesTool; } });
31
+ Object.defineProperty(exports, "handleDiscoverCapabilities", { enumerable: true, get: function () { return discover_capabilities_js_2.handleDiscoverCapabilities; } });
32
+ var check_capability_js_2 = require("./check-capability.js");
33
+ Object.defineProperty(exports, "checkCapabilityTool", { enumerable: true, get: function () { return check_capability_js_2.checkCapabilityTool; } });
34
+ Object.defineProperty(exports, "handleCheckCapability", { enumerable: true, get: function () { return check_capability_js_2.handleCheckCapability; } });
35
+ var get_cache_status_js_2 = require("./get-cache-status.js");
36
+ Object.defineProperty(exports, "getCacheStatusTool", { enumerable: true, get: function () { return get_cache_status_js_2.getCacheStatusTool; } });
37
+ Object.defineProperty(exports, "handleGetCacheStatus", { enumerable: true, get: function () { return get_cache_status_js_2.handleGetCacheStatus; } });
38
+ var query_resources_js_2 = require("./query-resources.js");
39
+ Object.defineProperty(exports, "queryResourcesTool", { enumerable: true, get: function () { return query_resources_js_2.queryResourcesTool; } });
40
+ Object.defineProperty(exports, "handleQueryResources", { enumerable: true, get: function () { return query_resources_js_2.handleQueryResources; } });
26
41
  // Collect all tool definitions
27
42
  exports.allTools = [
28
- get_resources_js_1.getResourcesTool,
43
+ // Sprint 1.5 tools
44
+ // getResourcesTool, // DISABLED - caused 37K+ token responses, replaced by Sprint 2 capability discovery
29
45
  parse_intent_js_1.parseIntentTool,
30
46
  create_vm_js_1.createVMTool,
31
47
  export_training_data_js_1.exportTrainingDataTool,
32
- provide_feedback_js_1.provideFeedbackTool
48
+ provide_feedback_js_1.provideFeedbackTool,
49
+ // Sprint 2 capability discovery tools
50
+ discover_capabilities_js_1.discoverCapabilitiesTool,
51
+ check_capability_js_1.checkCapabilityTool,
52
+ get_cache_status_js_1.getCacheStatusTool,
53
+ query_resources_js_1.queryResourcesTool
33
54
  ];
34
55
  // Export tool handlers map for easy lookup
35
56
  exports.toolHandlers = {
36
- get_resources: get_resources_js_1.handleGetResources,
57
+ // Sprint 1.5 handlers
58
+ // get_resources: handleGetResources, // DISABLED - caused 37K+ token responses
37
59
  parse_vm_intent: parse_intent_js_1.handleParseIntent,
38
60
  create_vm: create_vm_js_1.handleCreateVM,
39
61
  export_training_data: export_training_data_js_1.handleExportTrainingData,
40
- provide_feedback: provide_feedback_js_1.handleProvideFeedback
62
+ provide_feedback: provide_feedback_js_1.handleProvideFeedback,
63
+ // Sprint 2 capability discovery handlers
64
+ discover_capabilities: discover_capabilities_js_1.handleDiscoverCapabilities,
65
+ check_capability: check_capability_js_1.handleCheckCapability,
66
+ get_cache_status: get_cache_status_js_1.handleGetCacheStatus,
67
+ query_resources: query_resources_js_1.handleQueryResources
41
68
  };
@@ -0,0 +1,248 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleQueryResources = exports.queryResourcesTool = void 0;
4
+ const api_utils_js_1 = require("../lib/api-utils.js");
5
+ exports.queryResourcesTool = {
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.",
8
+ inputSchema: {
9
+ type: "object",
10
+ properties: {
11
+ natural_query: {
12
+ type: "string",
13
+ description: "Natural language query like 'Windows VMs', 'stopped VMs in production', 'any VMs?', 'Ubuntu hosts', 'VMs in development group'"
14
+ },
15
+ limit: {
16
+ type: "number",
17
+ description: "Maximum number of results to return (default: 10, max: 100)",
18
+ default: 10
19
+ }
20
+ },
21
+ required: ["natural_query"]
22
+ }
23
+ };
24
+ function parseResourceQuery(query, limit = 10) {
25
+ const q = query.toLowerCase().trim();
26
+ // Determine resource type
27
+ let resource = 'instances'; // default
28
+ if (q.includes('host') || q.includes('server') || q.includes('node')) {
29
+ resource = 'hosts';
30
+ }
31
+ else if (q.includes('network')) {
32
+ resource = 'networks';
33
+ }
34
+ // Determine action
35
+ let action = 'list';
36
+ if (q.includes('how many') || q.includes('count')) {
37
+ action = 'count';
38
+ }
39
+ else if (q.includes('any ') || q.includes('summary')) {
40
+ action = 'summary';
41
+ }
42
+ // Extract filters
43
+ const filters = {};
44
+ // OS Type patterns
45
+ if (q.includes('windows'))
46
+ filters.osType = 'windows';
47
+ if (q.includes('linux'))
48
+ filters.osType = 'linux';
49
+ if (q.includes('ubuntu'))
50
+ filters.osType = 'ubuntu';
51
+ if (q.includes('centos'))
52
+ filters.osType = 'centos';
53
+ if (q.includes('rocky'))
54
+ filters.osType = 'rocky';
55
+ if (q.includes('debian'))
56
+ filters.osType = 'debian';
57
+ if (q.includes('rhel'))
58
+ filters.osType = 'rhel';
59
+ // Status patterns
60
+ if (q.includes('running'))
61
+ filters.status = 'running';
62
+ if (q.includes('stopped'))
63
+ filters.status = 'stopped';
64
+ if (q.includes('failed') || q.includes('error'))
65
+ filters.status = 'failed';
66
+ if (q.includes('provisioning'))
67
+ filters.status = 'provisioning';
68
+ // Group patterns
69
+ if (q.includes('production') || q.includes('prod'))
70
+ filters.group = 'production';
71
+ if (q.includes('development') || q.includes('dev'))
72
+ filters.group = 'development';
73
+ if (q.includes('test') || q.includes('testing'))
74
+ filters.group = 'test';
75
+ // Name patterns (exact matches)
76
+ const nameMatch = q.match(/named?\s+["']?([a-zA-Z0-9\-_]+)["']?/);
77
+ if (nameMatch) {
78
+ filters.name = nameMatch[1];
79
+ }
80
+ return { resource, action, filters, limit };
81
+ }
82
+ function filterResults(data, filters) {
83
+ return data.filter(item => {
84
+ // OS Type filtering
85
+ if (filters.osType) {
86
+ const itemOS = item.osType?.toLowerCase() || '';
87
+ const filterOS = filters.osType.toLowerCase();
88
+ if (filterOS === 'windows' && !itemOS.includes('windows'))
89
+ return false;
90
+ if (filterOS === 'linux' && !itemOS.includes('linux') && !itemOS.includes('ubuntu') && !itemOS.includes('centos') && !itemOS.includes('rocky') && !itemOS.includes('debian') && !itemOS.includes('rhel'))
91
+ return false;
92
+ if (filterOS !== 'windows' && filterOS !== 'linux' && !itemOS.includes(filterOS))
93
+ return false;
94
+ }
95
+ // Status filtering
96
+ if (filters.status) {
97
+ const itemStatus = item.status?.toLowerCase() || '';
98
+ if (!itemStatus.includes(filters.status.toLowerCase()))
99
+ return false;
100
+ }
101
+ // Group filtering
102
+ if (filters.group) {
103
+ const itemGroup = item.group?.name?.toLowerCase() || item.site?.name?.toLowerCase() || '';
104
+ if (!itemGroup.includes(filters.group.toLowerCase()))
105
+ return false;
106
+ }
107
+ // Name filtering
108
+ if (filters.name) {
109
+ const itemName = item.name?.toLowerCase() || '';
110
+ if (!itemName.includes(filters.name.toLowerCase()))
111
+ return false;
112
+ }
113
+ return true;
114
+ });
115
+ }
116
+ function formatResponse(parsedQuery, results, totalCount) {
117
+ const { action, resource, filters } = parsedQuery;
118
+ if (action === 'count') {
119
+ return {
120
+ action: 'count',
121
+ resource: resource,
122
+ filters_applied: filters,
123
+ total_count: results.length,
124
+ query_summary: `Found ${results.length} ${resource} matching criteria`
125
+ };
126
+ }
127
+ if (action === 'summary') {
128
+ const statusCounts = {};
129
+ const osCounts = {};
130
+ results.forEach(item => {
131
+ const status = item.status || 'unknown';
132
+ const os = item.osType || 'unknown';
133
+ statusCounts[status] = (statusCounts[status] || 0) + 1;
134
+ osCounts[os] = (osCounts[os] || 0) + 1;
135
+ });
136
+ return {
137
+ action: 'summary',
138
+ resource: resource,
139
+ filters_applied: filters,
140
+ total_count: results.length,
141
+ summary: {
142
+ by_status: statusCounts,
143
+ by_os_type: osCounts
144
+ },
145
+ query_summary: `Found ${results.length} ${resource} matching criteria`
146
+ };
147
+ }
148
+ // Default: list action
149
+ const limitedResults = results.slice(0, parsedQuery.limit);
150
+ return {
151
+ action: 'list',
152
+ resource: resource,
153
+ filters_applied: filters,
154
+ total_count: results.length,
155
+ 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
+ })),
168
+ query_summary: `Showing ${limitedResults.length} of ${results.length} ${resource} matching criteria`
169
+ };
170
+ }
171
+ async function handleQueryResources(args) {
172
+ try {
173
+ const { natural_query, limit = 10 } = args;
174
+ if (!natural_query || typeof natural_query !== 'string') {
175
+ return {
176
+ content: [
177
+ {
178
+ type: "text",
179
+ text: JSON.stringify({
180
+ error: "Invalid input",
181
+ message: "natural_query parameter is required and must be a string",
182
+ examples: ["any VMs?", "Windows VMs", "stopped VMs in production", "Ubuntu hosts"]
183
+ }, null, 2)
184
+ }
185
+ ],
186
+ isError: true
187
+ };
188
+ }
189
+ const parsedQuery = parseResourceQuery(natural_query, Math.min(limit, 100));
190
+ // Map resource type to API endpoint
191
+ const endpointMap = {
192
+ 'instances': '/instances',
193
+ 'hosts': '/servers',
194
+ 'networks': '/networks'
195
+ };
196
+ const endpoint = endpointMap[parsedQuery.resource];
197
+ // Query the VME API
198
+ const response = await api_utils_js_1.api.get(endpoint);
199
+ // Extract data array from response
200
+ let rawData = [];
201
+ if (parsedQuery.resource === 'instances' && response.data.instances) {
202
+ rawData = response.data.instances;
203
+ }
204
+ else if (parsedQuery.resource === 'hosts' && response.data.servers) {
205
+ rawData = response.data.servers;
206
+ }
207
+ else if (parsedQuery.resource === 'networks' && response.data.networks) {
208
+ rawData = response.data.networks;
209
+ }
210
+ else {
211
+ // Fallback: try to find array in response
212
+ const possibleArrays = Object.values(response.data).filter(Array.isArray);
213
+ if (possibleArrays.length > 0) {
214
+ rawData = possibleArrays[0];
215
+ }
216
+ }
217
+ // Apply filters
218
+ const filteredResults = filterResults(rawData, parsedQuery.filters);
219
+ // Format response based on action
220
+ const formattedResponse = formatResponse(parsedQuery, filteredResults, rawData.length);
221
+ return {
222
+ content: [
223
+ {
224
+ type: "text",
225
+ text: JSON.stringify(formattedResponse, null, 2)
226
+ }
227
+ ],
228
+ isError: false
229
+ };
230
+ }
231
+ catch (error) {
232
+ return {
233
+ content: [
234
+ {
235
+ type: "text",
236
+ text: JSON.stringify({
237
+ error: "Resource query failed",
238
+ message: error.message,
239
+ natural_query: args?.natural_query || "unknown",
240
+ suggestion: "Check VME API connectivity or try discover_capabilities first to see available resources"
241
+ }, null, 2)
242
+ }
243
+ ],
244
+ isError: true
245
+ };
246
+ }
247
+ }
248
+ exports.handleQueryResources = handleQueryResources;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vme-mcp-server",
3
- "version": "0.1.6",
4
- "description": "VMware vCenter MCP Server - Natural language infrastructure management for Claude",
3
+ "version": "0.1.7",
4
+ "description": "",
5
5
  "main": "dist/server.js",
6
6
  "bin": {
7
7
  "vme-mcp-server": "./dist/server.js"
@@ -47,8 +47,8 @@
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/node": "^22.15.29",
50
+ "chai": "^5.2.0",
50
51
  "mocha": "^10.2.0",
51
52
  "nyc": "^15.1.0"
52
- },
53
- "description": ""
53
+ }
54
54
  }