Initial commit: forge-tools-proxmox — MCP-сервер для Proxmox VE

This commit is contained in:
Maksim Totmin
2026-10-01 10:41:43 +07:00
commit a7addaead9
30 changed files with 4226 additions and 0 deletions
+181
View File
@@ -0,0 +1,181 @@
package tools
import (
"context"
"fmt"
"net/url"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// snapshot.go — снапшоты VM/CT: list (read), create, delete, rollback.
// Снапшот — точка восстановления гостя; delete/rollback необратимы →
// confirm + gate + probe.
func registerSnapshotTools(s *mcp.Server) {
s.AddTool(&mcp.Tool{
Name: "snapshot_list",
Description: "List snapshots of a VM/CT. Read-only.",
InputSchema: schema(map[string]any{
"node": strProps("Node name", true),
"vmid": strProps("VM/CT ID", true),
"kind": strProps("Guest type", false, "qemu", "lxc"),
"host": strProps("Cluster alias (default: primary)", false),
}, []string{"node", "vmid"}),
}, snapshotListHandler)
registerPatternTool(s, &mcp.Tool{
Name: "snapshot_create",
Description: "Create a snapshot of a VM/CT. Requires confirm + write permission.",
InputSchema: schema(map[string]any{
"node": strProps("Node name", true),
"vmid": strProps("VM/CT ID", true),
"name": strProps("Snapshot name", true),
"desc": strProps("Snapshot description", false),
"vmstate": boolProps("Include running state (RAM). Default: false", false),
"kind": strProps("Guest type", false, "qemu", "lxc"),
"confirm": strProps("Set to \"true\" to confirm", true, "true"),
}, []string{"node", "vmid", "name", "confirm"}),
}, vmPatterns, snapshotCreateHandler)
registerPatternTool(s, &mcp.Tool{
Name: "snapshot_delete",
Description: "Delete a snapshot. Requires confirm + write permission.",
InputSchema: schema(map[string]any{
"node": strProps("Node name", true),
"vmid": strProps("VM/CT ID", true),
"name": strProps("Snapshot name", true),
"kind": strProps("Guest type", false, "qemu", "lxc"),
"confirm": strProps("Set to \"true\" to confirm permanent deletion", true, "true"),
}, []string{"node", "vmid", "name", "confirm"}),
}, vmPatterns, snapshotDeleteHandler)
registerPatternTool(s, &mcp.Tool{
Name: "snapshot_rollback",
Description: "Roll back a VM/CT to a snapshot (restores disk/state). Requires confirm + write permission.",
InputSchema: schema(map[string]any{
"node": strProps("Node name", true),
"vmid": strProps("VM/CT ID", true),
"name": strProps("Snapshot name", true),
"kind": strProps("Guest type", false, "qemu", "lxc"),
"confirm": strProps("Set to \"true\" to confirm destructive rollback", true, "true"),
}, []string{"node", "vmid", "name", "confirm"}),
}, vmPatterns, snapshotRollbackHandler)
}
func snapshotListHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
t, err := tenantFor(ctx, req)
if err != nil {
return errorResult(err.Error()), nil
}
args := requestArgs(req)
node, vmid, host, r := resolveVMArgs(t, args)
if r != nil {
return r, nil
}
kind := guestKind(args)
cctx, cancel := timeout(ctx, t)
defer cancel()
data, err := t.SnapshotList(cctx, host, node, kind, vmid)
if err != nil {
return errorResult(err.Error()), nil
}
return textResult(pretty(data)), nil
}
func snapshotCreateHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
t, err := tenantFor(ctx, req)
if err != nil {
return errorResult(err.Error()), nil
}
args := requestArgs(req)
node, vmid, host, r := resolveVMArgs(t, args)
if r != nil {
return r, nil
}
if err := confirm(args, "snapshot_create"); err != nil {
return errorResult(err.Error()), nil
}
if err := gateVMWrite(t, host, vmid); err != nil {
return errorResult(err.Error()), nil
}
name, err := requireName(args, "name")
if err != nil {
return errorResult(err.Error()), nil
}
form := url.Values{"snapname": {name}}
if d := getString(args, "desc", ""); d != "" {
form.Set("description", d)
}
if getBool(args, "vmstate", false) {
form.Set("vmstate", "1")
}
upid, err := t.SnapshotCreate(ctx, host, node, guestKind(args), vmid, form)
if err != nil {
return errorResult(err.Error()), nil
}
return textResult(upidMsg("snapshot_create", fmt.Sprintf("%s/%d %s", node, vmid, name), upid)), nil
}
func snapshotDeleteHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
t, err := tenantFor(ctx, req)
if err != nil {
return errorResult(err.Error()), nil
}
args := requestArgs(req)
node, vmid, host, r := resolveVMArgs(t, args)
if r != nil {
return r, nil
}
if err := confirm(args, "snapshot_delete"); err != nil {
return errorResult(err.Error()), nil
}
if err := gateVMWrite(t, host, vmid); err != nil {
return errorResult(err.Error()), nil
}
name, err := requireName(args, "name")
if err != nil {
return errorResult(err.Error()), nil
}
upid, err := t.SnapshotDelete(ctx, host, node, guestKind(args), vmid, name)
if err != nil {
return errorResult(err.Error()), nil
}
return textResult(upidMsg("snapshot_delete", fmt.Sprintf("%s/%d %s", node, vmid, name), upid)), nil
}
func snapshotRollbackHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
t, err := tenantFor(ctx, req)
if err != nil {
return errorResult(err.Error()), nil
}
args := requestArgs(req)
node, vmid, host, r := resolveVMArgs(t, args)
if r != nil {
return r, nil
}
if err := confirm(args, "snapshot_rollback"); err != nil {
return errorResult(err.Error()), nil
}
if err := gateVMWrite(t, host, vmid); err != nil {
return errorResult(err.Error()), nil
}
name, err := requireName(args, "name")
if err != nil {
return errorResult(err.Error()), nil
}
upid, err := t.SnapshotRollback(ctx, host, node, guestKind(args), vmid, name)
if err != nil {
return errorResult(err.Error()), nil
}
return textResult(upidMsg("snapshot_rollback", fmt.Sprintf("%s/%d %s", node, vmid, name), upid)), nil
}
// guestKind возвращает тип гостя (qemu/lxc) из аргумента, по умолчанию qemu.
func guestKind(args map[string]any) string {
k := getString(args, "kind", vmTypeQEMU)
if k != vmTypeQEMU && k != vmTypeLXC {
return vmTypeQEMU
}
return k
}