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"] } },
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It states it 'gets' information, implying a read-only operation, but doesn't specify permissions required, rate limits, error handling, or what 'detailed information' entails (e.g., format, fields). This is insufficient for a tool with no annotation coverage.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, clear sentence with no wasted words. It is appropriately sized and front-loaded, directly stating the tool's purpose without unnecessary elaboration, making it efficient and easy to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the lack of annotations and output schema, the description is incomplete. It doesn't explain what 'detailed information' includes (e.g., response structure), behavioral aspects like errors or permissions, or how it differs from sibling tools. For a tool in this context, more detail is needed to be fully helpful.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema description coverage is 100%, with the parameter 'id' documented as 'Tenant ID'. The description adds no additional meaning beyond this, such as format examples or constraints. Given the high schema coverage, the baseline score of 3 is appropriate as the schema handles the parameter documentation adequately.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb ('Get') and resource ('detailed information about a specific tenant'), making the purpose understandable. However, it doesn't explicitly differentiate from sibling tools like 'list_tenants' or 'tenant_action', which would require more specificity for a perfect score.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention when to choose 'get_tenant' over 'list_tenants' for listing tenants or 'tenant_action' for performing actions on tenants, leaving the agent without usage context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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