// Package mcpc ist der MCP-Client: Verbindungsaufbau, Bridge MCP-Tool -> // tool.Tool (5.6). package mcpc import ( "context" "fmt" "os/exec" "time" "github.com/modelcontextprotocol/go-sdk/mcp" "nub/internal/config" ) const defaultConnectTimeout = 5 * time.Second // Server ist eine erfolgreich verbundene MCP-Session. type Server struct { Name string Session *mcp.ClientSession } type ConnectResult struct { Servers []*Server Warnings []string } // ConnectAll verbindet alle konfigurierten Server parallel, mit einem // Timeout pro Server (Default 5s, E-06). Ein einzelner kaputter Server // verhindert den Start nicht — er fällt raus, mit einer Warnung. func ConnectAll(ctx context.Context, servers []config.MCPServer) ConnectResult { type outcome struct { srv *Server warn string } ch := make(chan outcome, len(servers)) for _, cfg := range servers { go func(cfg config.MCPServer) { connCtx, cancel := context.WithTimeout(ctx, defaultConnectTimeout) defer cancel() session, err := connect(connCtx, cfg) if err != nil { ch <- outcome{warn: fmt.Sprintf("mcp server %q: %v — skipped", cfg.Name, err)} return } ch <- outcome{srv: &Server{Name: cfg.Name, Session: session}} }(cfg) } var result ConnectResult for range servers { o := <-ch if o.srv != nil { result.Servers = append(result.Servers, o.srv) } if o.warn != "" { result.Warnings = append(result.Warnings, o.warn) } } return result } func connect(ctx context.Context, cfg config.MCPServer) (*mcp.ClientSession, error) { client := mcp.NewClient(&mcp.Implementation{Name: "nub", Version: "0.1.0"}, nil) var transport mcp.Transport switch { case cfg.Command != "": // Der Prozess läuft für die gesamte Session, nicht nur für den // Verbindungsaufbau — deshalb kein an connCtx gebundenes CommandContext. transport = &mcp.CommandTransport{Command: exec.Command(cfg.Command, cfg.Args...)} case cfg.URL != "": transport = &mcp.StreamableClientTransport{Endpoint: cfg.URL} default: return nil, fmt.Errorf("neither command nor url configured") } return client.Connect(ctx, transport, nil) } // CloseAll schließt alle Sessions; Fehler werden ignoriert (best effort beim // Beenden). func CloseAll(servers []*Server) { for _, s := range servers { _ = s.Session.Close() } }