Files
omnichannel-configserver-mcp/internal/tools/overview.go
T
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

110 lines
3.6 KiB
Go

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
}