nub/internal/mcpc/connect.go
Tom a97013d876 initial commit
- v 0.1.0 siehe CHANGELOG.md
2026-07-25 11:08:01 +02:00

90 lines
2.3 KiB
Go

// 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()
}
}