package tools import ( "context" "fmt" "net/url" "strings" "github.com/modelcontextprotocol/go-sdk/mcp" ) // vm_net.go — сетевые интерфейсы ВМ (net0..netN): добавить/обновить/удалить. // Это структурные изменения конфига → write gate + confirm + probe. Модель // задаёт iface (net0), модель/бридж; guard валидирует имя интерфейса. func registerVMNetTools(s *mcp.Server) { registerPatternTool(s, &mcp.Tool{ Name: "vm_add_network", Description: "Add a network interface (netN) to a VM, e.g. net0 via vmbr0. Requires confirm + write permission.", InputSchema: schema(map[string]any{ "node": strProps("Node name", true), "vmid": strProps("VM ID", true), "iface": strProps("Interface slot (e.g. net0)", true), "model": strProps("NIC model", true, "virtio", "e1000", "vmxnet3", "rtl8139"), "bridge": strProps("Bridge (e.g. vmbr0)", true), "mac": strProps("MAC (empty = auto)", false), "firewall": boolProps("Enable firewall (default: true)", false), "confirm": strProps("Set to \"true\" to confirm", true, "true"), }, []string{"node", "vmid", "iface", "bridge", "confirm"}), }, vmPatterns, vmAddNetworkHandler) registerPatternTool(s, &mcp.Tool{ Name: "vm_update_network", Description: "Update an existing network interface (model/bridge/mac/firewall). Requires confirm + write permission.", InputSchema: schema(map[string]any{ "node": strProps("Node name", true), "vmid": strProps("VM ID", true), "iface": strProps("Interface slot (e.g. net0)", true), "model": strProps("NIC model", false, "virtio", "e1000", "vmxnet3", "rtl8139"), "bridge": strProps("Bridge (e.g. vmbr0)", false), "mac": strProps("MAC address", false), "firewall": boolProps("Enable firewall (default: true)", false), "confirm": strProps("Set to \"true\" to confirm", true, "true"), }, []string{"node", "vmid", "iface", "confirm"}), }, vmPatterns, vmUpdateNetworkHandler) registerPatternTool(s, &mcp.Tool{ Name: "vm_remove_network", Description: "Remove a network interface from a VM. Requires confirm + write permission.", InputSchema: schema(map[string]any{ "node": strProps("Node name", true), "vmid": strProps("VM ID", true), "iface": strProps("Interface slot (e.g. net0)", true), "confirm": strProps("Set to \"true\" to confirm", true, "true"), }, []string{"node", "vmid", "iface", "confirm"}), }, vmPatterns, vmRemoveNetworkHandler) } func vmAddNetworkHandler(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_add_network"); err != nil { return errorResult(err.Error()), nil } if err := gateVMWrite(t, host, vmid); err != nil { return errorResult(err.Error()), nil } iface, err := requireName(args, "iface") if err != nil { return errorResult(err.Error()), nil } model := getString(args, "model", "virtio") bridge := getString(args, "bridge", "") if bridge == "" { return errorResult("'bridge' is required"), nil } value := fmt.Sprintf("%s,bridge=%s", model, bridge) if mac := getString(args, "mac", ""); mac != "" { value += ",mac=" + mac } if getBool(args, "firewall", true) { value += ",firewall=1" } upid, err := t.ConfigPost(ctx, host, node, vmTypeQEMU, vmid, url.Values{iface: {value}}) if err != nil { return errorResult(err.Error()), nil } return textResult(upidMsg("vm_add_network", fmt.Sprintf("%s/%d %s", node, vmid, iface), upid)), nil } func vmUpdateNetworkHandler(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_update_network"); err != nil { return errorResult(err.Error()), nil } if err := gateVMWrite(t, host, vmid); err != nil { return errorResult(err.Error()), nil } iface, err := requireName(args, "iface") if err != nil { return errorResult(err.Error()), nil } // update перезаписывает весь сетевой спецификатор: собираем из явных полей. parts := []string{getString(args, "model", "virtio")} if b := getString(args, "bridge", ""); b != "" { parts = append(parts, "bridge="+b) } if m := getString(args, "mac", ""); m != "" { parts = append(parts, "mac="+m) } if getBool(args, "firewall", true) { parts = append(parts, "firewall=1") } upid, err := t.ConfigPost(ctx, host, node, vmTypeQEMU, vmid, url.Values{iface: {strings.Join(parts, ",")}}) if err != nil { return errorResult(err.Error()), nil } return textResult(upidMsg("vm_update_network", fmt.Sprintf("%s/%d %s", node, vmid, iface), upid)), nil } func vmRemoveNetworkHandler(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_remove_network"); err != nil { return errorResult(err.Error()), nil } if err := gateVMWrite(t, host, vmid); err != nil { return errorResult(err.Error()), nil } iface, err := requireName(args, "iface") if err != nil { return errorResult(err.Error()), nil } upid, err := t.ConfigPost(ctx, host, node, vmTypeQEMU, vmid, url.Values{"delete": {iface}}) if err != nil { return errorResult(err.Error()), nil } return textResult(upidMsg("vm_remove_network", fmt.Sprintf("%s/%d %s", node, vmid, iface), upid)), nil }