74 lines
2.8 KiB
Go
74 lines
2.8 KiB
Go
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
|
||
}
|