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

106 lines
2.9 KiB
Go

package mcpc
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/modelcontextprotocol/go-sdk/mcp"
"nub/internal/config"
"nub/internal/tool"
)
const toolCountWarnThreshold = 15
// bridgeTool bindet ein einzelnes MCP-Tool eines Servers als tool.Tool ein.
// ForModel sammelt die Textblöcke, ForUI trägt den strukturierten Content.
type bridgeTool struct {
server string
def *mcp.Tool
session *mcp.ClientSession
}
func (t *bridgeTool) Name() string { return t.server + "__" + t.def.Name }
func (t *bridgeTool) Description() string { return t.def.Description }
func (t *bridgeTool) Schema() json.RawMessage {
b, err := json.Marshal(t.def.InputSchema)
if err != nil {
return json.RawMessage(`{"type":"object"}`)
}
return b
}
func (t *bridgeTool) Run(ctx context.Context, input json.RawMessage, env tool.Env) (tool.Result, error) {
var args map[string]any
if len(input) > 0 {
if err := json.Unmarshal(input, &args); err != nil {
return tool.Result{ForModel: "invalid input: " + err.Error(), IsError: true}, nil
}
}
res, err := t.session.CallTool(ctx, &mcp.CallToolParams{Name: t.def.Name, Arguments: args})
if err != nil {
// Server-Ausfall zur Laufzeit: IsError statt Panic/Loop-Abbruch (5.6).
return tool.Result{ForModel: fmt.Sprintf("mcp %s: %v", t.Name(), err), IsError: true}, nil
}
var textParts []string
for _, c := range res.Content {
if tc, ok := c.(*mcp.TextContent); ok {
textParts = append(textParts, tc.Text)
}
}
return tool.Result{
ForModel: strings.Join(textParts, "\n"),
ForUI: Result{Server: t.server, Content: res.Content},
IsError: res.IsError,
}, nil
}
// Result ist die strukturierte UI-Repräsentation eines MCP-Tool-Aufrufs.
type Result struct {
Server string
Content []mcp.Content
}
// RegisterTools listet die Tools eines verbundenen Servers, filtert nach der
// konfigurierten Allowlist (cfg.Tools) und registriert sie mit
// <server>__-Präfix. Ohne Allowlist werden alle Tools geladen, aber ab
// mehr als 15 Tools gewarnt (E-07).
func RegisterTools(ctx context.Context, reg *tool.Registry, srv *Server, cfg config.MCPServer) ([]string, error) {
var all []*mcp.Tool
for t, err := range srv.Session.Tools(ctx, nil) {
if err != nil {
return nil, err
}
all = append(all, t)
}
var warnings []string
allowed := all
if len(cfg.Tools) > 0 {
allowSet := make(map[string]bool, len(cfg.Tools))
for _, n := range cfg.Tools {
allowSet[n] = true
}
allowed = make([]*mcp.Tool, 0, len(all))
for _, t := range all {
if allowSet[t.Name] {
allowed = append(allowed, t)
}
}
} else if len(all) > toolCountWarnThreshold {
warnings = append(warnings, fmt.Sprintf(
"mcp server %q liefert %d Tools ohne Allowlist — 'tools = [...]' in der Config empfohlen",
cfg.Name, len(all)))
}
for _, t := range allowed {
reg.Register(&bridgeTool{server: cfg.Name, def: t, session: srv.Session})
}
return warnings, nil
}