105 lines
3.4 KiB
Go
105 lines
3.4 KiB
Go
// Command omnichannel-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-mcp/internal/configserver"
|
|
"omnichannel-mcp/internal/tools"
|
|
|
|
"git.totmin.ru/en2zmax/forge-toolkit"
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
)
|
|
|
|
// serverName — имя MCP-сервера; часть хостов добавляет префикс serverName+"__"
|
|
// к именам инструментов.
|
|
const serverName = "omnichannel-mcp"
|
|
|
|
func main() {
|
|
// --health должен быть дешёвым: без чтения конфига и подключений.
|
|
if toolkit.Health() {
|
|
return
|
|
}
|
|
|
|
configPath := flag.String("config", "", "путь к config.json (иначе ищется OMNI_CONFIG_DIR/omnichannel-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
|
|
}
|