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>
11 KiB
017 — MCP Client: consumir servidores MCP como tools del agente
Objetivo
Permitir que los agentes se conecten a servidores MCP externos y expongan las tools de esos servidores como tools normales en su registry. Desde el punto de vista del LLM, una tool MCP es indistinguible de una tool nativa (ssh_command, http_get, etc.) — aparece en el function calling con su nombre, descripcion y parametros.
Contexto
- Ya existe
shell/protocols/mcp.goque expone tools del agente como MCP server (server-side). Falta el cliente que consume tools de servidores MCP externos. - La dependencia
github.com/mark3labs/mcp-go v0.44.1ya esta en go.mod. Incluye paquetesclientymcpcon soporte para stdio y SSE/HTTP. - El config ya tiene
MCPToolCfgconServers []MCPServerCfgeninternal/config/schema.go, pero solo soportaurl— hay que extender para soportar transporte stdio (command + args). - El tool registry (
tools/Registry) ya soporta registrar cualquiertools.Tool(Def + Exec). - El runtime (
agents/runtime.go:buildToolRegistry) ya tiene el patron para registrar tools condicionalmente.
Prerequisitos
- Ninguno estricto. La infraestructura de tools y config ya existe.
Arquitectura
config.yaml (tools.mcp.servers)
↓
shell/mcp/client.go ← conecta a servidores MCP, descubre tools
↓
tools/mcptools/mcp.go ← wrappea cada tool MCP como tools.Tool
↓
agents/runtime.go ← registra en el Registry como cualquier otra tool
↓
LLM ve las tools MCP en function calling, las invoca normalmente
Patron pure core / impure shell
pkg/ (nada nuevo) → no se necesitan tipos puros nuevos; tools.Def ya cubre
shell/mcp/ → IMPURE: cliente MCP real (I/O, subprocesos, red)
tools/mcptools/ → bridge: convierte MCP tool → tools.Tool
Transportes MCP soportados
| Transporte | Config | Descripcion |
|---|---|---|
| stdio | command + args |
Lanza un subproceso y se comunica via stdin/stdout. El mas comun (Claude Desktop, npx servers). |
| SSE/HTTP | url |
Se conecta a un servidor MCP remoto via HTTP con Server-Sent Events. |
Tareas
Fase 1: Extender config para stdio transport
-
1.1 Modificar
MCPServerCfgeninternal/config/schema.gopara soportar ambos transportes:type MCPServerCfg struct { Name string `yaml:"name"` // nombre logico del servidor Transport string `yaml:"transport"` // "stdio" | "sse" (default: auto-detect) Command string `yaml:"command"` // stdio: comando a ejecutar Args []string `yaml:"args"` // stdio: argumentos del comando Env map[string]string `yaml:"env"` // stdio: variables de entorno extra URL string `yaml:"url"` // sse: URL del servidor Headers map[string]string `yaml:"headers"` // sse: headers HTTP extra (auth, etc.) Tools []string `yaml:"tools"` // filtro: solo exponer estas tools (vacio = todas) Prefix string `yaml:"prefix"` // prefijo para nombres de tools (evitar colisiones) Timeout time.Duration `yaml:"timeout"` // timeout por llamada (default: 30s) } -
1.2 Validar que
CommandoURLeste presente (al menos uno).
Fase 2: MCP Client en shell/mcp/
-
2.1 Crear
shell/mcp/client.go— wrapper sobremcp-go/client:// Client conecta a un servidor MCP y descubre sus tools. type Client struct { name string mcpClient *client.StdioMCPClient // o SSEMCPClient tools []mcp.Tool // tools descubiertas logger *slog.Logger } func NewStdioClient(name, command string, args []string, env map[string]string, logger *slog.Logger) (*Client, error) func NewSSEClient(name, url string, headers map[string]string, logger *slog.Logger) (*Client, error) func (c *Client) Tools() []mcp.Tool // tools descubiertas func (c *Client) CallTool(ctx context.Context, name string, args map[string]any) (*mcp.CallToolResult, error) func (c *Client) Close() error -
2.2 Implementar
NewStdioClient:- Crear
client.NewStdioMCPClient(command, env, args...)(ver API de mcp-go) - Llamar
Initialize()con info del agente - Llamar
ListTools()para descubrir tools disponibles - Guardar la lista de tools
- Crear
-
2.3 Implementar
NewSSEClient:- Crear
client.NewSSEMCPClient(url, options...) - Initialize + ListTools igual que stdio
- Crear
-
2.4 Implementar
CallTool:- Delegar a
mcpClient.CallTool(ctx, mcp.CallToolRequest{...}) - Extraer texto del resultado (manejar text y error results)
- Delegar a
-
2.5 Implementar
Close:- Cerrar el cliente MCP (mata el subproceso en stdio, cierra conexion en SSE)
Fase 3: Bridge MCP → tools.Tool en tools/mcptools/
-
3.1 Crear
tools/mcptools/mcp.go— convierte tools de un MCP server en[]tools.Tool:// FromMCPServer toma un shell/mcp.Client y genera tools.Tool para cada tool MCP. // prefix se antepone al nombre de la tool (ej: "brave_" → "brave_web_search"). // filter limita que tools exponer (vacio = todas). func FromMCPServer(mcpClient *shellmcp.Client, prefix string, filter []string, timeout time.Duration) []tools.Tool -
3.2 Implementar conversion de
mcp.Tool→tools.Def:Name= prefix + tool.NameDescription= tool.DescriptionParameters= convertirtool.InputSchema(JSON Schema) →[]tools.Param- JSON Schema properties → Param con name, type, description
- JSON Schema required → Param.Required = true
-
3.3 Implementar el
ToolFuncwrapper:- Recibe
args map[string]any - Llama a
mcpClient.CallTool(ctx, originalName, args)(sin prefix) - Convierte el resultado MCP a
tools.Result
- Recibe
Fase 4: Integracion en runtime
-
4.1 Crear
shell/mcp/manager.go— gestiona multiples clientes MCP:// Manager inicializa y gestiona conexiones a multiples servidores MCP. type Manager struct { clients map[string]*Client // name → client logger *slog.Logger } func NewManager(servers []config.MCPServerCfg, logger *slog.Logger) (*Manager, error) func (m *Manager) AllTools(reg *tools.Registry) // registra todas las tools en el registry func (m *Manager) Close() error // cierra todos los clientes -
4.2 Integrar en
agents/runtime.go:- En
New(): sicfg.Tools.MCP.Enabled && len(cfg.Tools.MCP.Servers) > 0, crearmcp.NewManager(...) - Llamar
manager.AllTools(toolReg)para registrar las tools MCP en el registry - Guardar manager en
Agentstruct para cerrar enRun()defer - Las tools MCP aparecen automaticamente en el function calling del LLM
- En
-
4.3 Anadir campo
mcpManageral structAgenty cerrar enRun():type Agent struct { // ...existing fields... mcpManager *shellmcp.Manager // nil when MCP client is disabled }
Fase 5: Ejemplo de configuracion
-
5.1 Documentar ejemplo con servidor MCP stdio (ej: brave-search, filesystem):
tools: mcp: enabled: true servers: - name: brave-search command: npx args: ["-y", "@anthropic/mcp-server-brave-search"] env: BRAVE_API_KEY: "${BRAVE_API_KEY}" prefix: "brave_" - name: filesystem command: npx args: ["-y", "@anthropic/mcp-server-filesystem", "/home/data"] prefix: "fs_" - name: remote-tools url: "http://localhost:8080/mcp" tools: ["search", "summarize"] # solo estas tools prefix: "remote_" -
5.2 Probar con al menos un servidor MCP real (brave-search o filesystem) en un agente de prueba.
Fase 6: Tests
- 6.1 Unit tests para
tools/mcptools/mcp.go— verificar conversion de schema MCP → tools.Def - 6.2 Unit tests para
shell/mcp/client.go— mock del protocolo MCP (o test con echo server) - 6.3 Integration test: un agente con MCP habilitado lista tools MCP en su registry
Fase 7: Cleanup y docs
- 7.1 Actualizar
CLAUDE.md— anadirshell/mcp/,tools/mcptools/a la estructura - 7.2 Actualizar
.claude/rules/create_tool.mdsi es necesario — mencionar que tools MCP se auto-registran - 7.3 Mover o refactorizar
shell/protocols/mcp.go(MCP server) ashell/mcp/server.gopara colocarlo junto al client
Ejemplo de flujo completo
1. Agente arranca, config tiene tools.mcp.servers con brave-search (stdio)
2. runtime.go → mcp.NewManager() → lanza `npx -y @anthropic/mcp-server-brave-search`
→ Initialize → ListTools → descubre: web_search, local_search
3. mcptools.FromMCPServer() convierte:
- mcp.Tool{name: "web_search", ...} → tools.Tool{Def: {Name: "brave_web_search", ...}, Exec: wrapper}
- mcp.Tool{name: "local_search", ...} → tools.Tool{Def: {Name: "brave_local_search", ...}, Exec: wrapper}
4. Se registran en el toolReg → aparecen en ToLLMSpecs()
5. Usuario pregunta: "busca noticias sobre Go 1.23"
→ LLM ve brave_web_search en sus tools → genera tool_call
→ runtime ejecuta → wrapper llama mcpClient.CallTool("web_search", args)
→ resultado vuelve al LLM → genera respuesta final
Decisiones de diseno
- Prefix por servidor: evita colisiones de nombres entre servidores MCP que tengan tools con el mismo nombre. Configurable por servidor.
- Filter de tools: permite exponer solo un subset de tools de un servidor MCP (seguridad + reducir contexto del LLM).
- Manager pattern: centraliza lifecycle de multiples clientes MCP. Similar a como el bus manager gestiona multiples agentes.
- Stdio como transporte principal: es el estandar de facto en MCP. Los servidores mas populares (brave, filesystem, github, etc.) usan stdio.
- Auto-discovery: las tools se descubren automaticamente via
ListTools(). No hace falta declararlas manualmente. - Sin tipos puros nuevos:
tools.Defytools.Paramya cubren la especificacion de una tool. No se necesita nada enpkg/.
Riesgos
- Subprocesos zombie: si el agente crashea, los procesos MCP stdio pueden quedar huerfanos. Mitigar con process groups y cleanup en
Close(). - Latencia de inicio:
npx -ydescarga paquetes la primera vez. Puede tardar. Considerar cache o pre-instalacion. - Schema complejo: algunos MCP servers tienen input schemas con nested objects/arrays. La conversion a
tools.Paramdebe manejar esto (al menosobjectyarraycomo tipos). - Seguridad: un servidor MCP malicioso podria exponer tools daninas. El filtro de tools y el prefix ayudan, pero la confianza es del operador.
- Timeout: llamadas a MCP servers externos pueden ser lentas. Timeout configurable por servidor.
Dependencias
github.com/mark3labs/mcp-go v0.44.1— ya en go.mod, incluyeclientpackage- No se necesitan dependencias nuevas