Version control Claude Code configuration including: - Global instructions (CLAUDE.md) - User settings (settings.json) - Custom agents (architect, designer, engineer, etc.) - Custom skills (create-skill templates and workflows) Excludes session data, secrets, cache, and temporary files per .gitignore. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
480 lines
17 KiB
Python
Executable File
480 lines
17 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Proxmox MCP Server
|
|
Exposes Proxmox operations as MCP tools for Claude Code
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
from typing import Any, Dict, List
|
|
from proxmox_client import ProxmoxClient
|
|
|
|
|
|
def create_mcp_server():
|
|
"""Create MCP server configuration"""
|
|
|
|
client = ProxmoxClient()
|
|
|
|
# Define MCP tools
|
|
tools = [
|
|
{
|
|
"name": "proxmox_list_vms",
|
|
"description": "List all virtual machines with their current status, resource usage (CPU, memory), and configuration",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"node": {
|
|
"type": "string",
|
|
"description": "Proxmox node name (optional, defaults to configured node)"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "proxmox_get_vm",
|
|
"description": "Get detailed information about a specific VM including configuration, status, and resource usage",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"vmid": {
|
|
"type": "integer",
|
|
"description": "VM ID number"
|
|
},
|
|
"node": {
|
|
"type": "string",
|
|
"description": "Proxmox node name (optional)"
|
|
}
|
|
},
|
|
"required": ["vmid"]
|
|
}
|
|
},
|
|
{
|
|
"name": "proxmox_start_vm",
|
|
"description": "Start a virtual machine",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"vmid": {
|
|
"type": "integer",
|
|
"description": "VM ID to start"
|
|
},
|
|
"node": {
|
|
"type": "string",
|
|
"description": "Proxmox node name (optional)"
|
|
}
|
|
},
|
|
"required": ["vmid"]
|
|
}
|
|
},
|
|
{
|
|
"name": "proxmox_stop_vm",
|
|
"description": "Stop a virtual machine (graceful shutdown or force)",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"vmid": {
|
|
"type": "integer",
|
|
"description": "VM ID to stop"
|
|
},
|
|
"force": {
|
|
"type": "boolean",
|
|
"description": "Force immediate shutdown (default: false)",
|
|
"default": False
|
|
},
|
|
"node": {
|
|
"type": "string",
|
|
"description": "Proxmox node name (optional)"
|
|
}
|
|
},
|
|
"required": ["vmid"]
|
|
}
|
|
},
|
|
{
|
|
"name": "proxmox_restart_vm",
|
|
"description": "Restart a virtual machine",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"vmid": {
|
|
"type": "integer",
|
|
"description": "VM ID to restart"
|
|
},
|
|
"node": {
|
|
"type": "string",
|
|
"description": "Proxmox node name (optional)"
|
|
}
|
|
},
|
|
"required": ["vmid"]
|
|
}
|
|
},
|
|
{
|
|
"name": "proxmox_create_snapshot",
|
|
"description": "Create a snapshot of a VM for backup or before making changes",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"vmid": {
|
|
"type": "integer",
|
|
"description": "VM ID"
|
|
},
|
|
"snapname": {
|
|
"type": "string",
|
|
"description": "Snapshot name (e.g., 'before-upgrade')"
|
|
},
|
|
"description": {
|
|
"type": "string",
|
|
"description": "Human-readable description",
|
|
"default": ""
|
|
},
|
|
"vmstate": {
|
|
"type": "boolean",
|
|
"description": "Include VM RAM state (for running VMs)",
|
|
"default": False
|
|
},
|
|
"node": {
|
|
"type": "string",
|
|
"description": "Proxmox node name (optional)"
|
|
}
|
|
},
|
|
"required": ["vmid", "snapname"]
|
|
}
|
|
},
|
|
{
|
|
"name": "proxmox_list_snapshots",
|
|
"description": "List all snapshots for a VM",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"vmid": {
|
|
"type": "integer",
|
|
"description": "VM ID"
|
|
},
|
|
"node": {
|
|
"type": "string",
|
|
"description": "Proxmox node name (optional)"
|
|
}
|
|
},
|
|
"required": ["vmid"]
|
|
}
|
|
},
|
|
{
|
|
"name": "proxmox_rollback_snapshot",
|
|
"description": "Rollback a VM to a previous snapshot",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"vmid": {
|
|
"type": "integer",
|
|
"description": "VM ID"
|
|
},
|
|
"snapname": {
|
|
"type": "string",
|
|
"description": "Snapshot name to rollback to"
|
|
},
|
|
"node": {
|
|
"type": "string",
|
|
"description": "Proxmox node name (optional)"
|
|
}
|
|
},
|
|
"required": ["vmid", "snapname"]
|
|
}
|
|
},
|
|
{
|
|
"name": "proxmox_delete_snapshot",
|
|
"description": "Delete a VM snapshot",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"vmid": {
|
|
"type": "integer",
|
|
"description": "VM ID"
|
|
},
|
|
"snapname": {
|
|
"type": "string",
|
|
"description": "Snapshot name to delete"
|
|
},
|
|
"node": {
|
|
"type": "string",
|
|
"description": "Proxmox node name (optional)"
|
|
}
|
|
},
|
|
"required": ["vmid", "snapname"]
|
|
}
|
|
},
|
|
{
|
|
"name": "proxmox_clone_vm",
|
|
"description": "Clone an existing VM to create a new one",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"vmid": {
|
|
"type": "integer",
|
|
"description": "Source VM ID to clone from"
|
|
},
|
|
"newid": {
|
|
"type": "integer",
|
|
"description": "New VM ID for the clone"
|
|
},
|
|
"name": {
|
|
"type": "string",
|
|
"description": "Name for the cloned VM (optional)"
|
|
},
|
|
"full": {
|
|
"type": "boolean",
|
|
"description": "Create full clone (true) or linked clone (false)",
|
|
"default": True
|
|
},
|
|
"node": {
|
|
"type": "string",
|
|
"description": "Proxmox node name (optional)"
|
|
}
|
|
},
|
|
"required": ["vmid", "newid"]
|
|
}
|
|
},
|
|
{
|
|
"name": "proxmox_get_node_status",
|
|
"description": "Get Proxmox node status including CPU, memory, storage, and uptime",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"node": {
|
|
"type": "string",
|
|
"description": "Proxmox node name (optional)"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "proxmox_list_containers",
|
|
"description": "List all LXC containers",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"node": {
|
|
"type": "string",
|
|
"description": "Proxmox node name (optional)"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "proxmox_start_container",
|
|
"description": "Start an LXC container",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"vmid": {
|
|
"type": "integer",
|
|
"description": "Container ID to start"
|
|
},
|
|
"node": {
|
|
"type": "string",
|
|
"description": "Proxmox node name (optional)"
|
|
}
|
|
},
|
|
"required": ["vmid"]
|
|
}
|
|
},
|
|
{
|
|
"name": "proxmox_stop_container",
|
|
"description": "Stop an LXC container",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"vmid": {
|
|
"type": "integer",
|
|
"description": "Container ID to stop"
|
|
},
|
|
"node": {
|
|
"type": "string",
|
|
"description": "Proxmox node name (optional)"
|
|
}
|
|
},
|
|
"required": ["vmid"]
|
|
}
|
|
},
|
|
{
|
|
"name": "proxmox_list_storage",
|
|
"description": "List all storage pools and their capacity",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"node": {
|
|
"type": "string",
|
|
"description": "Proxmox node name (optional)"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
|
|
return {
|
|
"tools": tools,
|
|
"client": client
|
|
}
|
|
|
|
|
|
def handle_tool_call(tool_name: str, arguments: Dict[str, Any], client: ProxmoxClient) -> Dict[str, Any]:
|
|
"""Handle MCP tool calls"""
|
|
|
|
try:
|
|
if tool_name == "proxmox_list_vms":
|
|
vms = client.get_all_vms_status(arguments.get("node"))
|
|
return {"content": [{"type": "text", "text": json.dumps(vms, indent=2)}]}
|
|
|
|
elif tool_name == "proxmox_get_vm":
|
|
vm = client.get_vm(arguments["vmid"], arguments.get("node"))
|
|
return {"content": [{"type": "text", "text": json.dumps(vm, indent=2)}]}
|
|
|
|
elif tool_name == "proxmox_start_vm":
|
|
upid = client.start_vm(arguments["vmid"], arguments.get("node"))
|
|
return {"content": [{"type": "text", "text": f"VM {arguments['vmid']} starting. Task ID: {upid}"}]}
|
|
|
|
elif tool_name == "proxmox_stop_vm":
|
|
upid = client.stop_vm(
|
|
arguments["vmid"],
|
|
arguments.get("node"),
|
|
arguments.get("force", False)
|
|
)
|
|
action = "Force stopping" if arguments.get("force") else "Stopping"
|
|
return {"content": [{"type": "text", "text": f"{action} VM {arguments['vmid']}. Task ID: {upid}"}]}
|
|
|
|
elif tool_name == "proxmox_restart_vm":
|
|
upid = client.restart_vm(arguments["vmid"], arguments.get("node"))
|
|
return {"content": [{"type": "text", "text": f"Restarting VM {arguments['vmid']}. Task ID: {upid}"}]}
|
|
|
|
elif tool_name == "proxmox_create_snapshot":
|
|
upid = client.create_snapshot(
|
|
arguments["vmid"],
|
|
arguments["snapname"],
|
|
arguments.get("description", ""),
|
|
arguments.get("vmstate", False),
|
|
arguments.get("node")
|
|
)
|
|
return {"content": [{"type": "text", "text": f"Creating snapshot '{arguments['snapname']}' for VM {arguments['vmid']}. Task ID: {upid}"}]}
|
|
|
|
elif tool_name == "proxmox_list_snapshots":
|
|
snapshots = client.list_snapshots(arguments["vmid"], arguments.get("node"))
|
|
return {"content": [{"type": "text", "text": json.dumps(snapshots, indent=2)}]}
|
|
|
|
elif tool_name == "proxmox_rollback_snapshot":
|
|
upid = client.rollback_snapshot(
|
|
arguments["vmid"],
|
|
arguments["snapname"],
|
|
arguments.get("node")
|
|
)
|
|
return {"content": [{"type": "text", "text": f"Rolling back VM {arguments['vmid']} to snapshot '{arguments['snapname']}'. Task ID: {upid}"}]}
|
|
|
|
elif tool_name == "proxmox_delete_snapshot":
|
|
upid = client.delete_snapshot(
|
|
arguments["vmid"],
|
|
arguments["snapname"],
|
|
arguments.get("node")
|
|
)
|
|
return {"content": [{"type": "text", "text": f"Deleting snapshot '{arguments['snapname']}' from VM {arguments['vmid']}. Task ID: {upid}"}]}
|
|
|
|
elif tool_name == "proxmox_clone_vm":
|
|
upid = client.clone_vm(
|
|
arguments["vmid"],
|
|
arguments["newid"],
|
|
arguments.get("name"),
|
|
arguments.get("node"),
|
|
arguments.get("full", True)
|
|
)
|
|
clone_type = "full" if arguments.get("full", True) else "linked"
|
|
return {"content": [{"type": "text", "text": f"Cloning VM {arguments['vmid']} to {arguments['newid']} ({clone_type} clone). Task ID: {upid}"}]}
|
|
|
|
elif tool_name == "proxmox_get_node_status":
|
|
status = client.get_node_status(arguments.get("node"))
|
|
return {"content": [{"type": "text", "text": json.dumps(status, indent=2)}]}
|
|
|
|
elif tool_name == "proxmox_list_containers":
|
|
containers = client.list_containers(arguments.get("node"))
|
|
return {"content": [{"type": "text", "text": json.dumps(containers, indent=2)}]}
|
|
|
|
elif tool_name == "proxmox_start_container":
|
|
upid = client.start_container(arguments["vmid"], arguments.get("node"))
|
|
return {"content": [{"type": "text", "text": f"Starting container {arguments['vmid']}. Task ID: {upid}"}]}
|
|
|
|
elif tool_name == "proxmox_stop_container":
|
|
upid = client.stop_container(arguments["vmid"], arguments.get("node"))
|
|
return {"content": [{"type": "text", "text": f"Stopping container {arguments['vmid']}. Task ID: {upid}"}]}
|
|
|
|
elif tool_name == "proxmox_list_storage":
|
|
storage = client.list_storage(arguments.get("node"))
|
|
return {"content": [{"type": "text", "text": json.dumps(storage, indent=2)}]}
|
|
|
|
else:
|
|
return {"content": [{"type": "text", "text": f"Unknown tool: {tool_name}"}], "isError": True}
|
|
|
|
except Exception as e:
|
|
return {"content": [{"type": "text", "text": f"Error: {str(e)}"}], "isError": True}
|
|
|
|
|
|
def main():
|
|
"""MCP stdio server main loop"""
|
|
|
|
server = create_mcp_server()
|
|
client = server["client"]
|
|
tools = server["tools"]
|
|
|
|
# Read messages from stdin
|
|
for line in sys.stdin:
|
|
try:
|
|
message = json.loads(line)
|
|
|
|
if message.get("method") == "tools/list":
|
|
response = {
|
|
"jsonrpc": "2.0",
|
|
"id": message.get("id"),
|
|
"result": {"tools": tools}
|
|
}
|
|
print(json.dumps(response), flush=True)
|
|
|
|
elif message.get("method") == "tools/call":
|
|
params = message.get("params", {})
|
|
tool_name = params.get("name")
|
|
arguments = params.get("arguments", {})
|
|
|
|
result = handle_tool_call(tool_name, arguments, client)
|
|
|
|
response = {
|
|
"jsonrpc": "2.0",
|
|
"id": message.get("id"),
|
|
"result": result
|
|
}
|
|
print(json.dumps(response), flush=True)
|
|
|
|
elif message.get("method") == "initialize":
|
|
response = {
|
|
"jsonrpc": "2.0",
|
|
"id": message.get("id"),
|
|
"result": {
|
|
"protocolVersion": "2024-11-05",
|
|
"capabilities": {
|
|
"tools": {}
|
|
},
|
|
"serverInfo": {
|
|
"name": "proxmox-mcp-server",
|
|
"version": "1.0.0"
|
|
}
|
|
}
|
|
}
|
|
print(json.dumps(response), flush=True)
|
|
|
|
except Exception as e:
|
|
error_response = {
|
|
"jsonrpc": "2.0",
|
|
"id": message.get("id") if 'message' in locals() else None,
|
|
"error": {
|
|
"code": -32603,
|
|
"message": str(e)
|
|
}
|
|
}
|
|
print(json.dumps(error_response), flush=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|