162 lines
5.0 KiB
Go
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
|
|
}
|