feat: añadir cliente MCP para consumir servidores externos
Implementa el cliente MCP que permite a los agentes conectarse a servidores MCP externos y usar sus tools como si fueran tools nativas del agente. Arquitectura implementada: - shell/mcp/client.go: Cliente MCP con soporte stdio y SSE - shell/mcp/manager.go: Gestor de múltiples clientes MCP - tools/mcptools/mcp.go: Bridge que convierte MCP tools → tools.Tool - shell/mcp/server.go: Movido desde shell/protocols/ para colocación junto al client Cambios en config: - MCPServerCfg extendido con campos Transport, Command, Args, Env, Headers, Prefix, Timeout para soportar stdio y SSE transport Integración en runtime: - agents/runtime.go: Inicializa MCP manager si config.Tools.MCP.Enabled - buildToolRegistry: Registra tools MCP automáticamente con prefijos configurables - Agent: Campo mcpManager que se cierra en shutdown Transportes soportados: - stdio: Lanza subproceso (ej: npx -y @anthropic/mcp-server-brave-search) - SSE: Se conecta a servidor HTTP MCP Las tools MCP son indistinguibles de tools nativas desde el punto de vista del LLM. Auto-discovery via ListTools(), conversión de JSON Schema a tools.Param. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+47
-2
@@ -29,6 +29,7 @@ import (
|
||||
shellknowledge "github.com/enmanuel/agents/shell/knowledge"
|
||||
shelllm "github.com/enmanuel/agents/shell/llm"
|
||||
"github.com/enmanuel/agents/shell/matrix"
|
||||
shellmcp "github.com/enmanuel/agents/shell/mcp"
|
||||
shellmem "github.com/enmanuel/agents/shell/memory"
|
||||
"github.com/enmanuel/agents/shell/ssh"
|
||||
"github.com/enmanuel/agents/tools"
|
||||
@@ -37,6 +38,7 @@ import (
|
||||
toolhttp "github.com/enmanuel/agents/tools/http"
|
||||
toolknowledge "github.com/enmanuel/agents/tools/knowledgetools"
|
||||
toolmatrix "github.com/enmanuel/agents/tools/matrix"
|
||||
toolmcp "github.com/enmanuel/agents/tools/mcptools"
|
||||
toolmemory "github.com/enmanuel/agents/tools/memorytools"
|
||||
toolssh "github.com/enmanuel/agents/tools/ssh"
|
||||
toolweather "github.com/enmanuel/agents/tools/weather"
|
||||
@@ -62,6 +64,7 @@ type Agent struct {
|
||||
toolReg *tools.Registry
|
||||
logger *slog.Logger
|
||||
cryptoStore io.Closer // non-nil when E2EE is enabled; closed on shutdown
|
||||
mcpManager *shellmcp.Manager // nil when MCP client is disabled
|
||||
|
||||
// Lifecycle — cancel stops this agent individually; done is closed when Run returns.
|
||||
cancel context.CancelFunc
|
||||
@@ -236,8 +239,20 @@ func New(cfg *config.AgentConfig, rules []decision.Rule, agentACL acl.ACL, logge
|
||||
logger.Info("acl enabled (centralized security policy)")
|
||||
}
|
||||
|
||||
// MCP client manager — connects to external MCP servers
|
||||
var mcpManager *shellmcp.Manager
|
||||
if cfg.Tools.MCP.Enabled && len(cfg.Tools.MCP.Servers) > 0 {
|
||||
var mcpErr error
|
||||
mcpManager, mcpErr = shellmcp.NewManager(context.Background(), cfg.Tools.MCP.Servers, logger)
|
||||
if mcpErr != nil {
|
||||
logger.Error("mcp_manager_init_failed", "err", mcpErr)
|
||||
} else {
|
||||
logger.Info("mcp manager initialized", "servers", len(cfg.Tools.MCP.Servers))
|
||||
}
|
||||
}
|
||||
|
||||
// Tool registry — register tools enabled in config
|
||||
toolReg := buildToolRegistry(cfg, sshExec, matrixClient, memStore, kStore, roomCtx, logger)
|
||||
toolReg := buildToolRegistry(cfg, sshExec, matrixClient, memStore, kStore, mcpManager, roomCtx, logger)
|
||||
|
||||
// Rate limiting for tools
|
||||
if cfg.Security.ToolRateLimit.Enabled {
|
||||
@@ -272,7 +287,8 @@ func New(cfg *config.AgentConfig, rules []decision.Rule, agentACL acl.ACL, logge
|
||||
toolReg: toolReg,
|
||||
logger: logger,
|
||||
cryptoStore: cryptoStore,
|
||||
done: make(chan struct{}),
|
||||
mcpManager: mcpManager,
|
||||
done: make(chan struct{}),
|
||||
commands: make(map[string]CommandHandler),
|
||||
cmdAliases: command.BuiltinNames(),
|
||||
startTime: time.Now(),
|
||||
@@ -401,6 +417,9 @@ func (a *Agent) Run(ctx context.Context) error {
|
||||
if a.knowledgeStore != nil {
|
||||
defer a.knowledgeStore.Close()
|
||||
}
|
||||
if a.mcpManager != nil {
|
||||
defer a.mcpManager.Close()
|
||||
}
|
||||
a.logger.Info("agent starting",
|
||||
"id", a.cfg.Agent.ID,
|
||||
"name", a.cfg.Agent.Name,
|
||||
@@ -980,6 +999,7 @@ func buildToolRegistry(
|
||||
matrixClient *matrix.Client,
|
||||
memStore memory.Store,
|
||||
kStore *shellknowledge.FileStore,
|
||||
mcpManager *shellmcp.Manager,
|
||||
roomCtx *toolmemory.RoomContext,
|
||||
logger *slog.Logger,
|
||||
) *tools.Registry {
|
||||
@@ -1031,5 +1051,30 @@ func buildToolRegistry(
|
||||
logger.Debug("registered knowledge tools")
|
||||
}
|
||||
|
||||
// MCP tools — register tools from all connected MCP servers
|
||||
if mcpManager != nil {
|
||||
for serverName, mcpClient := range mcpManager.AllClients() {
|
||||
// Find the config for this server to get prefix, filter, timeout
|
||||
var serverCfg *config.MCPServerCfg
|
||||
for i := range cfg.Tools.MCP.Servers {
|
||||
if cfg.Tools.MCP.Servers[i].Name == serverName {
|
||||
serverCfg = &cfg.Tools.MCP.Servers[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if serverCfg == nil {
|
||||
logger.Warn("no config found for MCP server", "name", serverName)
|
||||
continue
|
||||
}
|
||||
|
||||
// Convert and register MCP tools
|
||||
mcpTools := toolmcp.FromMCPServer(mcpClient, serverCfg.Prefix, serverCfg.Tools, serverCfg.Timeout, logger)
|
||||
for _, tool := range mcpTools {
|
||||
reg.Register(tool)
|
||||
}
|
||||
logger.Debug("registered MCP tools", "server", serverName, "count", len(mcpTools))
|
||||
}
|
||||
}
|
||||
|
||||
return reg
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user