Files
agents_and_robots/dev/issues/0145-mcp-bridge-claude-code-devicemesh.md
T
egutierrez 15596df7e4 feat(0145-1): binario devicemesh-mcp + issue doc
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>
2026-05-24 18:26:22 +02:00

7.8 KiB

id, title, status, type, domain, scope, priority, depends, related_flows, related_issues, created, updated, tags, flow
id title status type domain scope priority depends related_flows related_issues created updated tags flow
0145 MCP bridge claude-code → devicemesh tools pending feature
agents
llm
mcp
devicemesh
app high
0134
0144
0009
0134
0144
2026-05-24 2026-05-24
mcp
claude-code
devicemesh
agents
0009

0145 — MCP bridge claude-code → devicemesh tools

Objetivo

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:

  1. Cada agent levanta su propio MCP server (binario Go child de claude).
  2. claude descubre tools via tools/list, invoca via tools/call.
  3. El binario MCP traduce tools/callToolRegistry.Call → HTTP al device_agent remoto.
  4. claude ve los resultados reales, audit DB se llena, anti-hallucination pasa.

Arquitectura

agents_and_robots (VPS)
├─ launcher (Go)
│  └─ devagents.New(cfg)
│     ├─ buildDeviceMeshRegistry()  -- per-agent ToolRegistry
│     ├─ buildMCPConfig()           -- escribe /tmp/<agent_id>-mcp-config.json
│     └─ override cfg.LLM.Primary.ClaudeCode (MCPConfigPath, AllowedTools, DisableTools=false)
│
└─ bin/devicemesh-mcp (binario standalone)
   ├─ stdin  ← JSON-RPC frames del claude parent
   ├─ stdout → JSON-RPC responses
   ├─ tools/list  → enumera 14+ tools del registry filtered
   └─ tools/call  → dispatch HTTP al device_agent
                    via pkg/tools/devicemesh.NewClient + RegisterBuiltins

Flujo real una vez activado:

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).

Pieza 3 — Launcher integration

  • devagents/mcp_bridge.go — funcion BuildMCPBridge(cfg, logger) que:
    • 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).

Pieza 5 — Tests

  • cmd/devicemesh-mcp/main_test.go:
    • TestInitialize — frame initialize → serverInfo + capabilities.
    • TestToolsList — frame tools/list → 14+ tools con inputSchema. Mock device-agent via httptest.
    • TestToolsCallExec — tools/call name=exec → device-agent devuelve stdout=hi → assert MCP content contiene "hi".
    • TestToolsCallInvalidTool — tools/call name=nonexistent → assert isError.
    • TestNotificationsInitialized — notification (no id) → assert NO response.
    • TestUserModeFilter — --mode user → pkg.install NO listado; --mode sudo → si.
  • cmd/devicemesh-mcp/integration_test.go — spawn subprocess + secuencia completa.
  • devagents/mcp_bridge_test.go — assert config JSON valido, allowed_tools formato mcp__<server>__<tool>, override DisableTools.

Pieza 6 — Build + smoke

  1. go build -tags goolm -o bin/devicemesh-mcp ./cmd/devicemesh-mcp clean.
  2. go build -tags goolm -o bin/launcher ./cmd/launcher clean.
  3. Smoke test del binario: echo '{"jsonrpc":"2.0","id":1,"method":"initialize",...}' | bin/devicemesh-mcp produce JSON-RPC response.
  4. Deploy a VPS + restart agents_and_robots.service.
  5. Verificar /tmp/agent-wsl-lucas-mcp-config.json se genera tras restart + logs muestran tools registered + claude-code-with-MCP.

Aceptacion (anti-criterio A3 anti-hallucination)

  • Al pedirle a agent-wsl-lucas que ejecute ls, una entry aparece en audit.db del device dentro de 5s.
  • claude -p logs muestran tool_use: mcp__devicemesh__exec (no texto imitado).
  • /tmp/<agent_id>-mcp-config.json valido, mode 0600.
  • bin/devicemesh-mcp standalone responde a initialize/tools/list/tools/call en JSON-RPC.

DoD triada por capas

Capa Verificacion
Binario MCP bin/devicemesh-mcp build clean + tests passing
Launcher /tmp/<agent_id>-mcp-config.json generado + cfg overrides aplicados
claude args --mcp-config <path> + --allowedTools mcp__devicemesh__* presentes
Smoke real Audit DB del device crece tras prompt al agent

Decisiones de diseno

  1. 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.
  2. 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).
  3. 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.