package tools import ( "context" "forge-tools-lexicomai/internal/lexicomai" "git.totmin.ru/en2zmax/forge-toolkit" "github.com/modelcontextprotocol/go-sdk/mcp" ) // registerInstall — мутации (M3–M5): установка, обновление, удаление, база, // конфигурация, лицензия, управление службой. Все через addAction (forge-probe) // → хост применяет политику approval; домен дополнительно проверяет // read_only/allow_mutate. func registerInstall(r *registrar) { r.addAction("service_install", "Установка продукта на машину (асинхронно, возвращает id). Плюс последняя версия в ветке.", schema(map[string]any{ "product": strProps("Продукт компании", true, lexicomai.DefaultProducts()...), "alias": strProps("Машина (target alias); пусто = по умолчанию", false), "version": strProps("Версия (например 7.012); пусто = последняя", false), "branch": strProps("release или beta (default release)", false, "release", "beta"), }, []string{"product"}), patternsFor("install"), func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { args := requestArgs(req) p, err := productArg(args) if err != nil { return errorResult(err.Error()), nil } id, err := r.m.Install(ctx, getString(args, "alias", ""), p, getString(args, "version", ""), getString(args, "branch", "release")) if err != nil { return errorResult(err.Error()), nil } return textResult("install started, id=" + id), nil }) r.add("service_install_status", "Прогресс установки по id (service_install). Read-only.", schema(map[string]any{ "alias": strProps("Машина (target alias); пусто = по умолчанию", false), "id": strProps("id установки", true), }, []string{"id"}), func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { args := requestArgs(req) id, err := requireString(args, "id") if err != nil { return errorResult(err.Error()), nil } sess, ok := r.m.Session(id) if !ok { return errorResult("install session " + id + " not found (id is per-process)"), nil } t, err := targetArg(r.m, args) if err != nil { return errorResult(err.Error()), nil } out, err := r.m.InstallStatus(ctx, t, sess) if err != nil { return errorResult(err.Error()), nil } return textResult(out), nil }) r.addAction("service_uninstall", "Удаление продукта с машины (инсталлятор --uninstall).", schema(map[string]any{ "product": strProps("Продукт компании", true, lexicomai.DefaultProducts()...), "alias": strProps("Машина (target alias); пусто = по умолчанию", false), }, []string{"product"}), patternsFor("uninstall"), func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { args := requestArgs(req) p, err := productArg(args) if err != nil { return errorResult(err.Error()), nil } out, err := r.m.Uninstall(ctx, getString(args, "alias", ""), p) if err != nil { return errorResult(err.Error()), nil } return textResult(out), nil }) r.addAction("service_update", "Обновление продукта до последней версии в ветке (backup инсталлятором).", schema(map[string]any{ "product": strProps("Продукт компании", true, lexicomai.DefaultProducts()...), "alias": strProps("Машина (target alias); пусто = по умолчанию", false), "branch": strProps("release или beta (default release)", false, "release", "beta"), }, []string{"product"}), patternsFor("update"), func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { args := requestArgs(req) p, err := productArg(args) if err != nil { return errorResult(err.Error()), nil } id, err := r.m.Install(ctx, getString(args, "alias", ""), p, "", getString(args, "branch", "release")) if err != nil { return errorResult(err.Error()), nil } return textResult("update started, id=" + id), nil }) r.addAction("base_ensure", "Создание минимальной базы /opt/pve на машине (идемпотентно).", schema(map[string]any{ "alias": strProps("Машина (target alias); пусто = по умолчанию", false), }, nil), func(map[string]any) ([]string, []string) { return []string{"lexicomai base"}, nil }, func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { args := requestArgs(req) out, err := r.m.BaseEnsure(ctx, getString(args, "alias", "")) if err != nil { return errorResult(err.Error()), nil } return textResult(out), nil }) r.addAction("service_config_set", "Изменение ключей config.json (точечный путь, например logs.level) + рестарт.", schema(map[string]any{ "product": strProps("Продукт компании", true, lexicomai.DefaultProducts()...), "alias": strProps("Машина (target alias); пусто = по умолчанию", false), "key": strProps("Ключ (точечный путь, например logs.level)", true), "value": strProps("Новое значение (строка)", true), }, []string{"product", "key", "value"}), patternsFor("config"), func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { args := requestArgs(req) p, err := productArg(args) if err != nil { return errorResult(err.Error()), nil } key, err := requireString(args, "key") if err != nil { return errorResult(err.Error()), nil } out, err := r.m.ConfigSet(ctx, getString(args, "alias", ""), p, map[string]any{key: getString(args, "value", "")}) if err != nil { return errorResult(err.Error()), nil } return textResult(out), nil }) r.addAction("license_apply", "Запись license.json и проверка через /license/check.", schema(map[string]any{ "product": strProps("Продукт компании", true, lexicomai.DefaultProducts()...), "alias": strProps("Машина (target alias); пусто = по умолчанию", false), "license": strProps("JSON лицензии (service/expire/licenses/key)", true), }, []string{"product", "license"}), patternsFor("license"), func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { args := requestArgs(req) p, err := productArg(args) if err != nil { return errorResult(err.Error()), nil } lic, err := requireString(args, "license") if err != nil { return errorResult(err.Error()), nil } out, err := r.m.LicenseApply(ctx, getString(args, "alias", ""), p, lic) if err != nil { return errorResult(err.Error()), nil } return textResult(out), nil }) r.addAction("service_restart", "Перезапуск службы продукта (systemctl restart).", schema(map[string]any{ "product": strProps("Продукт компании", true, lexicomai.DefaultProducts()...), "alias": strProps("Машина (target alias); пусто = по умолчанию", false), }, []string{"product"}), patternsFor("restart"), func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { args := requestArgs(req) p, err := productArg(args) if err != nil { return errorResult(err.Error()), nil } out, err := r.m.ServiceControl(ctx, getString(args, "alias", ""), p, "restart") if err != nil { return errorResult(err.Error()), nil } return textResult(out), nil }) } // patternsFor строит permission-паттерн «lexicomai ». func patternsFor(action string) toolkit.PatternsFn { return func(args map[string]any) ([]string, []string) { p := getString(args, "product", "") return []string{"lexicomai " + action + " " + p}, nil } }