package tools import ( "context" "omnichannel-configserver-mcp/internal/configserver" "github.com/modelcontextprotocol/go-sdk/mcp" ) // overview.go — сводные read-only инструменты для эксплуатации. Они не // изменяют состояние, а собирают картину несколькими GET-вызовами (best-effort: // сбой одного источника не рушит остальные, а попадает в поле errors). func registerOverviewTools(s *mcp.Server) { addTool(s, &mcp.Tool{ Name: "overview", Description: "Сводка состояния стенда одним вызовом: health, статистика, карта сервисов, приложения не в OK, последние failed-задачи. Read-only.", InputSchema: schema(map[string]any{"server": serverProp()}, nil), }, handleOverview) addTool(s, &mcp.Tool{ Name: "release_status", Description: "Состояние развёртывания релиза: release_job, release-задачи, приложения, сопоставление IP. Read-only.", InputSchema: schema(map[string]any{"server": serverProp()}, nil), }, handleReleaseStatus) } func handleOverview(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { return observeCall(ctx, req, func(api *configserver.API) (any, error) { out := map[string]any{"server": api.Alias(), "read_only": api.Config.ReadOnly} var errs []string if status, body, err := api.Health(ctx); err != nil { errs = append(errs, "health: "+err.Error()) } else { out["health"] = map[string]any{"http_status": status, "body": body} } if st, err := api.Stats(ctx); err != nil { errs = append(errs, "stats: "+err.Error()) } else { out["stats"] = st } if sm, err := api.ServiceMap(ctx); err != nil { errs = append(errs, "service_map: "+err.Error()) } else { out["service_map"] = sm } if apps, err := api.ListApplications(ctx, false); err != nil { errs = append(errs, "applications: "+err.Error()) } else { out["unhealthy_applications"] = unhealthy(apps) } if tasks, err := api.ListTasks(ctx, configserver.TaskFilter{Status: "failed", Limit: 10}); err != nil { errs = append(errs, "failed_tasks: "+err.Error()) } else { out["recent_failed_tasks"] = tasks } if len(errs) > 0 { out["errors"] = errs } return out, nil }) } func handleReleaseStatus(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { return observeCall(ctx, req, func(api *configserver.API) (any, error) { out := map[string]any{"server": api.Alias()} var errs []string if job, err := api.ReleaseJob(ctx); err != nil { errs = append(errs, "release_job: "+err.Error()) } else { out["release_job"] = job } if tasks, err := api.DeploymentTasks(ctx); err != nil { errs = append(errs, "deployment_tasks: "+err.Error()) } else { out["deployment_tasks"] = tasks } if apps, err := api.ListApplications(ctx, false); err != nil { errs = append(errs, "applications: "+err.Error()) } else { out["applications"] = apps } if ipm, err := api.IPMatch(ctx); err != nil { errs = append(errs, "ip_match: "+err.Error()) } else { out["ip_match"] = ipm } if len(errs) > 0 { out["errors"] = errs } return out, nil }) } // unhealthy возвращает приложения со статусом, отличным от OK. func unhealthy(apps []configserver.Application) []configserver.Application { out := []configserver.Application{} for _, app := range apps { if app.Status != "OK" { out = append(out, app) } } return out }