106 lines
2.9 KiB
Go
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
|
|
}
|