Files

3162 lines
120 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import fetch from 'node-fetch';
import https from 'https';
import crypto from 'crypto';
import { readFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { homedir } from 'os';
// Load environment variables from ~/.env file (standardized location)
const envPath = join(homedir(), '.env');
// Also try relative path as fallback for backwards compatibility
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const envPathFallback = join(__dirname, '../.env');
function loadEnvFile(filePath) {
try {
const envFile = readFileSync(filePath, 'utf8');
const envVars = envFile.split('\n').filter(line => line.includes('=') && !line.trim().startsWith('#'));
for (const line of envVars) {
const [key, ...values] = line.split('=');
// Validate key is a valid environment variable name (alphanumeric and underscore only)
if (key && values.length > 0 && /^[A-Z_][A-Z0-9_]*$/.test(key.trim())) {
// Remove surrounding quotes if present and trim
let value = values.join('=').trim();
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
value = value.slice(1, -1);
}
process.env[key.trim()] = value;
}
}
return true;
} catch (error) {
return false;
}
}
// Try ~/.env first, then fallback to relative path
if (!loadEnvFile(envPath)) {
if (!loadEnvFile(envPathFallback)) {
console.error('Warning: Could not load .env file from ~/.env or ../.env');
}
}
class ProxmoxServer {
constructor() {
this.server = new Server(
{
name: 'proxmox-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
this.proxmoxHost = process.env.PROXMOX_HOST || '192.168.6.247';
this.proxmoxUser = process.env.PROXMOX_USER || 'root@pam';
this.proxmoxTokenName = process.env.PROXMOX_TOKEN_NAME || 'mcpserver';
this.proxmoxTokenValue = process.env.PROXMOX_TOKEN_VALUE;
this.proxmoxPort = process.env.PROXMOX_PORT || '8006';
this.allowElevated = process.env.PROXMOX_ALLOW_ELEVATED === 'true';
// Create agent that accepts self-signed certificates
this.httpsAgent = new https.Agent({
rejectUnauthorized: false
});
this.setupToolHandlers();
}
// Input validation methods for security
validateNodeName(node) {
if (!node || typeof node !== 'string') {
throw new Error('Node name is required and must be a string');
}
// Only allow alphanumeric, hyphens, and underscores
if (!/^[a-zA-Z0-9\-_]+$/.test(node)) {
throw new Error('Invalid node name format. Only alphanumeric, hyphens, and underscores allowed');
}
if (node.length > 64) {
throw new Error('Node name too long (max 64 characters)');
}
return node;
}
validateVMID(vmid) {
if (!vmid) {
throw new Error('VM ID is required');
}
const id = parseInt(vmid, 10);
if (isNaN(id) || id < 100 || id > 999999999) {
throw new Error('Invalid VM ID. Must be a number between 100 and 999999999');
}
return id.toString();
}
validateCommand(command) {
if (!command || typeof command !== 'string') {
throw new Error('Command is required and must be a string');
}
// Check for dangerous characters that could be used for command injection
const dangerousChars = /[;&|`$(){}[\]<>\\]/g;
if (dangerousChars.test(command)) {
throw new Error('Command contains potentially dangerous characters: ; & | ` $ ( ) { } [ ] < > \\');
}
// Limit command length
if (command.length > 1000) {
throw new Error('Command exceeds maximum allowed length (1000 characters)');
}
return command;
}
generateSecurePassword() {
// Generate a secure random password using Node.js crypto
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';
let password = '';
const randomBytes = crypto.randomBytes(16);
for (let i = 0; i < 16; i++) {
password += chars[randomBytes[i] % chars.length];
}
return password;
}
async proxmoxRequest(endpoint, method = 'GET', body = null) {
const baseUrl = `https://${this.proxmoxHost}:${this.proxmoxPort}/api2/json`;
const url = `${baseUrl}${endpoint}`;
const headers = {
'Authorization': `PVEAPIToken=${this.proxmoxUser}!${this.proxmoxTokenName}=${this.proxmoxTokenValue}`,
'Content-Type': 'application/json'
};
const options = {
method,
headers,
agent: this.httpsAgent
};
if (body) {
options.body = JSON.stringify(body);
}
try {
const response = await fetch(url, options);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Proxmox API error: ${response.status} - ${errorText}`);
}
const textResponse = await response.text();
if (!textResponse.trim()) {
throw new Error('Empty response from Proxmox API');
}
const data = JSON.parse(textResponse);
return data.data;
} catch (error) {
if (error.name === 'SyntaxError') {
throw new Error(`Failed to parse Proxmox API response: ${error.message}`);
}
throw new Error(`Failed to connect to Proxmox: ${error.message}`);
}
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'proxmox_get_nodes',
description: 'List all Proxmox cluster nodes with their status and resources',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'proxmox_get_node_status',
description: 'Get detailed status information for a specific Proxmox node',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name (e.g., pve1, proxmox-node2)' }
},
required: ['node']
}
},
{
name: 'proxmox_get_vms',
description: 'List all virtual machines across the cluster with their status',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Optional: filter by specific node' },
type: { type: 'string', enum: ['qemu', 'lxc', 'all'], description: 'VM type filter', default: 'all' }
}
}
},
{
name: 'proxmox_get_vm_status',
description: 'Get detailed status information for a specific VM',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' },
type: { type: 'string', enum: ['qemu', 'lxc'], description: 'VM type', default: 'qemu' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_execute_vm_command',
description: 'Execute a shell command on a virtual machine via Proxmox API',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' },
command: { type: 'string', description: 'Shell command to execute' },
type: { type: 'string', enum: ['qemu', 'lxc'], description: 'VM type', default: 'qemu' }
},
required: ['node', 'vmid', 'command']
}
},
{
name: 'proxmox_get_storage',
description: 'List all storage pools and their usage across the cluster',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Optional: filter by specific node' }
}
}
},
{
name: 'proxmox_get_cluster_status',
description: 'Get overall cluster status including nodes and resource usage',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'proxmox_list_templates',
description: 'List available LXC container templates on a storage',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name' },
storage: { type: 'string', description: 'Storage name (e.g., local)', default: 'local' }
},
required: ['node']
}
},
{
name: 'proxmox_create_lxc',
description: 'Create a new LXC container (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container will be created' },
vmid: { type: 'string', description: 'Container ID number (must be unique, or use proxmox_get_next_vmid)' },
ostemplate: { type: 'string', description: 'OS template (e.g., local:vztmpl/debian-12-standard_12.2-1_amd64.tar.gz)' },
hostname: { type: 'string', description: 'Container hostname' },
password: { type: 'string', description: 'Root password' },
memory: { type: 'number', description: 'RAM in MB', default: 512 },
storage: { type: 'string', description: 'Storage location', default: 'local-lvm' },
rootfs: { type: 'string', description: 'Root filesystem size in GB', default: '8' }
},
required: ['node', 'vmid', 'ostemplate']
}
},
{
name: 'proxmox_create_vm',
description: 'Create a new QEMU virtual machine (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM will be created' },
vmid: { type: 'string', description: 'VM ID number (must be unique, or use proxmox_get_next_vmid)' },
name: { type: 'string', description: 'VM name' },
memory: { type: 'number', description: 'RAM in MB', default: 512 },
cores: { type: 'number', description: 'Number of CPU cores', default: 1 },
sockets: { type: 'number', description: 'Number of CPU sockets', default: 1 },
disk_size: { type: 'string', description: 'Disk size (e.g., "8G", "10G")', default: '8G' },
storage: { type: 'string', description: 'Storage location for disk', default: 'local-lvm' },
iso: { type: 'string', description: 'ISO image (e.g., "local:iso/alpine-virt-3.19.1-x86_64.iso"), optional' },
ostype: { type: 'string', description: 'OS type (l26=Linux 2.6+, win10, etc)', default: 'l26' },
net0: { type: 'string', description: 'Network interface config', default: 'virtio,bridge=vmbr0' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_get_next_vmid',
description: 'Get the next available VM/Container ID number',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'proxmox_start_lxc',
description: 'Start an LXC container (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container is located' },
vmid: { type: 'string', description: 'Container ID number' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_start_vm',
description: 'Start a QEMU virtual machine (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_stop_lxc',
description: 'Stop an LXC container (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container is located' },
vmid: { type: 'string', description: 'Container ID number' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_stop_vm',
description: 'Stop a QEMU virtual machine (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_delete_lxc',
description: 'Delete an LXC container (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container is located' },
vmid: { type: 'string', description: 'Container ID number to delete' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_delete_vm',
description: 'Delete a QEMU virtual machine (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number to delete' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_reboot_lxc',
description: 'Reboot an LXC container (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container is located' },
vmid: { type: 'string', description: 'Container ID number' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_reboot_vm',
description: 'Reboot a QEMU virtual machine (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_shutdown_lxc',
description: 'Gracefully shutdown an LXC container (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container is located' },
vmid: { type: 'string', description: 'Container ID number' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_shutdown_vm',
description: 'Gracefully shutdown a QEMU virtual machine (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_pause_vm',
description: 'Pause a QEMU virtual machine (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_resume_vm',
description: 'Resume a paused QEMU virtual machine (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_clone_lxc',
description: 'Clone an LXC container (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container is located' },
vmid: { type: 'string', description: 'Container ID to clone from' },
newid: { type: 'string', description: 'New container ID' },
hostname: { type: 'string', description: 'Hostname for cloned container (optional)' }
},
required: ['node', 'vmid', 'newid']
}
},
{
name: 'proxmox_clone_vm',
description: 'Clone a QEMU virtual machine (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID to clone from' },
newid: { type: 'string', description: 'New VM ID' },
name: { type: 'string', description: 'Name for cloned VM (optional)' }
},
required: ['node', 'vmid', 'newid']
}
},
{
name: 'proxmox_resize_lxc',
description: 'Resize an LXC container CPU/memory (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container is located' },
vmid: { type: 'string', description: 'Container ID number' },
memory: { type: 'number', description: 'Memory in MB (optional)' },
cores: { type: 'number', description: 'Number of CPU cores (optional)' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_resize_vm',
description: 'Resize a QEMU VM CPU/memory (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' },
memory: { type: 'number', description: 'Memory in MB (optional)' },
cores: { type: 'number', description: 'Number of CPU cores (optional)' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_create_snapshot_lxc',
description: 'Create a snapshot of an LXC container (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container is located' },
vmid: { type: 'string', description: 'Container ID number' },
snapname: { type: 'string', description: 'Snapshot name' }
},
required: ['node', 'vmid', 'snapname']
}
},
{
name: 'proxmox_create_snapshot_vm',
description: 'Create a snapshot of a QEMU virtual machine (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' },
snapname: { type: 'string', description: 'Snapshot name' }
},
required: ['node', 'vmid', 'snapname']
}
},
{
name: 'proxmox_list_snapshots_lxc',
description: 'List all snapshots of an LXC container (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container is located' },
vmid: { type: 'string', description: 'Container ID number' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_list_snapshots_vm',
description: 'List all snapshots of a QEMU virtual machine (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_rollback_snapshot_lxc',
description: 'Rollback an LXC container to a snapshot (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container is located' },
vmid: { type: 'string', description: 'Container ID number' },
snapname: { type: 'string', description: 'Snapshot name to rollback to' }
},
required: ['node', 'vmid', 'snapname']
}
},
{
name: 'proxmox_rollback_snapshot_vm',
description: 'Rollback a QEMU virtual machine to a snapshot (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' },
snapname: { type: 'string', description: 'Snapshot name to rollback to' }
},
required: ['node', 'vmid', 'snapname']
}
},
{
name: 'proxmox_delete_snapshot_lxc',
description: 'Delete a snapshot of an LXC container (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container is located' },
vmid: { type: 'string', description: 'Container ID number' },
snapname: { type: 'string', description: 'Snapshot name to delete' }
},
required: ['node', 'vmid', 'snapname']
}
},
{
name: 'proxmox_delete_snapshot_vm',
description: 'Delete a snapshot of a QEMU virtual machine (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' },
snapname: { type: 'string', description: 'Snapshot name to delete' }
},
required: ['node', 'vmid', 'snapname']
}
},
{
name: 'proxmox_create_backup_lxc',
description: 'Create a backup of an LXC container (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container is located' },
vmid: { type: 'string', description: 'Container ID number' },
storage: { type: 'string', description: 'Storage location for backup', default: 'local' },
mode: { type: 'string', enum: ['snapshot', 'suspend', 'stop'], description: 'Backup mode', default: 'snapshot' },
compress: { type: 'string', enum: ['none', 'lzo', 'gzip', 'zstd'], description: 'Compression algorithm', default: 'zstd' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_create_backup_vm',
description: 'Create a backup of a QEMU virtual machine (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' },
storage: { type: 'string', description: 'Storage location for backup', default: 'local' },
mode: { type: 'string', enum: ['snapshot', 'suspend', 'stop'], description: 'Backup mode', default: 'snapshot' },
compress: { type: 'string', enum: ['none', 'lzo', 'gzip', 'zstd'], description: 'Compression algorithm', default: 'zstd' }
},
required: ['node', 'vmid']
}
},
{
name: 'proxmox_list_backups',
description: 'List all backups on a storage (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name' },
storage: { type: 'string', description: 'Storage name', default: 'local' }
},
required: ['node']
}
},
{
name: 'proxmox_restore_backup_lxc',
description: 'Restore an LXC container from backup (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container will be restored' },
vmid: { type: 'string', description: 'New container ID for restored container' },
archive: { type: 'string', description: 'Backup archive path (e.g., local:backup/vzdump-lxc-100-2025_11_06-09_00_00.tar.zst)' },
storage: { type: 'string', description: 'Storage location for restored container (optional)' }
},
required: ['node', 'vmid', 'archive']
}
},
{
name: 'proxmox_restore_backup_vm',
description: 'Restore a QEMU virtual machine from backup (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM will be restored' },
vmid: { type: 'string', description: 'New VM ID for restored VM' },
archive: { type: 'string', description: 'Backup archive path (e.g., local:backup/vzdump-qemu-100-2025_11_06-09_00_00.vma.zst)' },
storage: { type: 'string', description: 'Storage location for restored VM (optional)' }
},
required: ['node', 'vmid', 'archive']
}
},
{
name: 'proxmox_delete_backup',
description: 'Delete a backup file from storage (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name' },
storage: { type: 'string', description: 'Storage name (e.g., local)' },
volume: { type: 'string', description: 'Backup volume ID (e.g., local:backup/vzdump-lxc-100-2025_11_06-09_00_00.tar.zst)' }
},
required: ['node', 'storage', 'volume']
}
},
{
name: 'proxmox_add_disk_vm',
description: 'Add a new disk to a QEMU virtual machine (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' },
disk: { type: 'string', description: 'Disk name (e.g., scsi1, virtio1, sata1, ide1)' },
storage: { type: 'string', description: 'Storage name (e.g., local-lvm)' },
size: { type: 'string', description: 'Disk size in GB (e.g., 10)' }
},
required: ['node', 'vmid', 'disk', 'storage', 'size']
}
},
{
name: 'proxmox_add_mountpoint_lxc',
description: 'Add a mount point to an LXC container (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container is located' },
vmid: { type: 'string', description: 'Container ID number' },
mp: { type: 'string', description: 'Mount point name (e.g., mp0, mp1, mp2)' },
storage: { type: 'string', description: 'Storage name (e.g., local-lvm)' },
size: { type: 'string', description: 'Mount point size in GB (e.g., 10)' }
},
required: ['node', 'vmid', 'mp', 'storage', 'size']
}
},
{
name: 'proxmox_resize_disk_vm',
description: 'Resize a QEMU VM disk (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' },
disk: { type: 'string', description: 'Disk name (e.g., scsi0, virtio0, sata0, ide0)' },
size: { type: 'string', description: 'New size with + for relative or absolute (e.g., +10G or 50G)' }
},
required: ['node', 'vmid', 'disk', 'size']
}
},
{
name: 'proxmox_resize_disk_lxc',
description: 'Resize an LXC container disk or mount point (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container is located' },
vmid: { type: 'string', description: 'Container ID number' },
disk: { type: 'string', description: 'Disk name (rootfs, mp0, mp1, etc.)' },
size: { type: 'string', description: 'New size with + for relative or absolute (e.g., +10G or 50G)' }
},
required: ['node', 'vmid', 'disk', 'size']
}
},
{
name: 'proxmox_remove_disk_vm',
description: 'Remove a disk from a QEMU virtual machine (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' },
disk: { type: 'string', description: 'Disk name to remove (e.g., scsi1, virtio1, sata1, ide1)' }
},
required: ['node', 'vmid', 'disk']
}
},
{
name: 'proxmox_remove_mountpoint_lxc',
description: 'Remove a mount point from an LXC container (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container is located' },
vmid: { type: 'string', description: 'Container ID number' },
mp: { type: 'string', description: 'Mount point name to remove (e.g., mp0, mp1, mp2)' }
},
required: ['node', 'vmid', 'mp']
}
},
{
name: 'proxmox_move_disk_vm',
description: 'Move a QEMU VM disk to different storage (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' },
disk: { type: 'string', description: 'Disk name to move (e.g., scsi0, virtio0, sata0, ide0)' },
storage: { type: 'string', description: 'Target storage name' },
delete: { type: 'boolean', description: 'Delete source disk after move (default: true)', default: true }
},
required: ['node', 'vmid', 'disk', 'storage']
}
},
{
name: 'proxmox_move_disk_lxc',
description: 'Move an LXC container disk to different storage (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container is located' },
vmid: { type: 'string', description: 'Container ID number' },
disk: { type: 'string', description: 'Disk/volume name to move (rootfs, mp0, mp1, etc.)' },
storage: { type: 'string', description: 'Target storage name' },
delete: { type: 'boolean', description: 'Delete source disk after move (default: true)', default: true }
},
required: ['node', 'vmid', 'disk', 'storage']
}
},
{
name: 'proxmox_add_network_vm',
description: 'Add network interface to QEMU VM (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' },
net: { type: 'string', description: 'Network interface name (net0, net1, net2, etc.)' },
bridge: { type: 'string', description: 'Bridge name (e.g., vmbr0, vmbr1)' },
model: { type: 'string', description: 'Network model (virtio, e1000, rtl8139, vmxnet3)', default: 'virtio' },
macaddr: { type: 'string', description: 'MAC address (XX:XX:XX:XX:XX:XX) - auto-generated if not specified' },
vlan: { type: 'number', description: 'VLAN tag (1-4094)' },
firewall: { type: 'boolean', description: 'Enable firewall on this interface' }
},
required: ['node', 'vmid', 'net', 'bridge']
}
},
{
name: 'proxmox_add_network_lxc',
description: 'Add network interface to LXC container (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container is located' },
vmid: { type: 'string', description: 'Container ID number' },
net: { type: 'string', description: 'Network interface name (net0, net1, net2, etc.)' },
bridge: { type: 'string', description: 'Bridge name (e.g., vmbr0, vmbr1)' },
ip: { type: 'string', description: 'IP address (dhcp, 192.168.1.100/24, auto)' },
gw: { type: 'string', description: 'Gateway IP address' },
firewall: { type: 'boolean', description: 'Enable firewall on this interface' }
},
required: ['node', 'vmid', 'net', 'bridge']
}
},
{
name: 'proxmox_update_network_vm',
description: 'Update/modify VM network interface configuration (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' },
net: { type: 'string', description: 'Network interface name to update (net0, net1, net2, etc.)' },
bridge: { type: 'string', description: 'Bridge name (e.g., vmbr0, vmbr1)' },
model: { type: 'string', description: 'Network model (virtio, e1000, rtl8139, vmxnet3)' },
macaddr: { type: 'string', description: 'MAC address (XX:XX:XX:XX:XX:XX)' },
vlan: { type: 'number', description: 'VLAN tag (1-4094)' },
firewall: { type: 'boolean', description: 'Enable firewall on this interface' }
},
required: ['node', 'vmid', 'net']
}
},
{
name: 'proxmox_update_network_lxc',
description: 'Update/modify LXC network interface configuration (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container is located' },
vmid: { type: 'string', description: 'Container ID number' },
net: { type: 'string', description: 'Network interface name to update (net0, net1, net2, etc.)' },
bridge: { type: 'string', description: 'Bridge name (e.g., vmbr0, vmbr1)' },
ip: { type: 'string', description: 'IP address (dhcp, 192.168.1.100/24, auto)' },
gw: { type: 'string', description: 'Gateway IP address' },
firewall: { type: 'boolean', description: 'Enable firewall on this interface' }
},
required: ['node', 'vmid', 'net']
}
},
{
name: 'proxmox_remove_network_vm',
description: 'Remove network interface from QEMU VM (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where VM is located' },
vmid: { type: 'string', description: 'VM ID number' },
net: { type: 'string', description: 'Network interface name to remove (net0, net1, net2, etc.)' }
},
required: ['node', 'vmid', 'net']
}
},
{
name: 'proxmox_remove_network_lxc',
description: 'Remove network interface from LXC container (requires elevated permissions)',
inputSchema: {
type: 'object',
properties: {
node: { type: 'string', description: 'Node name where container is located' },
vmid: { type: 'string', description: 'Container ID number' },
net: { type: 'string', description: 'Network interface name to remove (net0, net1, net2, etc.)' }
},
required: ['node', 'vmid', 'net']
}
}
]
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'proxmox_get_nodes':
return await this.getNodes();
case 'proxmox_get_node_status':
return await this.getNodeStatus(args.node);
case 'proxmox_get_vms':
return await this.getVMs(args.node, args.type);
case 'proxmox_get_vm_status':
return await this.getVMStatus(args.node, args.vmid, args.type);
case 'proxmox_execute_vm_command':
return await this.executeVMCommand(args.node, args.vmid, args.command, args.type);
case 'proxmox_get_storage':
return await this.getStorage(args.node);
case 'proxmox_get_cluster_status':
return await this.getClusterStatus();
case 'proxmox_list_templates':
return await this.listTemplates(args.node, args.storage);
case 'proxmox_create_lxc':
return await this.createLXCContainer(args);
case 'proxmox_create_vm':
return await this.createVM(args);
case 'proxmox_get_next_vmid':
return await this.getNextVMID();
case 'proxmox_start_lxc':
return await this.startVM(args.node, args.vmid, 'lxc');
case 'proxmox_start_vm':
return await this.startVM(args.node, args.vmid, 'qemu');
case 'proxmox_stop_lxc':
return await this.stopVM(args.node, args.vmid, 'lxc');
case 'proxmox_stop_vm':
return await this.stopVM(args.node, args.vmid, 'qemu');
case 'proxmox_delete_lxc':
return await this.deleteVM(args.node, args.vmid, 'lxc');
case 'proxmox_delete_vm':
return await this.deleteVM(args.node, args.vmid, 'qemu');
case 'proxmox_reboot_lxc':
return await this.rebootVM(args.node, args.vmid, 'lxc');
case 'proxmox_reboot_vm':
return await this.rebootVM(args.node, args.vmid, 'qemu');
case 'proxmox_shutdown_lxc':
return await this.shutdownVM(args.node, args.vmid, 'lxc');
case 'proxmox_shutdown_vm':
return await this.shutdownVM(args.node, args.vmid, 'qemu');
case 'proxmox_pause_vm':
return await this.pauseVM(args.node, args.vmid);
case 'proxmox_resume_vm':
return await this.resumeVM(args.node, args.vmid);
case 'proxmox_clone_lxc':
return await this.cloneVM(args.node, args.vmid, args.newid, args.hostname, 'lxc');
case 'proxmox_clone_vm':
return await this.cloneVM(args.node, args.vmid, args.newid, args.name, 'qemu');
case 'proxmox_resize_lxc':
return await this.resizeVM(args.node, args.vmid, args.memory, args.cores, 'lxc');
case 'proxmox_resize_vm':
return await this.resizeVM(args.node, args.vmid, args.memory, args.cores, 'qemu');
case 'proxmox_create_snapshot_lxc':
return await this.createSnapshot(args.node, args.vmid, args.snapname, 'lxc');
case 'proxmox_create_snapshot_vm':
return await this.createSnapshot(args.node, args.vmid, args.snapname, 'qemu');
case 'proxmox_list_snapshots_lxc':
return await this.listSnapshots(args.node, args.vmid, 'lxc');
case 'proxmox_list_snapshots_vm':
return await this.listSnapshots(args.node, args.vmid, 'qemu');
case 'proxmox_rollback_snapshot_lxc':
return await this.rollbackSnapshot(args.node, args.vmid, args.snapname, 'lxc');
case 'proxmox_rollback_snapshot_vm':
return await this.rollbackSnapshot(args.node, args.vmid, args.snapname, 'qemu');
case 'proxmox_delete_snapshot_lxc':
return await this.deleteSnapshot(args.node, args.vmid, args.snapname, 'lxc');
case 'proxmox_delete_snapshot_vm':
return await this.deleteSnapshot(args.node, args.vmid, args.snapname, 'qemu');
case 'proxmox_create_backup_lxc':
return await this.createBackup(args.node, args.vmid, args.storage, args.mode, args.compress, 'lxc');
case 'proxmox_create_backup_vm':
return await this.createBackup(args.node, args.vmid, args.storage, args.mode, args.compress, 'qemu');
case 'proxmox_list_backups':
return await this.listBackups(args.node, args.storage);
case 'proxmox_restore_backup_lxc':
return await this.restoreBackup(args.node, args.vmid, args.archive, args.storage, 'lxc');
case 'proxmox_restore_backup_vm':
return await this.restoreBackup(args.node, args.vmid, args.archive, args.storage, 'qemu');
case 'proxmox_delete_backup':
return await this.deleteBackup(args.node, args.storage, args.volume);
case 'proxmox_add_disk_vm':
return await this.addDiskVM(args.node, args.vmid, args.disk, args.storage, args.size);
case 'proxmox_add_mountpoint_lxc':
return await this.addMountPointLXC(args.node, args.vmid, args.mp, args.storage, args.size);
case 'proxmox_resize_disk_vm':
return await this.resizeDiskVM(args.node, args.vmid, args.disk, args.size);
case 'proxmox_resize_disk_lxc':
return await this.resizeDiskLXC(args.node, args.vmid, args.disk, args.size);
case 'proxmox_remove_disk_vm':
return await this.removeDiskVM(args.node, args.vmid, args.disk);
case 'proxmox_remove_mountpoint_lxc':
return await this.removeMountPointLXC(args.node, args.vmid, args.mp);
case 'proxmox_move_disk_vm':
return await this.moveDiskVM(args.node, args.vmid, args.disk, args.storage, args.delete);
case 'proxmox_move_disk_lxc':
return await this.moveDiskLXC(args.node, args.vmid, args.disk, args.storage, args.delete);
case 'proxmox_add_network_vm':
return await this.addNetworkVM(args.node, args.vmid, args.net, args.bridge, args.model, args.macaddr, args.vlan, args.firewall);
case 'proxmox_add_network_lxc':
return await this.addNetworkLXC(args.node, args.vmid, args.net, args.bridge, args.ip, args.gw, args.firewall);
case 'proxmox_update_network_vm':
return await this.updateNetworkVM(args.node, args.vmid, args.net, args.bridge, args.model, args.macaddr, args.vlan, args.firewall);
case 'proxmox_update_network_lxc':
return await this.updateNetworkLXC(args.node, args.vmid, args.net, args.bridge, args.ip, args.gw, args.firewall);
case 'proxmox_remove_network_vm':
return await this.removeNetworkVM(args.node, args.vmid, args.net);
case 'proxmox_remove_network_lxc':
return await this.removeNetworkLXC(args.node, args.vmid, args.net);
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error.message}`
}
]
};
}
});
}
async getNodes() {
const nodes = await this.proxmoxRequest('/nodes');
let output = '🖥️ **Proxmox Cluster Nodes**\n\n';
for (const node of nodes) {
const status = node.status === 'online' ? '🟢' : '🔴';
const uptime = node.uptime ? this.formatUptime(node.uptime) : 'N/A';
const cpuUsage = node.cpu ? `${(node.cpu * 100).toFixed(1)}%` : 'N/A';
const memUsage = node.mem && node.maxmem ?
`${this.formatBytes(node.mem)} / ${this.formatBytes(node.maxmem)} (${((node.mem / node.maxmem) * 100).toFixed(1)}%)` : 'N/A';
output += `${status} **${node.node}**\n`;
output += ` • Status: ${node.status}\n`;
output += ` • Uptime: ${uptime}\n`;
output += ` • CPU: ${cpuUsage}\n`;
output += ` • Memory: ${memUsage}\n`;
output += ` • Load: ${node.loadavg?.[0]?.toFixed(2) || 'N/A'}\n\n`;
}
return {
content: [{ type: 'text', text: output }]
};
}
async getNodeStatus(node) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Node Status Requires Elevated Permissions**\n\nTo view detailed node status, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file and ensure your API token has Sys.Audit permissions.\n\n**Current permissions**: Basic (node listing only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const status = await this.proxmoxRequest(`/nodes/${safeNode}/status`);
let output = `🖥️ **Node ${safeNode} Status**\n\n`;
output += `• **Status**: ${status.uptime ? '🟢 Online' : '🔴 Offline'}\n`;
output += `• **Uptime**: ${status.uptime ? this.formatUptime(status.uptime) : 'N/A'}\n`;
output += `• **Load Average**: ${status.loadavg?.join(', ') || 'N/A'}\n`;
output += `• **CPU Usage**: ${status.cpu ? `${(status.cpu * 100).toFixed(1)}%` : 'N/A'}\n`;
output += `• **Memory**: ${status.memory ?
`${this.formatBytes(status.memory.used)} / ${this.formatBytes(status.memory.total)} (${((status.memory.used / status.memory.total) * 100).toFixed(1)}%)` : 'N/A'}\n`;
output += `• **Root Disk**: ${status.rootfs ?
`${this.formatBytes(status.rootfs.used)} / ${this.formatBytes(status.rootfs.total)} (${((status.rootfs.used / status.rootfs.total) * 100).toFixed(1)}%)` : 'N/A'}\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to get node status**\n\nError: ${error.message}`
}]
};
}
}
async getVMs(nodeFilter = null, typeFilter = 'all') {
let vms = [];
if (nodeFilter) {
const nodeVMs = await this.proxmoxRequest(`/nodes/${nodeFilter}/qemu`);
const nodeLXCs = await this.proxmoxRequest(`/nodes/${nodeFilter}/lxc`);
if (typeFilter === 'all' || typeFilter === 'qemu') {
vms.push(...nodeVMs.map(vm => ({ ...vm, type: 'qemu', node: nodeFilter })));
}
if (typeFilter === 'all' || typeFilter === 'lxc') {
vms.push(...nodeLXCs.map(vm => ({ ...vm, type: 'lxc', node: nodeFilter })));
}
} else {
const nodes = await this.proxmoxRequest('/nodes');
for (const node of nodes) {
if (typeFilter === 'all' || typeFilter === 'qemu') {
const nodeVMs = await this.proxmoxRequest(`/nodes/${node.node}/qemu`);
vms.push(...nodeVMs.map(vm => ({ ...vm, type: 'qemu', node: node.node })));
}
if (typeFilter === 'all' || typeFilter === 'lxc') {
const nodeLXCs = await this.proxmoxRequest(`/nodes/${node.node}/lxc`);
vms.push(...nodeLXCs.map(vm => ({ ...vm, type: 'lxc', node: vm.node || node.node })));
}
}
}
let output = '💻 **Virtual Machines**\n\n';
if (vms.length === 0) {
output += 'No virtual machines found.\n';
} else {
for (const vm of vms.sort((a, b) => parseInt(a.vmid) - parseInt(b.vmid))) {
const status = vm.status === 'running' ? '🟢' : vm.status === 'stopped' ? '🔴' : '🟡';
const typeIcon = vm.type === 'qemu' ? '🖥️' : '📦';
const uptime = vm.uptime ? this.formatUptime(vm.uptime) : 'N/A';
const cpuUsage = vm.cpu ? `${(vm.cpu * 100).toFixed(1)}%` : 'N/A';
const memUsage = vm.mem && vm.maxmem ?
`${this.formatBytes(vm.mem)} / ${this.formatBytes(vm.maxmem)}` : 'N/A';
output += `${status} ${typeIcon} **${vm.name || `VM-${vm.vmid}`}** (ID: ${vm.vmid})\n`;
output += ` • Node: ${vm.node}\n`;
output += ` • Status: ${vm.status}\n`;
output += ` • Type: ${vm.type.toUpperCase()}\n`;
if (vm.status === 'running') {
output += ` • Uptime: ${uptime}\n`;
output += ` • CPU: ${cpuUsage}\n`;
output += ` • Memory: ${memUsage}\n`;
}
output += '\n';
}
}
return {
content: [{ type: 'text', text: output }]
};
}
async getVMStatus(node, vmid, type = 'qemu') {
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const vmStatus = await this.proxmoxRequest(`/nodes/${safeNode}/${type}/${safeVMID}/status/current`);
const status = vmStatus.status === 'running' ? '🟢' : vmStatus.status === 'stopped' ? '🔴' : '🟡';
const typeIcon = type === 'qemu' ? '🖥️' : '📦';
let output = `${status} ${typeIcon} **${vmStatus.name || `VM-${safeVMID}`}** (ID: ${safeVMID})\n\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Status**: ${vmStatus.status}\n`;
output += `• **Type**: ${type.toUpperCase()}\n`;
if (vmStatus.status === 'running') {
output += `• **Uptime**: ${vmStatus.uptime ? this.formatUptime(vmStatus.uptime) : 'N/A'}\n`;
output += `• **CPU Usage**: ${vmStatus.cpu ? `${(vmStatus.cpu * 100).toFixed(1)}%` : 'N/A'}\n`;
output += `• **Memory**: ${vmStatus.mem && vmStatus.maxmem ?
`${this.formatBytes(vmStatus.mem)} / ${this.formatBytes(vmStatus.maxmem)} (${((vmStatus.mem / vmStatus.maxmem) * 100).toFixed(1)}%)` : 'N/A'}\n`;
output += `• **Disk Read**: ${vmStatus.diskread ? this.formatBytes(vmStatus.diskread) : 'N/A'}\n`;
output += `• **Disk Write**: ${vmStatus.diskwrite ? this.formatBytes(vmStatus.diskwrite) : 'N/A'}\n`;
output += `• **Network In**: ${vmStatus.netin ? this.formatBytes(vmStatus.netin) : 'N/A'}\n`;
output += `• **Network Out**: ${vmStatus.netout ? this.formatBytes(vmStatus.netout) : 'N/A'}\n`;
}
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{ type: 'text', text: `❌ Failed to get VM status: ${error.message}` }],
isError: true
};
}
}
async executeVMCommand(node, vmid, command, type = 'qemu') {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **VM Command Execution Requires Elevated Permissions**\n\nTo execute commands on VMs, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file and ensure your API token has appropriate VM permissions.\n\n**Current permissions**: Basic (VM listing only)\n**Requested command**: \`${command}\``
}]
};
}
try {
// Validate inputs to prevent injection attacks
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const safeCommand = this.validateCommand(command);
// For QEMU VMs, we need to use the guest agent
if (type === 'qemu') {
const result = await this.proxmoxRequest(`/nodes/${safeNode}/qemu/${safeVMID}/agent/exec`, 'POST', {
command: safeCommand
});
let output = `💻 **Command executed on VM ${safeVMID}**\n\n`;
output += `**Command**: \`${safeCommand}\`\n`;
output += `**Result**: Command submitted to guest agent\n`;
output += `**PID**: ${result.pid || 'N/A'}\n\n`;
output += `*Note: Use guest agent status to check command completion*`;
return {
content: [{ type: 'text', text: output }]
};
} else {
// For LXC containers, we can execute directly
const result = await this.proxmoxRequest(`/nodes/${safeNode}/lxc/${safeVMID}/exec`, 'POST', {
command: safeCommand
});
let output = `📦 **Command executed on LXC ${safeVMID}**\n\n`;
output += `**Command**: \`${safeCommand}\`\n`;
output += `**Output**:\n\`\`\`\n${result || 'Command executed successfully'}\n\`\`\``;
return {
content: [{ type: 'text', text: output }]
};
}
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to execute command on VM ${vmid}**\n\nError: ${error.message}\n\n*Note: Make sure the VM has guest agent installed and running*`
}]
};
}
}
async getStorage(nodeFilter = null) {
let storages = [];
if (nodeFilter) {
storages = await this.proxmoxRequest(`/nodes/${nodeFilter}/storage`);
storages = storages.map(storage => ({ ...storage, node: nodeFilter }));
} else {
const nodes = await this.proxmoxRequest('/nodes');
for (const node of nodes) {
const nodeStorages = await this.proxmoxRequest(`/nodes/${node.node}/storage`);
storages.push(...nodeStorages.map(storage => ({ ...storage, node: node.node })));
}
}
let output = '💾 **Storage Pools**\n\n';
if (storages.length === 0) {
output += 'No storage found.\n';
} else {
const uniqueStorages = [];
const seen = new Set();
for (const storage of storages) {
const key = `${storage.storage}-${storage.node}`;
if (!seen.has(key)) {
seen.add(key);
uniqueStorages.push(storage);
}
}
for (const storage of uniqueStorages.sort((a, b) => a.storage.localeCompare(b.storage))) {
const enabled = storage.enabled ? '🟢' : '🔴';
const usagePercent = storage.total && storage.used ?
((storage.used / storage.total) * 100).toFixed(1) : 'N/A';
output += `${enabled} **${storage.storage}**\n`;
output += ` • Node: ${storage.node}\n`;
output += ` • Type: ${storage.type || 'N/A'}\n`;
output += ` • Content: ${storage.content || 'N/A'}\n`;
if (storage.total && storage.used) {
output += ` • Usage: ${this.formatBytes(storage.used)} / ${this.formatBytes(storage.total)} (${usagePercent}%)\n`;
}
output += ` • Status: ${storage.enabled ? 'Enabled' : 'Disabled'}\n\n`;
}
}
return {
content: [{ type: 'text', text: output }]
};
}
async getClusterStatus() {
try {
const nodes = await this.proxmoxRequest('/nodes');
// Try to get cluster status, but fall back gracefully if permissions are insufficient
let clusterStatus = null;
if (this.allowElevated) {
try {
clusterStatus = await this.proxmoxRequest('/cluster/status');
} catch (error) {
// Ignore cluster status errors for elevated permissions
}
}
let output = '🏗️ **Proxmox Cluster Status**\n\n';
// Cluster overview
const onlineNodes = nodes.filter(n => n.status === 'online').length;
const totalNodes = nodes.length;
output += `**Cluster Health**: ${onlineNodes === totalNodes ? '🟢 Healthy' : '🟡 Warning'}\n`;
output += `**Nodes**: ${onlineNodes}/${totalNodes} online\n\n`;
if (this.allowElevated) {
// Resource summary (only available with elevated permissions)
let totalCpu = 0, usedCpu = 0;
let totalMem = 0, usedMem = 0;
for (const node of nodes) {
if (node.status === 'online') {
totalCpu += node.maxcpu || 0;
usedCpu += (node.cpu || 0) * (node.maxcpu || 0);
totalMem += node.maxmem || 0;
usedMem += node.mem || 0;
}
}
const cpuPercent = totalCpu > 0 ? ((usedCpu / totalCpu) * 100).toFixed(1) : 'N/A';
const memPercent = totalMem > 0 ? ((usedMem / totalMem) * 100).toFixed(1) : 'N/A';
output += `**Resource Usage**:\n`;
output += `• CPU: ${cpuPercent}% (${usedCpu.toFixed(1)}/${totalCpu} cores)\n`;
output += `• Memory: ${memPercent}% (${this.formatBytes(usedMem)}/${this.formatBytes(totalMem)})\n\n`;
} else {
output += `⚠️ **Limited Information**: Resource usage requires elevated permissions\n\n`;
}
// Node status
output += `**Node Details**:\n`;
for (const node of nodes.sort((a, b) => a.node.localeCompare(b.node))) {
const status = node.status === 'online' ? '🟢' : '🔴';
output += `${status} ${node.node} - ${node.status}\n`;
}
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to get cluster status**\n\nError: ${error.message}`
}]
};
}
}
async listTemplates(node, storage = 'local') {
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const templates = await this.proxmoxRequest(`/nodes/${safeNode}/storage/${storage}/content?content=vztmpl`);
let output = '📦 **Available LXC Templates**\n\n';
if (!templates || templates.length === 0) {
output += `No templates found on storage \`${storage}\`.\n\n`;
output += `**Tip**: Download templates in Proxmox:\n`;
output += `1. Go to your node → Storage → ${storage}\n`;
output += `2. Click "CT Templates"\n`;
output += `3. Download a template (e.g., Debian, Ubuntu)\n`;
} else {
for (const template of templates) {
const size = template.size ? this.formatBytes(template.size) : 'N/A';
output += `• **${template.volid}**\n`;
output += ` Size: ${size}\n\n`;
}
}
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to list templates**\n\nError: ${error.message}\n\n**Note**: Make sure the storage exists and contains LXC templates.`
}]
};
}
}
async createLXCContainer(args) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Container Creation Requires Elevated Permissions**\n\nTo create containers, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file and ensure your API token has VM.Allocate permissions.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(args.node);
const safeVMID = this.validateVMID(args.vmid);
// Generate secure password if not provided
const generatedPassword = args.password || this.generateSecurePassword();
const isPasswordGenerated = !args.password;
// Build the request body
const body = {
vmid: safeVMID,
ostemplate: args.ostemplate,
hostname: args.hostname || `ct${safeVMID}`,
password: generatedPassword,
memory: args.memory || 512,
storage: args.storage || 'local-lvm',
rootfs: `${args.storage || 'local-lvm'}:${args.rootfs || 8}`
};
// Make the API request
const result = await this.proxmoxRequest(`/nodes/${safeNode}/lxc`, 'POST', body);
let output = `✅ **LXC Container Creation Started**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Hostname**: ${body.hostname}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Template**: ${args.ostemplate}\n`;
output += `• **Memory**: ${body.memory} MB\n`;
output += `• **Storage**: ${body.storage}\n`;
if (isPasswordGenerated) {
output += `• **🔐 Generated Password**: \`${generatedPassword}\`\n`;
output += ` ⚠️ **SAVE THIS PASSWORD** - it will not be shown again!\n`;
}
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Next steps**:\n`;
output += `1. Wait a moment for container to be created\n`;
output += `2. Start it with \`proxmox_start_lxc\`\n`;
output += `3. View status with \`proxmox_get_vm_status\`\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to create container**\n\nError: ${error.message}\n\n**Common issues**:\n- VM ID already in use\n- Invalid template path\n- Insufficient permissions\n- Storage doesn't exist`
}]
};
}
}
async createVM(args) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **VM Creation Requires Elevated Permissions**\n\nTo create VMs, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file and ensure your API token has VM.Allocate permissions.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(args.node);
const safeVMID = this.validateVMID(args.vmid);
// Build the request body for QEMU VM creation
const body = {
vmid: safeVMID,
name: args.name || `vm${safeVMID}`,
memory: args.memory || 512,
cores: args.cores || 1,
sockets: args.sockets || 1,
ostype: args.ostype || 'l26',
net0: args.net0 || 'virtio,bridge=vmbr0'
};
// Add disk configuration
// Format: storage:size (size in GB, no suffix)
const storage = args.storage || 'local-lvm';
const diskSize = args.disk_size || '8G';
// Extract numeric value from disk size (e.g., "8G" -> "8")
const sizeValue = diskSize.replace(/[^0-9]/g, '');
body.scsi0 = `${storage}:${sizeValue}`;
// Add ISO if provided
if (args.iso) {
body.ide2 = `${args.iso},media=cdrom`;
body.boot = 'order=ide2;scsi0';
}
// Make the API request
const result = await this.proxmoxRequest(`/nodes/${safeNode}/qemu`, 'POST', body);
let output = `✅ **QEMU VM Creation Started**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Name**: ${body.name}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Memory**: ${body.memory} MB\n`;
output += `• **CPU**: ${body.sockets} socket(s), ${body.cores} core(s)\n`;
output += `• **Disk**: ${body.scsi0}\n`;
output += `• **Network**: ${body.net0}\n`;
if (args.iso) {
output += `• **ISO**: ${args.iso}\n`;
}
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Next steps**:\n`;
output += `1. Wait a moment for VM to be created\n`;
output += `2. Start it with \`proxmox_start_vm\`\n`;
output += `3. View status with \`proxmox_get_vm_status\`\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to create VM**\n\nError: ${error.message}\n\n**Common issues**:\n- VM ID already in use\n- Invalid ISO path\n- Insufficient permissions\n- Storage doesn't exist`
}]
};
}
}
async startVM(node, vmid, type = 'lxc') {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **VM Control Requires Elevated Permissions**\n\nTo start/stop VMs, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const result = await this.proxmoxRequest(`/nodes/${safeNode}/${type}/${safeVMID}/status/start`, 'POST', {});
let output = `▶️ **VM/Container Start Command Sent**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Type**: ${type.toUpperCase()}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Tip**: Use \`proxmox_get_vm_status\` to check if it's running.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to start VM/Container**\n\nError: ${error.message}`
}]
};
}
}
async stopVM(node, vmid, type = 'lxc') {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **VM Control Requires Elevated Permissions**\n\nTo start/stop VMs, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const result = await this.proxmoxRequest(`/nodes/${safeNode}/${type}/${safeVMID}/status/stop`, 'POST', {});
let output = `⏹️ **VM/Container Stop Command Sent**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Type**: ${type.toUpperCase()}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Tip**: Use \`proxmox_get_vm_status\` to confirm it's stopped.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to stop VM/Container**\n\nError: ${error.message}`
}]
};
}
}
async getNextVMID() {
try {
const result = await this.proxmoxRequest('/cluster/nextid');
return {
content: [{ type: 'text', text: `**Next Available VM/Container ID**: ${result}` }]
};
} catch (error) {
return {
content: [{ type: 'text', text: `❌ **Failed to get next VMID**\n\nError: ${error.message}` }]
};
}
}
async deleteVM(node, vmid, type = 'lxc') {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **VM/Container Deletion Requires Elevated Permissions**\n\nTo delete VMs/containers, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const result = await this.proxmoxRequest(`/nodes/${safeNode}/${type}/${safeVMID}`, 'DELETE');
let output = `🗑️ **VM/Container Deletion Started**\n\n`;
output += `• **VM/Container ID**: ${safeVMID}\n`;
output += `• **Type**: ${type.toUpperCase()}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Note**: Deletion may take a moment to complete.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to delete VM/Container**\n\nError: ${error.message}\n\n**Note**: Make sure the VM/container is stopped first.`
}]
};
}
}
async rebootVM(node, vmid, type = 'lxc') {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **VM Reboot Requires Elevated Permissions**\n\nTo reboot VMs/containers, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const result = await this.proxmoxRequest(`/nodes/${safeNode}/${type}/${safeVMID}/status/reboot`, 'POST', {});
let output = `🔄 **VM/Container Reboot Command Sent**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Type**: ${type.toUpperCase()}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Tip**: The VM/container will restart momentarily.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to reboot VM/Container**\n\nError: ${error.message}`
}]
};
}
}
async shutdownVM(node, vmid, type = 'lxc') {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **VM Shutdown Requires Elevated Permissions**\n\nTo shutdown VMs/containers, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const result = await this.proxmoxRequest(`/nodes/${safeNode}/${type}/${safeVMID}/status/shutdown`, 'POST', {});
let output = `⏸️ **VM/Container Shutdown Command Sent**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Type**: ${type.toUpperCase()}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Note**: This is a graceful shutdown. Use \`proxmox_stop_vm\` for forceful stop.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to shutdown VM/Container**\n\nError: ${error.message}`
}]
};
}
}
async pauseVM(node, vmid) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **VM Pause Requires Elevated Permissions**\n\nTo pause VMs, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const result = await this.proxmoxRequest(`/nodes/${safeNode}/qemu/${safeVMID}/status/suspend`, 'POST', {});
let output = `⏸️ **VM Pause Command Sent**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Type**: QEMU\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Note**: VM is now paused. Use \`proxmox_resume_vm\` to resume.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to pause VM**\n\nError: ${error.message}\n\n**Note**: Pause is only available for QEMU VMs, not LXC containers.`
}]
};
}
}
async resumeVM(node, vmid) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **VM Resume Requires Elevated Permissions**\n\nTo resume VMs, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const result = await this.proxmoxRequest(`/nodes/${safeNode}/qemu/${safeVMID}/status/resume`, 'POST', {});
let output = `▶️ **VM Resume Command Sent**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Type**: QEMU\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Note**: VM is now resuming from paused state.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to resume VM**\n\nError: ${error.message}\n\n**Note**: Resume is only available for QEMU VMs, not LXC containers.`
}]
};
}
}
async cloneVM(node, vmid, newid, nameOrHostname, type = 'lxc') {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **VM Clone Requires Elevated Permissions**\n\nTo clone VMs/containers, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const safeNewID = this.validateVMID(newid);
const body = {
newid: safeNewID
};
// For LXC, use 'hostname', for QEMU use 'name'
if (type === 'lxc') {
body.hostname = nameOrHostname || `clone-${safeNewID}`;
} else {
body.name = nameOrHostname || `clone-${safeNewID}`;
}
const result = await this.proxmoxRequest(`/nodes/${safeNode}/${type}/${safeVMID}/clone`, 'POST', body);
let output = `📋 **VM/Container Clone Started**\n\n`;
output += `• **Source VM ID**: ${safeVMID}\n`;
output += `• **New VM ID**: ${safeNewID}\n`;
output += `• **New Name**: ${nameOrHostname || `clone-${safeNewID}`}\n`;
output += `• **Type**: ${type.toUpperCase()}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Note**: Clone operation may take several minutes. Check task status in Proxmox.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to clone VM/Container**\n\nError: ${error.message}\n\n**Common issues**:\n- New VM ID already in use\n- Insufficient storage space\n- Source VM is running (some storage types require stopped VM)`
}]
};
}
}
async resizeVM(node, vmid, memory, cores, type = 'lxc') {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **VM Resize Requires Elevated Permissions**\n\nTo resize VMs/containers, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
// Build body with only provided parameters
const body = {};
if (memory !== undefined) {
body.memory = memory;
}
if (cores !== undefined) {
body.cores = cores;
}
if (Object.keys(body).length === 0) {
return {
content: [{
type: 'text',
text: `⚠️ **No Resize Parameters Provided**\n\nPlease specify at least one parameter:\n- \`memory\`: Memory in MB\n- \`cores\`: Number of CPU cores`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const result = await this.proxmoxRequest(`/nodes/${safeNode}/${type}/${safeVMID}/config`, 'PUT', body);
let output = `📏 **VM/Container Resize Command Sent**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Type**: ${type.toUpperCase()}\n`;
output += `• **Node**: ${safeNode}\n`;
if (memory !== undefined) {
output += `• **New Memory**: ${memory} MB\n`;
}
if (cores !== undefined) {
output += `• **New Cores**: ${cores}\n`;
}
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Note**: Some changes may require a reboot to take effect.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to resize VM/Container**\n\nError: ${error.message}\n\n**Common issues**:\n- Memory/CPU values exceed node capacity\n- VM is locked or in use\n- Invalid parameter values`
}]
};
}
}
async createSnapshot(node, vmid, snapname, type = 'lxc') {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Snapshot Creation Requires Elevated Permissions**\n\nTo create snapshots, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const result = await this.proxmoxRequest(`/nodes/${safeNode}/${type}/${safeVMID}/snapshot`, 'POST', {
snapname: snapname
});
let output = `📸 **Snapshot Creation Started**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Snapshot Name**: ${snapname}\n`;
output += `• **Type**: ${type.toUpperCase()}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Tip**: Use \`proxmox_list_snapshots_${type === 'lxc' ? 'lxc' : 'vm'}\` to view all snapshots.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to create snapshot**\n\nError: ${error.message}\n\n**Common issues**:\n- Snapshot name already exists\n- Insufficient disk space\n- VM is locked or in use`
}]
};
}
}
async listSnapshots(node, vmid, type = 'lxc') {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Snapshot Listing Requires Elevated Permissions**\n\nTo list snapshots, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const snapshots = await this.proxmoxRequest(`/nodes/${safeNode}/${type}/${safeVMID}/snapshot`);
let output = `📋 **Snapshots for ${type.toUpperCase()} ${safeVMID}**\n\n`;
if (!snapshots || snapshots.length === 0) {
output += `No snapshots found.\n\n`;
output += `**Tip**: Create a snapshot with \`proxmox_create_snapshot_${type === 'lxc' ? 'lxc' : 'vm'}\`.\n`;
} else {
// Filter out 'current' pseudo-snapshot that Proxmox includes
const realSnapshots = snapshots.filter(snap => snap.name !== 'current');
if (realSnapshots.length === 0) {
output += `No snapshots found.\n\n`;
output += `**Tip**: Create a snapshot with \`proxmox_create_snapshot_${type === 'lxc' ? 'lxc' : 'vm'}\`.\n`;
} else {
for (const snapshot of realSnapshots) {
output += `• **${snapshot.name}**\n`;
if (snapshot.description) {
output += ` Description: ${snapshot.description}\n`;
}
if (snapshot.snaptime) {
const date = new Date(snapshot.snaptime * 1000);
output += ` Created: ${date.toLocaleString()}\n`;
}
output += `\n`;
}
output += `**Total**: ${realSnapshots.length} snapshot(s)\n`;
}
}
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to list snapshots**\n\nError: ${error.message}`
}]
};
}
}
async rollbackSnapshot(node, vmid, snapname, type = 'lxc') {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Snapshot Rollback Requires Elevated Permissions**\n\nTo rollback snapshots, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const result = await this.proxmoxRequest(`/nodes/${safeNode}/${type}/${safeVMID}/snapshot/${snapname}/rollback`, 'POST', {});
let output = `⏮️ **Snapshot Rollback Started**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Snapshot Name**: ${snapname}\n`;
output += `• **Type**: ${type.toUpperCase()}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Warning**: This will restore the VM/container to the state of the snapshot.\n`;
output += `**Tip**: Any changes made after the snapshot was created will be lost.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to rollback snapshot**\n\nError: ${error.message}\n\n**Common issues**:\n- Snapshot doesn't exist\n- VM is running (stop it first)\n- VM is locked or in use`
}]
};
}
}
async deleteSnapshot(node, vmid, snapname, type = 'lxc') {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Snapshot Deletion Requires Elevated Permissions**\n\nTo delete snapshots, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const result = await this.proxmoxRequest(`/nodes/${safeNode}/${type}/${safeVMID}/snapshot/${snapname}`, 'DELETE');
let output = `🗑️ **Snapshot Deletion Started**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Snapshot Name**: ${snapname}\n`;
output += `• **Type**: ${type.toUpperCase()}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Note**: Snapshot deletion may take a moment to complete.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to delete snapshot**\n\nError: ${error.message}\n\n**Common issues**:\n- Snapshot doesn't exist\n- VM is locked or in use\n- Insufficient permissions`
}]
};
}
}
async createBackup(node, vmid, storage = 'local', mode = 'snapshot', compress = 'zstd', type = 'lxc') {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Backup Creation Requires Elevated Permissions**\n\nTo create backups, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const result = await this.proxmoxRequest(`/nodes/${safeNode}/vzdump`, 'POST', {
vmid: safeVMID,
storage: storage,
mode: mode,
compress: compress
});
let output = `💾 **Backup Creation Started**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Type**: ${type.toUpperCase()}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Storage**: ${storage}\n`;
output += `• **Mode**: ${mode}\n`;
output += `• **Compression**: ${compress}\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Tip**: Backup runs in the background. Use \`proxmox_list_backups\` to view all backups.\n`;
output += `**Note**: Backup modes:\n`;
output += ` - snapshot: Quick backup using snapshots (recommended)\n`;
output += ` - suspend: Suspends VM during backup\n`;
output += ` - stop: Stops VM during backup\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to create backup**\n\nError: ${error.message}\n\n**Common issues**:\n- Insufficient disk space on storage\n- VM is locked or in use\n- Invalid storage name\n- Insufficient permissions`
}]
};
}
}
async listBackups(node, storage = 'local') {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Backup Listing Requires Elevated Permissions**\n\nTo list backups, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const backups = await this.proxmoxRequest(`/nodes/${safeNode}/storage/${storage}/content?content=backup`);
let output = `📦 **Backups on ${storage}**\n\n`;
if (!backups || backups.length === 0) {
output += `No backups found on storage \`${storage}\`.\n\n`;
output += `**Tip**: Create a backup with \`proxmox_create_backup_lxc\` or \`proxmox_create_backup_vm\`.\n`;
} else {
// Sort by creation time (newest first)
backups.sort((a, b) => (b.ctime || 0) - (a.ctime || 0));
for (const backup of backups) {
// Parse backup filename to extract VM type and ID
const filename = backup.volid.split('/').pop();
const match = filename.match(/vzdump-(lxc|qemu)-(\d+)-/);
const vmType = match ? match[1].toUpperCase() : 'UNKNOWN';
const vmId = match ? match[2] : 'N/A';
output += `• **${filename}**\n`;
output += ` VM ID: ${vmId} (${vmType})\n`;
output += ` Size: ${backup.size ? this.formatBytes(backup.size) : 'N/A'}\n`;
if (backup.ctime) {
const date = new Date(backup.ctime * 1000);
output += ` Created: ${date.toLocaleString()}\n`;
}
output += ` Volume: ${backup.volid}\n`;
output += `\n`;
}
output += `**Total**: ${backups.length} backup(s)\n`;
}
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to list backups**\n\nError: ${error.message}\n\n**Common issues**:\n- Storage doesn't exist\n- Storage is not accessible\n- Insufficient permissions`
}]
};
}
}
async restoreBackup(node, vmid, archive, storage, type = 'lxc') {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Backup Restore Requires Elevated Permissions**\n\nTo restore backups, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const body = {
vmid: safeVMID,
archive: archive,
restore: 1
};
if (storage) {
body.storage = storage;
}
const result = await this.proxmoxRequest(`/nodes/${safeNode}/${type}`, 'POST', body);
let output = `♻️ **Backup Restore Started**\n\n`;
output += `• **New VM ID**: ${safeVMID}\n`;
output += `• **Type**: ${type.toUpperCase()}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Archive**: ${archive}\n`;
if (storage) {
output += `• **Storage**: ${storage}\n`;
}
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Note**: Restore operation may take several minutes depending on backup size.\n`;
output += `**Tip**: Use \`proxmox_get_vm_status\` to check the restored VM status after completion.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to restore backup**\n\nError: ${error.message}\n\n**Common issues**:\n- VM ID already in use\n- Backup archive doesn't exist\n- Insufficient storage space\n- Invalid archive path\n- Insufficient permissions`
}]
};
}
}
async deleteBackup(node, storage, volume) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Backup Deletion Requires Elevated Permissions**\n\nTo delete backups, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const encodedVolume = encodeURIComponent(volume);
const result = await this.proxmoxRequest(`/nodes/${safeNode}/storage/${storage}/content/${encodedVolume}`, 'DELETE');
let output = `🗑️ **Backup Deletion Started**\n\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Storage**: ${storage}\n`;
output += `• **Volume**: ${volume}\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Note**: Backup file will be permanently deleted from storage.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to delete backup**\n\nError: ${error.message}\n\n**Common issues**:\n- Backup doesn't exist\n- Invalid volume path\n- Backup is in use\n- Insufficient permissions`
}]
};
}
}
async addDiskVM(node, vmid, disk, storage, size) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Disk Management Requires Elevated Permissions**\n\nTo add disks to VMs, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const body = {
[disk]: `${storage}:${size}`
};
const result = await this.proxmoxRequest(`/nodes/${safeNode}/qemu/${safeVMID}/config`, 'PUT', body);
let output = `💿 **VM Disk Addition Started**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Disk**: ${disk}\n`;
output += `• **Storage**: ${storage}\n`;
output += `• **Size**: ${size} GB\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Disk naming conventions**:\n`;
output += ` - SCSI: scsi0-15\n`;
output += ` - VirtIO: virtio0-15\n`;
output += ` - SATA: sata0-5\n`;
output += ` - IDE: ide0-3\n\n`;
output += `**Note**: The VM may need to be stopped for this operation depending on configuration.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to add disk to VM**\n\nError: ${error.message}\n\n**Common issues**:\n- Disk name already in use\n- VM is running (may need to be stopped)\n- Invalid disk name format\n- Insufficient storage space\n- Storage doesn't exist`
}]
};
}
}
async addMountPointLXC(node, vmid, mp, storage, size) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Disk Management Requires Elevated Permissions**\n\nTo add mount points to containers, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const body = {
[mp]: `${storage}:${size}`
};
const result = await this.proxmoxRequest(`/nodes/${safeNode}/lxc/${safeVMID}/config`, 'PUT', body);
let output = `💿 **LXC Mount Point Addition Started**\n\n`;
output += `• **Container ID**: ${safeVMID}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Mount Point**: ${mp}\n`;
output += `• **Storage**: ${storage}\n`;
output += `• **Size**: ${size} GB\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Mount point naming**: mp0-255\n\n`;
output += `**Note**: The container may need to be stopped for this operation.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to add mount point to container**\n\nError: ${error.message}\n\n**Common issues**:\n- Mount point name already in use\n- Container is running (may need to be stopped)\n- Invalid mount point name\n- Insufficient storage space\n- Storage doesn't exist`
}]
};
}
}
async resizeDiskVM(node, vmid, disk, size) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Disk Management Requires Elevated Permissions**\n\nTo resize VM disks, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const body = {
disk: disk,
size: size
};
const result = await this.proxmoxRequest(`/nodes/${safeNode}/qemu/${safeVMID}/resize`, 'PUT', body);
let output = `📏 **VM Disk Resize Started**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Disk**: ${disk}\n`;
output += `• **New Size**: ${size}\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Size format examples**:\n`;
output += ` - +10G: Add 10GB to current size\n`;
output += ` - 50G: Set absolute size to 50GB\n\n`;
output += `**Note**: Disks can only be expanded, not shrunk. Some configurations allow online resizing.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to resize VM disk**\n\nError: ${error.message}\n\n**Common issues**:\n- Disk doesn't exist\n- Trying to shrink disk (not supported)\n- Insufficient storage space\n- Invalid size format\n- VM is locked or in use`
}]
};
}
}
async resizeDiskLXC(node, vmid, disk, size) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Disk Management Requires Elevated Permissions**\n\nTo resize LXC disks, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const body = {
disk: disk,
size: size
};
const result = await this.proxmoxRequest(`/nodes/${safeNode}/lxc/${safeVMID}/resize`, 'PUT', body);
let output = `📏 **LXC Disk Resize Started**\n\n`;
output += `• **Container ID**: ${safeVMID}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Disk**: ${disk}\n`;
output += `• **New Size**: ${size}\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Size format examples**:\n`;
output += ` - +10G: Add 10GB to current size\n`;
output += ` - 50G: Set absolute size to 50GB\n\n`;
output += `**Valid disk names**: rootfs, mp0, mp1, mp2, etc.\n\n`;
output += `**Note**: Disks can only be expanded, not shrunk. Container may need to be stopped.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to resize LXC disk**\n\nError: ${error.message}\n\n**Common issues**:\n- Disk doesn't exist\n- Trying to shrink disk (not supported)\n- Insufficient storage space\n- Invalid size format\n- Container is locked or in use`
}]
};
}
}
async removeDiskVM(node, vmid, disk) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Disk Management Requires Elevated Permissions**\n\nTo remove disks from VMs, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const body = {
delete: disk
};
const result = await this.proxmoxRequest(`/nodes/${safeNode}/qemu/${safeVMID}/config`, 'PUT', body);
let output = ` **VM Disk Removal Started**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Disk**: ${disk}\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Warning**: This will permanently delete the disk and all its data.\n`;
output += `**Note**: The VM should be stopped for this operation.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to remove disk from VM**\n\nError: ${error.message}\n\n**Common issues**:\n- Disk doesn't exist\n- VM is running (must be stopped)\n- Cannot remove boot disk\n- VM is locked or in use`
}]
};
}
}
async removeMountPointLXC(node, vmid, mp) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Disk Management Requires Elevated Permissions**\n\nTo remove mount points from containers, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const body = {
delete: mp
};
const result = await this.proxmoxRequest(`/nodes/${safeNode}/lxc/${safeVMID}/config`, 'PUT', body);
let output = ` **LXC Mount Point Removal Started**\n\n`;
output += `• **Container ID**: ${safeVMID}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Mount Point**: ${mp}\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Warning**: This will permanently delete the mount point and all its data.\n`;
output += `**Note**: The container should be stopped for this operation.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to remove mount point from container**\n\nError: ${error.message}\n\n**Common issues**:\n- Mount point doesn't exist\n- Container is running (must be stopped)\n- Cannot remove rootfs\n- Container is locked or in use`
}]
};
}
}
async moveDiskVM(node, vmid, disk, storage, deleteSource = true) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Disk Management Requires Elevated Permissions**\n\nTo move VM disks, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const body = {
disk: disk,
storage: storage,
delete: deleteSource ? 1 : 0
};
const result = await this.proxmoxRequest(`/nodes/${safeNode}/qemu/${safeVMID}/move_disk`, 'POST', body);
let output = `📦 **VM Disk Move Started**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Disk**: ${disk}\n`;
output += `• **Target Storage**: ${storage}\n`;
output += `• **Delete Source**: ${deleteSource ? 'Yes' : 'No'}\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Note**: Disk move operation may take several minutes depending on disk size.\n`;
output += `**Tip**: The VM should be stopped for this operation in most configurations.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to move VM disk**\n\nError: ${error.message}\n\n**Common issues**:\n- Disk doesn't exist\n- Target storage doesn't exist\n- Insufficient space on target storage\n- VM is running (may need to be stopped)\n- VM is locked or in use`
}]
};
}
}
async moveDiskLXC(node, vmid, disk, storage, deleteSource = true) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Disk Management Requires Elevated Permissions**\n\nTo move LXC disks, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const body = {
volume: disk,
storage: storage,
delete: deleteSource ? 1 : 0
};
const result = await this.proxmoxRequest(`/nodes/${safeNode}/lxc/${safeVMID}/move_volume`, 'POST', body);
let output = `📦 **LXC Disk Move Started**\n\n`;
output += `• **Container ID**: ${safeVMID}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Volume**: ${disk}\n`;
output += `• **Target Storage**: ${storage}\n`;
output += `• **Delete Source**: ${deleteSource ? 'Yes' : 'No'}\n`;
output += `• **Task ID**: ${result || 'N/A'}\n\n`;
output += `**Valid volumes**: rootfs, mp0, mp1, mp2, etc.\n\n`;
output += `**Note**: Volume move operation may take several minutes depending on volume size.\n`;
output += `**Tip**: The container should be stopped for this operation.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to move LXC volume**\n\nError: ${error.message}\n\n**Common issues**:\n- Volume doesn't exist\n- Target storage doesn't exist\n- Insufficient space on target storage\n- Container is running (may need to be stopped)\n- Container is locked or in use`
}]
};
}
}
async addNetworkVM(node, vmid, net, bridge, model = 'virtio', macaddr, vlan, firewall) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Network Management Requires Elevated Permissions**\n\nTo add VM network interfaces, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
// Build network configuration string
let netConfig = `${model || 'virtio'},bridge=${bridge}`;
if (macaddr) {
netConfig += `,macaddr=${macaddr}`;
}
if (vlan !== undefined && vlan !== null) {
netConfig += `,tag=${vlan}`;
}
if (firewall) {
netConfig += `,firewall=1`;
}
const body = {
[net]: netConfig
};
await this.proxmoxRequest(`/nodes/${safeNode}/qemu/${safeVMID}/config`, 'PUT', body);
let output = `🌐 **VM Network Interface Added**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Interface**: ${net}\n`;
output += `• **Bridge**: ${bridge}\n`;
output += `• **Model**: ${model || 'virtio'}\n`;
if (macaddr) output += `• **MAC Address**: ${macaddr}\n`;
if (vlan !== undefined && vlan !== null) output += `• **VLAN Tag**: ${vlan}\n`;
if (firewall) output += `• **Firewall**: Enabled\n`;
output += `\n**Valid interfaces**: net0, net1, net2, etc.\n`;
output += `**Valid models**: virtio (recommended), e1000, rtl8139, vmxnet3\n`;
output += `**Valid bridges**: vmbr0, vmbr1, vmbr2, etc.\n\n`;
output += `**Tip**: Use virtio model for best performance with modern guests.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to add VM network interface**\n\nError: ${error.message}\n\n**Common issues**:\n- Network interface already exists\n- Bridge doesn't exist\n- Invalid MAC address format\n- Invalid VLAN tag (must be 1-4094)\n- VM is locked or in use`
}]
};
}
}
async addNetworkLXC(node, vmid, net, bridge, ip, gw, firewall) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Network Management Requires Elevated Permissions**\n\nTo add LXC network interfaces, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
// Extract interface number (e.g., net0 -> 0, net1 -> 1)
const netNum = net.replace('net', '');
// Build network configuration string
let netConfig = `name=eth${netNum},bridge=${bridge}`;
if (ip) {
netConfig += `,ip=${ip}`;
}
if (gw) {
netConfig += `,gw=${gw}`;
}
if (firewall) {
netConfig += `,firewall=1`;
}
const body = {
[net]: netConfig
};
await this.proxmoxRequest(`/nodes/${safeNode}/lxc/${safeVMID}/config`, 'PUT', body);
let output = `🌐 **LXC Network Interface Added**\n\n`;
output += `• **Container ID**: ${safeVMID}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Interface**: ${net} (eth${netNum})\n`;
output += `• **Bridge**: ${bridge}\n`;
if (ip) output += `• **IP Address**: ${ip}\n`;
if (gw) output += `• **Gateway**: ${gw}\n`;
if (firewall) output += `• **Firewall**: Enabled\n`;
output += `\n**Valid interfaces**: net0, net1, net2, etc.\n`;
output += `**Valid bridges**: vmbr0, vmbr1, vmbr2, etc.\n`;
output += `**IP formats**: dhcp, 192.168.1.100/24, auto\n\n`;
output += `**Tip**: Use DHCP for automatic IP assignment or specify static IP with CIDR notation.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to add LXC network interface**\n\nError: ${error.message}\n\n**Common issues**:\n- Network interface already exists\n- Bridge doesn't exist\n- Invalid IP address format\n- Invalid gateway address\n- Container is locked or in use`
}]
};
}
}
async updateNetworkVM(node, vmid, net, bridge, model, macaddr, vlan, firewall) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Network Management Requires Elevated Permissions**\n\nTo update VM network interfaces, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
// Get current VM configuration
const config = await this.proxmoxRequest(`/nodes/${safeNode}/qemu/${safeVMID}/config`, 'GET');
if (!config[net]) {
return {
content: [{
type: 'text',
text: `❌ **Network interface ${net} does not exist**\n\nPlease add the interface first using proxmox_add_network_vm.\n\n**Existing interfaces**: ${Object.keys(config).filter(k => k.startsWith('net')).join(', ') || 'None'}`
}]
};
}
// Parse current configuration
const currentConfig = config[net];
const configParts = {};
currentConfig.split(',').forEach(part => {
const [key, value] = part.split('=');
configParts[key] = value;
});
// Update only provided parameters
if (model !== undefined) {
// Extract MAC if present, remove old model
const mac = configParts.macaddr || configParts[Object.keys(configParts).find(k => k.match(/^[0-9A-F]{2}:/i))];
configParts[model] = mac || '';
// Remove old model keys
['virtio', 'e1000', 'rtl8139', 'vmxnet3'].forEach(m => {
if (m !== model) delete configParts[m];
});
}
if (bridge !== undefined) {
configParts.bridge = bridge;
}
if (macaddr !== undefined) {
configParts.macaddr = macaddr;
}
if (vlan !== undefined && vlan !== null) {
configParts.tag = vlan;
} else if (vlan === null) {
delete configParts.tag;
}
if (firewall !== undefined) {
if (firewall) {
configParts.firewall = '1';
} else {
delete configParts.firewall;
}
}
// Rebuild configuration string
const netConfig = Object.entries(configParts)
.map(([key, value]) => value ? `${key}=${value}` : key)
.join(',');
const body = {
[net]: netConfig
};
await this.proxmoxRequest(`/nodes/${safeNode}/qemu/${safeVMID}/config`, 'PUT', body);
let output = `🔧 **VM Network Interface Updated**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Interface**: ${net}\n`;
output += `• **New Configuration**: ${netConfig}\n\n`;
output += `**Changes applied**:\n`;
if (bridge !== undefined) output += `- Bridge: ${bridge}\n`;
if (model !== undefined) output += `- Model: ${model}\n`;
if (macaddr !== undefined) output += `- MAC Address: ${macaddr}\n`;
if (vlan !== undefined) output += `- VLAN Tag: ${vlan !== null ? vlan : 'Removed'}\n`;
if (firewall !== undefined) output += `- Firewall: ${firewall ? 'Enabled' : 'Disabled'}\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to update VM network interface**\n\nError: ${error.message}\n\n**Common issues**:\n- Network interface doesn't exist\n- Bridge doesn't exist\n- Invalid MAC address format\n- Invalid VLAN tag (must be 1-4094)\n- VM is locked or in use`
}]
};
}
}
async updateNetworkLXC(node, vmid, net, bridge, ip, gw, firewall) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Network Management Requires Elevated Permissions**\n\nTo update LXC network interfaces, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
// Get current container configuration
const config = await this.proxmoxRequest(`/nodes/${safeNode}/lxc/${safeVMID}/config`, 'GET');
if (!config[net]) {
return {
content: [{
type: 'text',
text: `❌ **Network interface ${net} does not exist**\n\nPlease add the interface first using proxmox_add_network_lxc.\n\n**Existing interfaces**: ${Object.keys(config).filter(k => k.startsWith('net')).join(', ') || 'None'}`
}]
};
}
// Parse current configuration
const currentConfig = config[net];
const configParts = {};
currentConfig.split(',').forEach(part => {
const [key, value] = part.split('=');
configParts[key] = value;
});
// Update only provided parameters
if (bridge !== undefined) {
configParts.bridge = bridge;
}
if (ip !== undefined) {
configParts.ip = ip;
}
if (gw !== undefined) {
configParts.gw = gw;
}
if (firewall !== undefined) {
if (firewall) {
configParts.firewall = '1';
} else {
delete configParts.firewall;
}
}
// Rebuild configuration string
const netConfig = Object.entries(configParts)
.map(([key, value]) => `${key}=${value}`)
.join(',');
const body = {
[net]: netConfig
};
await this.proxmoxRequest(`/nodes/${safeNode}/lxc/${safeVMID}/config`, 'PUT', body);
let output = `🔧 **LXC Network Interface Updated**\n\n`;
output += `• **Container ID**: ${safeVMID}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Interface**: ${net}\n`;
output += `• **New Configuration**: ${netConfig}\n\n`;
output += `**Changes applied**:\n`;
if (bridge !== undefined) output += `- Bridge: ${bridge}\n`;
if (ip !== undefined) output += `- IP Address: ${ip}\n`;
if (gw !== undefined) output += `- Gateway: ${gw}\n`;
if (firewall !== undefined) output += `- Firewall: ${firewall ? 'Enabled' : 'Disabled'}\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to update LXC network interface**\n\nError: ${error.message}\n\n**Common issues**:\n- Network interface doesn't exist\n- Bridge doesn't exist\n- Invalid IP address format\n- Invalid gateway address\n- Container is locked or in use`
}]
};
}
}
async removeNetworkVM(node, vmid, net) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Network Management Requires Elevated Permissions**\n\nTo remove VM network interfaces, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const body = {
delete: net
};
await this.proxmoxRequest(`/nodes/${safeNode}/qemu/${safeVMID}/config`, 'PUT', body);
let output = ` **VM Network Interface Removed**\n\n`;
output += `• **VM ID**: ${safeVMID}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Interface Removed**: ${net}\n\n`;
output += `**Note**: The network interface has been removed from the VM configuration.\n`;
output += `**Tip**: If the VM is running, you may need to restart it for changes to take effect.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to remove VM network interface**\n\nError: ${error.message}\n\n**Common issues**:\n- Network interface doesn't exist\n- VM is locked or in use\n- Invalid interface name`
}]
};
}
}
async removeNetworkLXC(node, vmid, net) {
if (!this.allowElevated) {
return {
content: [{
type: 'text',
text: `⚠️ **Network Management Requires Elevated Permissions**\n\nTo remove LXC network interfaces, set \`PROXMOX_ALLOW_ELEVATED=true\` in your .env file.\n\n**Current permissions**: Basic (read-only)`
}]
};
}
try {
// Validate inputs
const safeNode = this.validateNodeName(node);
const safeVMID = this.validateVMID(vmid);
const body = {
delete: net
};
await this.proxmoxRequest(`/nodes/${safeNode}/lxc/${safeVMID}/config`, 'PUT', body);
let output = ` **LXC Network Interface Removed**\n\n`;
output += `• **Container ID**: ${safeVMID}\n`;
output += `• **Node**: ${safeNode}\n`;
output += `• **Interface Removed**: ${net}\n\n`;
output += `**Note**: The network interface has been removed from the container configuration.\n`;
output += `**Tip**: If the container is running, you may need to restart it for changes to take effect.\n`;
return {
content: [{ type: 'text', text: output }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `❌ **Failed to remove LXC network interface**\n\nError: ${error.message}\n\n**Common issues**:\n- Network interface doesn't exist\n- Container is locked or in use\n- Invalid interface name`
}]
};
}
}
formatUptime(seconds) {
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
if (days > 0) {
return `${days}d ${hours}h ${minutes}m`;
} else if (hours > 0) {
return `${hours}h ${minutes}m`;
} else {
return `${minutes}m`;
}
}
formatBytes(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Proxmox MCP server running on stdio');
}
}
const server = new ProxmoxServer();
server.run().catch(console.error);