package tools import ( "context" "fmt" "net/url" "github.com/modelcontextprotocol/go-sdk/mcp" ) // vm_config.go — изменение конфига ВМ (vm_config_update): НЕ структурные // поля (name/cpu/core/memory/balloon/vcpus ...), безопасные для общего update. // Структурно-опасные ключи (delete/revert/hotplug/sockets/cores...) — // в pve.DefaultDenyConfigKeys, туда же отправляем через выделенные // инструменты (disk/net) с confirm, а не через общий update. // // Принимает неструктурированный объект `updates` (map key→value) — модель // собирает нужные ей поля; guard отклоняет запрещённые. func registerVMConfigTools(s *mcp.Server) { registerPatternTool(s, &mcp.Tool{ Name: "vm_config_update", Description: "Update safe (non-structural) QEMU config fields of a VM: name, cpu, cores, memory, balloon, vcpus, tags, description. Structural/denied keys are rejected. Requires confirm + write permission.", InputSchema: schema(map[string]any{ "node": strProps("Node name", true), "vmid": strProps("VM ID", true), "updates": objectProps("Object of key->value config fields to set", true), "confirm": strProps("Set to \"true\" to confirm", true, "true"), }, []string{"node", "vmid", "updates", "confirm"}), }, vmPatterns, vmConfigUpdateHandler) } func vmConfigUpdateHandler(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, "vm_config_update"); err != nil { return errorResult(err.Error()), nil } if err := gateVMWrite(t, host, vmid); err != nil { return errorResult(err.Error()), nil } updates := getObject(args, "updates") if len(updates) == 0 { return errorResult("'updates' must be a non-empty object of config fields"), nil } form := make(url.Values, len(updates)) for k, v := range updates { if t.Config().DenyConfigKey(k) { return errorResult(fmt.Sprintf("field %q is denied by policy (use a dedicated tool)", k)), nil } form.Set(k, fmt.Sprint(v)) } upid, err := t.ConfigPost(ctx, host, node, vmTypeQEMU, vmid, form) if err != nil { return errorResult(err.Error()), nil } return textResult(upidMsg("vm_config_update", fmt.Sprintf("%s/%d", node, vmid), upid)), nil } // getObject — читает объект аргумента (map[string]any). func getObject(args map[string]any, key string) map[string]any { if v, ok := args[key].(map[string]any); ok { return v } return nil }