90 lines
2.3 KiB
Go
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()
|
|
}
|
|
}
|