// Command omnichannel-configserver-mcp — MCP-сервер управления платформой Omnichannel через // HTTP API config_server v1.1.0+ (деплой и эксплуатация). // // Следует общим соглашениям MCP-серверов: stdio-only, один потокобезопасный // Manager, ручные JSON-схемы, отдельный домен, конфиг с live-reload, // probe-паттерны разрешений на изменяющих инструментах. // // Логи пишутся ТОЛЬКО в stderr: stdout занят JSON-RPC протоколом. package main import ( "encoding/json" "flag" "fmt" "log/slog" "os" "strings" "omnichannel-configserver-mcp/internal/configserver" "omnichannel-configserver-mcp/internal/tools" "git.totmin.ru/en2zmax/forge-toolkit" "github.com/modelcontextprotocol/go-sdk/mcp" ) // serverName — имя MCP-сервера; часть хостов добавляет префикс serverName+"__" // к именам инструментов. const serverName = "omnichannel-configserver-mcp" func main() { // --health должен быть дешёвым: без чтения конфига и подключений. if toolkit.Health() { return } configPath := flag.String("config", "", "путь к config.json (иначе ищется OMNI_CONFIG_DIR/omnichannel-configserver-mcp.json)") checkConfig := flag.Bool("check-config", false, "проверить конфиг, напечатать (секреты маскируются) и выйти") logLevel := flag.String("log-level", envOr("OMNI_LOG_LEVEL", "info"), "уровень логов: debug|info|warn|error") flag.Parse() setupLogger(*logLevel) if *checkConfig { os.Exit(runCheckConfig(*configPath)) } mgr := configserver.NewManager(*configPath) defer mgr.Close() if err := toolkit.Run(serverName, func(s *mcp.Server) { tools.RegisterAll(s, mgr) }); err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", serverName, err) os.Exit(1) } } // runCheckConfig печатает разрешённый конфиг (с маской секретов) — для проверки // настройки до подключения клиента и в CI. Возвращает код выхода. func runCheckConfig(cliPath string) int { mgr := configserver.NewManager(cliPath) path := mgr.ResolvePath("") if path == "" { fmt.Fprintln(os.Stderr, "config: путь не задан (нужен -config или OMNI_CONFIG_DIR)") return 1 } cfg, _, err := mgr.Config("") if err != nil { fmt.Fprintf(os.Stderr, "config: %v\n", err) return 1 } out, err := json.MarshalIndent(cfg.Redacted(), "", " ") if err != nil { fmt.Fprintf(os.Stderr, "config: %v\n", err) return 1 } fmt.Printf("config: %s\n%s\n", path, out) fmt.Printf("servers: %s (default: %s)\n", strings.Join(cfg.ServerNames(), ", "), cfg.Default) return 0 } // setupLogger направляет логи в stderr с выбранным уровнем. func setupLogger(level string) { var lvl slog.Level switch strings.ToLower(strings.TrimSpace(level)) { case "debug": lvl = slog.LevelDebug case "warn", "warning": lvl = slog.LevelWarn case "error": lvl = slog.LevelError default: lvl = slog.LevelInfo } handler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: lvl}) slog.SetDefault(slog.New(handler)) } func envOr(key, def string) string { if v := os.Getenv(key); v != "" { return v } return def }