173 lines
5.3 KiB
Go
173 lines
5.3 KiB
Go
package tools
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
|
||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||
)
|
||
|
||
// task.go — задачи Proxmox (UPID): list (read), status (read, с опц. wait),
|
||
// log (read), stop (confirm + write). Мутации возвращают UPID, поэтому
|
||
// task_status(wait=true) — ключ к подтверждению async-операций, при этом
|
||
// poll строго ограничен timeout (bounded §9.6).
|
||
|
||
func registerTaskTools(s *mcp.Server) {
|
||
s.AddTool(&mcp.Tool{
|
||
Name: "tasks_list",
|
||
Description: "List recent cluster/node tasks. Read-only.",
|
||
InputSchema: schema(map[string]any{
|
||
"node": strProps("Node name (empty = whole cluster)", false),
|
||
"limit": intProps("Number of tasks (default: 20)", false),
|
||
"host": strProps("Cluster alias (default: primary)", false),
|
||
}, nil),
|
||
}, tasksListHandler)
|
||
|
||
s.AddTool(&mcp.Tool{
|
||
Name: "task_status",
|
||
Description: "Get status of a task by UPID. Set wait=true to poll until it finishes (bounded by task_poll_max_sec). Read-only.",
|
||
InputSchema: schema(map[string]any{
|
||
"node": strProps("Node name", true),
|
||
"upid": strProps("Task UPID", true),
|
||
"wait": boolProps("Wait for completion (default: false)", false),
|
||
"host": strProps("Cluster alias (default: primary)", false),
|
||
}, []string{"node", "upid"}),
|
||
}, taskStatusHandler)
|
||
|
||
s.AddTool(&mcp.Tool{
|
||
Name: "task_log",
|
||
Description: "Get the log of a task by UPID. Read-only.",
|
||
InputSchema: schema(map[string]any{
|
||
"node": strProps("Node name", true),
|
||
"upid": strProps("Task UPID", true),
|
||
"limit": intProps("Number of lines (default: 50)", false),
|
||
"host": strProps("Cluster alias (default: primary)", false),
|
||
}, []string{"node", "upid"}),
|
||
}, taskLogHandler)
|
||
|
||
registerPatternTool(s, &mcp.Tool{
|
||
Name: "task_stop",
|
||
Description: "Stop/cancel a running task by UPID. Requires confirm + write permission.",
|
||
InputSchema: schema(map[string]any{
|
||
"node": strProps("Node name", true),
|
||
"upid": strProps("Task UPID", true),
|
||
"confirm": strProps("Set to \"true\" to confirm", true, "true"),
|
||
"host": strProps("Cluster alias (default: primary)", false),
|
||
}, []string{"node", "upid", "confirm"}),
|
||
}, nodePatterns, taskStopHandler)
|
||
}
|
||
|
||
func tasksListHandler(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)
|
||
host, err := resolveHost(t, args)
|
||
if err != nil {
|
||
return errorResult(err.Error()), nil
|
||
}
|
||
limit := getInt(args, "limit", 20)
|
||
cctx, cancel := timeout(ctx, t)
|
||
defer cancel()
|
||
data, err := t.TasksList(cctx, host, getString(args, "node", ""), limit)
|
||
if err != nil {
|
||
return errorResult(err.Error()), nil
|
||
}
|
||
return textResult(pretty(data)), nil
|
||
}
|
||
|
||
func taskStatusHandler(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
|
||
}
|
||
upid, err := requireUPID(args)
|
||
if err != nil {
|
||
return errorResult(err.Error()), nil
|
||
}
|
||
host, err := resolveHost(t, args)
|
||
if err != nil {
|
||
return errorResult(err.Error()), nil
|
||
}
|
||
if getBool(args, "wait", false) {
|
||
out, err := t.TaskWait(ctx, host, node, upid)
|
||
if err != nil {
|
||
return errorResult(err.Error()), nil
|
||
}
|
||
return textResult(out), nil
|
||
}
|
||
cctx, cancel := timeout(ctx, t)
|
||
defer cancel()
|
||
data, err := t.TaskStatus(cctx, host, node, upid)
|
||
if err != nil {
|
||
return errorResult(err.Error()), nil
|
||
}
|
||
return textResult(pretty(data)), nil
|
||
}
|
||
|
||
func taskLogHandler(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
|
||
}
|
||
upid, err := requireUPID(args)
|
||
if err != nil {
|
||
return errorResult(err.Error()), nil
|
||
}
|
||
host, err := resolveHost(t, args)
|
||
if err != nil {
|
||
return errorResult(err.Error()), nil
|
||
}
|
||
limit := getInt(args, "limit", 50)
|
||
cctx, cancel := timeout(ctx, t)
|
||
defer cancel()
|
||
data, err := t.TaskLog(cctx, host, node, upid, limit)
|
||
if err != nil {
|
||
return errorResult(err.Error()), nil
|
||
}
|
||
return textResult(pretty(data)), nil
|
||
}
|
||
|
||
func taskStopHandler(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
|
||
}
|
||
upid, err := requireUPID(args)
|
||
if err != nil {
|
||
return errorResult(err.Error()), nil
|
||
}
|
||
host, err := resolveHost(t, args)
|
||
if err != nil {
|
||
return errorResult(err.Error()), nil
|
||
}
|
||
if err := confirm(args, "task_stop"); err != nil {
|
||
return errorResult(err.Error()), nil
|
||
}
|
||
// task_stop — отмена выполняющейся задачи (не ресурс кластера).
|
||
if err := gateNodeWrite(t, host, node); err != nil {
|
||
return errorResult(err.Error()), nil
|
||
}
|
||
upidMsg2, err := t.TaskStop(ctx, host, node, upid)
|
||
if err != nil {
|
||
return errorResult(err.Error()), nil
|
||
}
|
||
return textResult(upidMsg("task_stop", fmt.Sprintf("%s %s", node, upid), upidMsg2)), nil
|
||
}
|