Files

162 lines
5.0 KiB
Go

package tools
import (
"context"
"fmt"
"strings"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// cluster.go — read-only инструменты кластера/нод (наблюдение, без мутаций).
// Здесь НЕТ node_reboot/shutdown: в однонодовом homelab рестарт хоста
// обрушил бы всё окружение (blast-radius при нулевой ценности).
func registerClusterTools(s *mcp.Server) {
// clusters_list — перечень настроенных кластеров (операторских) + версия.
s.AddTool(&mcp.Tool{
Name: "clusters_list",
Description: "List configured Proxmox clusters (host aliases) and their PVE version. Read-only.",
InputSchema: schema(nil, nil),
}, clustersListHandler)
// cluster_status — состояние кластера (quorum, узлы, версии).
s.AddTool(&mcp.Tool{
Name: "cluster_status",
Description: "Get cluster health: quorum, node list, versions. Read-only.",
InputSchema: schema(map[string]any{
"host": strProps("Cluster alias (default: primary)", false),
}, nil),
}, clusterStatusHandler)
// nodes_list — список нод кластера.
s.AddTool(&mcp.Tool{
Name: "nodes_list",
Description: "List all nodes in the cluster with status and resource usage. Read-only.",
InputSchema: schema(map[string]any{
"host": strProps("Cluster alias (default: primary)", false),
}, nil),
}, nodesListHandler)
// node_status — детальное состояние ноды.
s.AddTool(&mcp.Tool{
Name: "node_status",
Description: "Get detailed status (CPU/memory/disk/uptime) of one node. Read-only.",
InputSchema: schema(map[string]any{
"node": strProps("Node name", true),
"host": strProps("Cluster alias (default: primary)", false),
}, []string{"node"}),
}, nodeStatusHandler)
// node_network — сетевые интерфейсы ноды (мосты/eth).
s.AddTool(&mcp.Tool{
Name: "node_network",
Description: "List network interfaces and bridges of a node. Read-only.",
InputSchema: schema(map[string]any{
"node": strProps("Node name", true),
"host": strProps("Cluster alias (default: primary)", false),
}, []string{"node"}),
}, nodeNetworkHandler)
}
func clustersListHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
t, err := tenantFor(ctx, req)
if err != nil {
return errorResult(err.Error()), nil
}
var aliases []string
for _, h := range t.Config().Hosts {
aliases = append(aliases, h.Alias)
}
cctx, cancel := timeout(ctx, t)
defer cancel()
ver, err := t.Version(cctx, "")
if err != nil {
return textResult(fmt.Sprintf("Clusters configured: %s\nPVE version: unavailable (%s)", strings.Join(aliases, ", "), err)), nil
}
return textResult(fmt.Sprintf("Clusters configured: %s\nPVE version: %s",
strings.Join(aliases, ", "), pretty(ver))), nil
}
func clusterStatusHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
t, err := tenantFor(ctx, req)
if err != nil {
return errorResult(err.Error()), nil
}
host, err := resolveHost(t, requestArgs(req))
if err != nil {
return errorResult(err.Error()), nil
}
cctx, cancel := timeout(ctx, t)
defer cancel()
data, err := t.ClusterStatus(cctx, host)
if err != nil {
return errorResult(err.Error()), nil
}
return textResult(pretty(data)), nil
}
func nodesListHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
t, err := tenantFor(ctx, req)
if err != nil {
return errorResult(err.Error()), nil
}
host, err := resolveHost(t, requestArgs(req))
if err != nil {
return errorResult(err.Error()), nil
}
cctx, cancel := timeout(ctx, t)
defer cancel()
data, err := t.Nodes(cctx, host)
if err != nil {
return errorResult(err.Error()), nil
}
return textResult(pretty(data)), nil
}
func nodeStatusHandler(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, err := requireNode(args)
if err != nil {
return errorResult(err.Error()), nil
}
host, err := resolveHost(t, args)
if err != nil {
return errorResult(err.Error()), nil
}
cctx, cancel := timeout(ctx, t)
defer cancel()
data, err := t.NodeStatus(cctx, host, node)
if err != nil {
return errorResult(err.Error()), nil
}
return textResult(pretty(data)), nil
}
func nodeNetworkHandler(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, err := requireNode(args)
if err != nil {
return errorResult(err.Error()), nil
}
host, err := resolveHost(t, args)
if err != nil {
return errorResult(err.Error()), nil
}
cctx, cancel := timeout(ctx, t)
defer cancel()
data, err := t.NodeNetwork(cctx, host, node)
if err != nil {
return errorResult(err.Error()), nil
}
return textResult(pretty(data)), nil
}