Files
Maksim Totmin 1c7242d5c5
ci / test (push) Successful in 2m12s
ci / lint (push) Successful in 4m21s
Переименование Go-модуля в omnichannel-configserver-mcp
2026-10-07 20:14:40 +07:00

141 lines
5.3 KiB
Go

package tools
import (
"context"
"omnichannel-configserver-mcp/internal/configserver"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// operate.go — жизненный цикл сервиса (deploy/restart/down/migrate) и загрузка
// фронта. Мутации; обычно require_approval в agent.yaml.
func registerOperateTools(s *mcp.Server) {
addPatternTool(s, &mcp.Tool{
Name: "deploy",
Description: "Поднять сервис (docker-compose up -d). Создаёт задачу агенту; wait=true — дождаться результата.",
InputSchema: lifecycleSchema(),
}, appPatterns, func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return runLifecycle(ctx, req, "deploy", false)
})
addPatternTool(s, &mcp.Tool{
Name: "restart",
Description: "Перезапустить сервис (down + up -d). Создаёт задачу агенту; wait=true — дождаться результата.",
InputSchema: lifecycleSchema(),
}, appPatterns, func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return runLifecycle(ctx, req, "restart", false)
})
addPatternTool(s, &mcp.Tool{
Name: "migrate",
Description: "Запустить миграции сервиса (docker-compose run migration). Создаёт задачу агенту.",
InputSchema: lifecycleSchema(),
}, appPatterns, func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return runLifecycle(ctx, req, "migrate", false)
})
addPatternTool(s, &mcp.Tool{
Name: "down",
Description: "Остановить сервис (docker-compose down). Разрушительно: требует confirm=\"true\".",
InputSchema: schema(map[string]any{
"server": serverProp(),
"app_id": intProps("Идентификатор сервиса", true),
"wait": boolProps("Дождаться завершения (по умолчанию false)", false),
"confirm": boolProps("Подтверждение разрушительной операции (обязательно true)", true),
}, []string{"app_id", "confirm"}),
}, appPatterns, func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return runLifecycle(ctx, req, "down", true)
})
addPatternTool(s, &mcp.Tool{
Name: "update_front",
Description: "Загрузить локальную сборку фронта на хост сервиса (полная замена build_folder). Разрушительно: требует confirm=\"true\". Каталог — внутри front_roots.",
InputSchema: schema(map[string]any{
"server": serverProp(),
"app_id": intProps("Идентификатор фронт-сервиса", true),
"build_dir": strProps("Локальный каталог сборки (абсолютный путь внутри front_roots)", true),
"confirm": boolProps("Подтверждение разрушительной операции (обязательно true)", true),
}, []string{"app_id", "build_dir", "confirm"}),
}, appPatterns, func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
args := requestArgs(req)
if res := confirmGate(args, "update_front"); res != nil {
return res, nil
}
id, res := appIDArg(args)
if res != nil {
return res, nil
}
buildDir, err := requireString(args, "build_dir")
if err != nil {
return errorResult(err.Error()), nil
}
api, res := openAPI(ctx, args, true)
if res != nil {
return res, nil
}
defer api.Close()
return result(api.UpdateFront(ctx, id, buildDir))
})
}
// lifecycleSchema — общая схема deploy/restart/migrate/down.
func lifecycleSchema() map[string]any {
return schema(map[string]any{
"server": serverProp(),
"app_id": intProps("Идентификатор сервиса", true),
"wait": boolProps("Дождаться завершения задачи (по умолчанию false)", false),
}, []string{"app_id"})
}
// runLifecycle — общий путь операций жизненного цикла: вызвать действие, при
// wait=true ограниченно дождаться задачи агента.
func runLifecycle(ctx context.Context, req *mcp.CallToolRequest, action string, needConfirm bool) (*mcp.CallToolResult, error) {
args := requestArgs(req)
if needConfirm {
if res := confirmGate(args, action); res != nil {
return res, nil
}
}
id, res := appIDArg(args)
if res != nil {
return res, nil
}
wait := getBool(args, "wait", false)
api, res := openAPI(ctx, args, true)
if res != nil {
return res, nil
}
defer api.Close()
var (
ack *configserver.TaskAck
err error
)
switch action {
case "deploy":
ack, err = api.Deploy(ctx, id)
case "restart":
ack, err = api.Restart(ctx, id)
case "down":
ack, err = api.Down(ctx, id)
case "migrate":
ack, err = api.Migrate(ctx, id)
}
if err != nil {
return result(nil, err)
}
// Без wait отдаём task_id, чтобы не блокировать последовательный loop агента.
if !wait {
return result(ack, nil)
}
task, err := api.WaitTask(ctx, ack.TaskID)
if err != nil {
return result(nil, err)
}
return result(map[string]any{"ack": ack, "task": task}, nil)
}