Initial commit: forge-tools-proxmox — MCP-сервер для Proxmox VE
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user