Skip to main content
Glama
dvvincent

VergeOS MCP Server

by dvvincent

get_tenant

Retrieve detailed information about a specific tenant in VergeOS virtualization platforms by providing the tenant ID.

Instructions

Get detailed information about a specific tenant

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
idYesTenant ID

Implementation Reference

  • Core handler function in the VergeOSAPI class that executes the tool logic by making an authenticated HTTP request to the VergeOS API endpoint for tenant details.
    async getTenant(id) {
      return this.request(`/api/v4/tenants/${id}?fields=most`);
    }
  • Input schema definition for the get_tenant tool, specifying the required 'id' parameter as a number.
    {
      name: "get_tenant",
      description: "Get detailed information about a specific tenant",
      inputSchema: {
        type: "object",
        properties: {
          id: {
            type: "number",
            description: "Tenant ID",
          },
        },
        required: ["id"],
      },
    },
  • src/index.js:579-581 (registration)
    Registration and dispatch logic within the MCP CallToolRequest handler switch statement that routes the tool call to the getTenant function.
    case "get_tenant":
      result = await api.getTenant(args.id);
      break;
  • Identical handler function in the HTTP MCP server variant.
      return {
        vm_id: id,
        name: vm.name,
        machine: vm.machine,
        running: status.running,
        power_state: status.status,
        status_info: status.status_info || "",
        migratable: status.migratable,
      };
    }
    async vmAction(id, action) {
      return this.request("/api/v4/vm_actions", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ vm: id, action }),
      });
    }
    async powerOnVM(id) {
      const status = await this.getVMStatus(id);
      if (status.running) {
        return { success: false, error: `VM '${status.name}' is already running`, current_state: status.power_state };
      }
      await this.vmAction(id, "poweron");
      return { success: true, message: `Power on command sent to VM '${status.name}'`, previous_state: status.power_state };
    }
    
    async powerOffVM(id, options = {}) {
      const { wait_timeout = 0, force_after_timeout = false } = options;
      const status = await this.getVMStatus(id);
      
      if (!status.running) {
        return { success: true, message: `VM '${status.name}' is already stopped`, current_state: status.power_state, was_running: false };
      }
      
      // Send graceful shutdown
      await this.vmAction(id, "poweroff");
      
      // If no wait requested, return immediately
      if (wait_timeout <= 0) {
        return { 
          success: true, 
          message: `Graceful shutdown command sent to VM '${status.name}'`, 
          previous_state: status.power_state,
          note: "Use wait_timeout parameter to wait for shutdown completion"
        };
      }
      
      // Poll for shutdown with timeout
      const startTime = Date.now();
      const pollInterval = 3000; // 3 seconds
      const maxWait = Math.min(wait_timeout, 300) * 1000; // Cap at 5 minutes
      
      while (Date.now() - startTime < maxWait) {
        await new Promise(resolve => setTimeout(resolve, pollInterval));
        const currentStatus = await this.getVMStatus(id);
        
        if (!currentStatus.running) {
          return { 
            success: true, 
            message: `VM '${status.name}' shut down gracefully`, 
            final_state: currentStatus.power_state,
            elapsed_seconds: Math.round((Date.now() - startTime) / 1000)
          };
        }
      }
      
      // Timeout reached - check if we should force
      if (force_after_timeout) {
        await this.vmAction(id, "kill");
        // Wait a moment for kill to take effect
        await new Promise(resolve => setTimeout(resolve, 2000));
        const finalStatus = await this.getVMStatus(id);
        return { 
          success: true, 
          message: `VM '${status.name}' did not shut down within ${wait_timeout}s - forced power off`, 
          final_state: finalStatus.power_state,
          forced: true,
          elapsed_seconds: Math.round((Date.now() - startTime) / 1000)
        };
      }
      
      // Timeout without force
      const currentStatus = await this.getVMStatus(id);
      return { 
        success: false, 
        error: `VM '${status.name}' did not shut down within ${wait_timeout}s`, 
        current_state: currentStatus.power_state,
        elapsed_seconds: Math.round((Date.now() - startTime) / 1000),
        hint: "Use force_after_timeout=true to automatically force shutdown after timeout"
      };
    }
    
    async forceOffVM(id) {
      const status = await this.getVMStatus(id);
      if (!status.running) {
        return { success: true, message: `VM '${status.name}' is already stopped`, current_state: status.power_state };
      }
      await this.vmAction(id, "kill");
      return { success: true, message: `Force power off (kill) command sent to VM '${status.name}'`, previous_state: status.power_state };
    }
    
    async resetVM(id) {
      const status = await this.getVMStatus(id);
      if (!status.running) {
        return { success: false, error: `VM '${status.name}' is not running - cannot reset`, current_state: status.power_state };
      }
      await this.vmAction(id, "reset");
      return { success: true, message: `Reset command sent to VM '${status.name}'`, previous_state: status.power_state };
    }
    async getVMNics(vmId) {
      // Get VM to find machine ID
      const vm = await this.getVM(vmId);
      const machineId = vm.machine;
      const nics = await this.request(`/api/v4/machine_nics?machine=${machineId}&fields=all`);
      return nics.filter((nic) => nic.machine === machineId).map(n => ({
        id: n.$key,
        name: n.name,
        mac: n.macaddress,
        network_id: n.vnet,
        ip: n.ipaddress,
        interface: n.interface,
        enabled: n.enabled,
      }));
    }
    async getVMDrives(vmId) {
      // Get VM to find machine ID (same pattern as getVMNics)
      const vm = await this.getVM(vmId);
      const machineId = vm.machine;
      const drives = await this.request(`/api/v4/machine_drives?machine=${machineId}&fields=all`);
      // Filter by machine ID and return simplified info
      return drives
        .filter((d) => d.machine === machineId)
        .map((d) => ({
          id: d.$key,
          name: d.name,
          interface: d.interface,
          size_gb: d.disksize ? Math.round(d.disksize / (1024 * 1024 * 1024)) : null,
          size_bytes: d.disksize || null,
          enabled: d.enabled,
          media_type: d.media_type,
          description: d.description || "",
        }));
    }
    
    async resizeDrive(driveId, newSizeGB) {
      // Get current drive info
      const drive = await this.request(`/api/v4/machine_drives/${driveId}?fields=all`);
      if (!drive) {
        return { success: false, error: `Drive ${driveId} not found` };
      }
      
      const currentSizeGB = Math.round(drive.disksize / (1024 * 1024 * 1024));
      const newSizeBytes = newSizeGB * 1024 * 1024 * 1024;
      
      if (newSizeGB <= currentSizeGB) {
        return { 
          success: false, 
          error: `New size (${newSizeGB} GB) must be larger than current size (${currentSizeGB} GB). Shrinking disks is not supported.`,
          current_size_gb: currentSizeGB
        };
      }
      
      // Resize the drive
      await this.request(`/api/v4/machine_drives/${driveId}`, {
        method: "PUT",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ disksize: newSizeBytes }),
      });
      
      return { 
        success: true, 
        message: `Drive '${drive.name}' resized from ${currentSizeGB} GB to ${newSizeGB} GB`,
        drive_id: driveId,
        previous_size_gb: currentSizeGB,
        new_size_gb: newSizeGB,
        note: "You may need to extend the partition/filesystem inside the VM to use the new space"
      };
    }
    
    async addDrive(machineId, options = {}) {
      const { name, size_gb, interface_type = "virtio-scsi", description = "" } = options;
      
      if (!name) {
        return { success: false, error: "Drive name is required" };
      }
      if (!size_gb || size_gb < 1) {
        return { success: false, error: "Size in GB is required and must be at least 1 GB" };
      }
      
      const sizeBytes = size_gb * 1024 * 1024 * 1024;
      
      // Create the drive
      const result = await this.request("/api/v4/machine_drives", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          machine: machineId,
          name: name,
          disksize: sizeBytes,
          interface: interface_type,
          media: "disk",
          description: description,
          enabled: true,
        }),
      });
      
      return { 
        success: true, 
        message: `Drive '${name}' (${size_gb} GB) added to machine ${machineId}`,
        drive_id: result.$key,
        name: name,
        size_gb: size_gb,
        interface: interface_type,
        note: "The VM may need to be restarted to detect the new drive"
      };
    }
    
    async modifyVM(vmId, options = {}) {
      const { cpu_cores, ram_mb, shutdown_if_running = false, wait_timeout = 60, force_after_timeout = true } = options;
      
      if (!cpu_cores && !ram_mb) {
        return { success: false, error: "Must specify cpu_cores and/or ram_mb to modify" };
      }
      
      // Get current VM info and status
      const vm = await this.getVM(vmId);
      const status = await this.getVMStatus(vmId);
      
      const changes = {};
      if (cpu_cores) changes.cpu_cores = cpu_cores;
      if (ram_mb) changes.ram = ram_mb;
      
      // Check if VM is running
      if (status.running) {
        if (!shutdown_if_running) {
          return {
            success: false,
            error: `VM '${vm.name}' is currently running. CPU/RAM changes require the VM to be powered off.`,
            current_state: status.power_state,
            current_cpu: vm.cpu_cores,
            current_ram_mb: vm.ram,
            requested_cpu: cpu_cores || vm.cpu_cores,
            requested_ram_mb: ram_mb || vm.ram,
            hint: "Set shutdown_if_running=true to automatically shut down the VM, apply changes, and optionally restart it"
          };
        }
        
        // Shut down the VM
        const shutdownResult = await this.powerOffVM(vmId, { wait_timeout, force_after_timeout });
        if (!shutdownResult.success && !shutdownResult.message?.includes("already stopped")) {
          return {
            success: false,
            error: `Failed to shut down VM '${vm.name}': ${shutdownResult.error}`,
            shutdown_result: shutdownResult
          };
        }
      }
      
      // Apply changes
      await this.request(`/api/v4/vms/${vmId}`, {
        method: "PUT",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(changes),
      });
      
      return {
        success: true,
        message: `VM '${vm.name}' modified successfully`,
        vm_id: vmId,
        previous_cpu: vm.cpu_cores,
        previous_ram_mb: vm.ram,
        new_cpu: cpu_cores || vm.cpu_cores,
        new_ram_mb: ram_mb || vm.ram,
        was_running: status.running,
        note: status.running ? "VM was shut down to apply changes. Use power_on_vm to restart it." : "VM is stopped. Use power_on_vm to start it with new settings."
      };
    }
    
    // Network Operations
    async listNetworks(options = {}) {
      const { type, name, enabled, limit = 100, offset = 0 } = options;
      const networks = await this.request("/api/v4/vnets?fields=most");
      
      // Filter
      let filtered = networks;
      if (type) filtered = filtered.filter(n => n.type === type);
      if (name) filtered = filtered.filter(n => n.name?.toLowerCase().includes(name.toLowerCase()));
      if (enabled !== undefined) filtered = filtered.filter(n => n.enabled === enabled);
      
      // Paginate and return summary view
      return filtered.slice(offset, offset + limit).map(n => ({
        id: n.$key,
        name: n.name,
        type: n.type,
        network: n.network,
        enabled: n.enabled,
        running: n.running,
        description: n.description || null,
      }));
    }
    async getNetwork(id) { return this.request(`/api/v4/vnets/${id}?fields=most`); }
    async networkAction(id, action) {
      return this.request("/api/v4/vnet_actions", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ vnet: id, action }),
      });
    }
    
    // Tenant Operations
    async listTenants() { return this.request("/api/v4/tenants?fields=most"); }
    async getTenant(id) { return this.request(`/api/v4/tenants/${id}?fields=most`); }
  • Schema in the HTTP MCP server's compact TOOLS array.
    { name: "get_tenant", description: "Get tenant details", inputSchema: { type: "object", properties: { id: { type: "number" } }, required: ["id"] } },
    { name: "tenant_action", description: "Perform tenant action", inputSchema: { type: "object", properties: { id: { type: "number" }, action: { type: "string", enum: ["poweron", "poweroff", "reset"] } }, required: ["id", "action"] } },

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dvvincent/vergeos-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server