Initial commit: forge-tools-proxmox — MCP-сервер для Proxmox VE

This commit is contained in:
Maksim Totmin
2026-10-01 10:41:43 +07:00
commit a7addaead9
30 changed files with 4226 additions and 0 deletions
+73
View File
@@ -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
}