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 }