Files

74 lines
2.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}