Anade el binario standalone cmd/devicemesh-mcp/ que expone via JSON-RPC
sobre stdio el catalogo de devicemesh tools (exec, shell.eval, fs.*,
git.*, pkg.*, proc.*, docker.*) al claude -p parent.
Arquitectura issue 0145:
- main.go: flags (--device-agent, --mode, --tools-allowed, --server-name),
inicializa devicemesh.Client + RegisterBuiltins + FilterByAllowed, lanza
server.ServeStdio del SDK mark3labs/mcp-go (ya dep).
- bridge.go: registra cada ToolSpec como mcp.Tool con WithRawInputSchema +
handler que invoca ToolRegistry.Call (validate->map->HTTP->map). Resultado
serializado a NewToolResultText, errores como NewToolResultError para que
el modelo se autocorrija.
Razon: hoy claude -p ve nuestras tool names solo como TEXTO en el system
prompt y las imita sin ejecutar. Con --mcp-config apuntando a este binario,
claude las descubre via tools/list e invoca via tools/call REALMENTE.
Smoke OK: initialize frame produce {capabilities:{tools:{listChanged:true}},
serverInfo:{name:"devicemesh",version:"0.1.0"}}.
Issue doc 0145 incluido con aceptacion A3 anti-hallucination + DoD triada.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hacer que claude -p (subprocess que usa el provider claude-code de cada agent) invoque REALMENTE las 14+ tools de pkg/tools/devicemesh (exec, shell.eval, fs.*, git.*, pkg.*, proc.*, docker.*) en lugar de imitar el formato como texto. Esto se logra exponiendo el ToolRegistry per-agent como un servidor MCP (Model Context Protocol) que claude descubre via --mcp-config y consume via JSON-RPC stdio.
Contexto
Hoy claude -p se invoca con disable_tools: true → --tools "", y las tools de device-mesh viven solo en el system prompt como descripcion textual. Resultado:
claude imita el formato ({"tool": "exec", ...}) pero NO ejecuta nada.
El audit chain del device_agent queda vacio tras un "exec" anunciado por el bot.
Anti-criterio A3 del flow 0009 (anti-hallucination) falla: el bot dice que hizo algo, el device no recibe nada.
El fix correcto es darle a claude un transporte real para invocar tools. MCP es el contrato nativo de claude-code:
Cada agent levanta su propio MCP server (binario Go child de claude).
claude descubre tools via tools/list, invoca via tools/call.
El binario MCP traduce tools/call → ToolRegistry.Call → HTTP al device_agent remoto.
claude ve los resultados reales, audit DB se llena, anti-hallucination pasa.
operator → Matrix DM → agent-wsl-lucas
→ claude -p --mcp-config /tmp/agent-wsl-lucas-mcp-config.json --allowedTools "mcp__devicemesh__exec" ...
→ claude spawna ./bin/devicemesh-mcp como child
→ claude envia tools/list → devicemesh-mcp responde con 14 tools
→ claude decide ejecutar exec
→ claude envia tools/call name=exec args={argv:["ls"]}
→ devicemesh-mcp llama ToolRegistry.Call("exec", {argv:["ls"]})
→ POST http://10.42.0.10:7474/capability {capability:"shell.exec", args:{argv:["ls"]}}
→ device_agent ejecuta, registra en audit.db, devuelve resultado
→ devicemesh-mcp empaqueta como MCP {content:[{type:"text", text:"<JSON>"}]}
→ claude recibe resultado real, lo razona, responde al operador
Tareas
Pieza 1 — Binario cmd/devicemesh-mcp/
cmd/devicemesh-mcp/main.go — entrypoint con flags --device-agent, --mode, --tools-allowed. Inicializa Client + RegisterBuiltins + FilterByAllowed. Lanza loop stdio via mcp-go server.ServeStdio.
cmd/devicemesh-mcp/bridge.go — adapter: itera ToolRegistry.List() y registra cada spec como MCP tool, con handler que invoca reg.Call(ctx, name, args) y devuelve mcp.NewToolResultText(<json>) o mcp.NewToolResultError(<msg>).
Build target: bin/devicemesh-mcp.
Pieza 2 — Schema config
internal/config/schema.go:
ClaudeCodeCfg: anadir MCPConfigPath string y MCPServerName string (default "devicemesh").
DeviceMeshConfig: anadir ExposeViaMCP *bool (puntero para distinguir "no establecido" vs "false explicito"). Helper ShouldExposeViaMCP() que devuelve true cuando enabled && (nil || *true).
Resuelve binario bin/devicemesh-mcp relativo al ejecutable del launcher.
Resuelve URL device_agent (env override igual que buildDeviceMeshRegistry).
Construye lista de tools allowed.
Genera el JSON de mcp-config en /tmp/<agent_id>-mcp-config.json (mode 0600).
Devuelve (configPath, allowedToolNames, err).
devagents/runtime.go o cmd/launcher/main.go: tras cargar config si DeviceMesh.Enabled && ShouldExposeViaMCP, llamar BuildMCPBridge y aplicar overrides a cfg.LLM.Primary.ClaudeCode (MCPConfigPath, AllowedTools, DisableTools=false). Logging explicito.
Pieza 4 — shell/llm/claudecode.go
En buildClaudeArgs: si cfg.MCPConfigPath != "", append --mcp-config <path>.
Validacion defensiva: si DisableTools=true y AllowedTools no vacio, log warning + ignorar DisableTools (AllowedTools tiene prioridad).
MCP via mcp-go SDK en vez de implementar JSON-RPC raw. La dep github.com/mark3labs/mcp-go v0.44.1 ya existe (shell/mcp/server.go ya la usa). Usar server.ServeStdio reduce superficie de bugs y test surface.
Binario standalone (cmd/devicemesh-mcp/) en vez de embebido en el launcher. Razon: claude lo lanza como child via --mcp-config — necesita un ejecutable separado. Tambien permite debuggear en aislamiento (echo ... | bin/devicemesh-mcp).
MCPConfigPath en /tmp/ (no en <agent_dir>/data/). El path es runtime-only, regenerable cada arranque, contiene path absoluto al binario del launcher actual + URL devicemesh. Persistirlo en repo crea drift PC↔VPS.