Files
mcp-proxmox/index.js
2026-03-02 12:14:08 -08:00

3162 lines
120 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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);