feat(0145-2,3,4): schema + launcher wiring + claude --mcp-config arg

Pieza 2 — schema (internal/config/schema.go):
- DeviceMeshConfig.ExposeViaMCP *bool: pointer para distinguir "no
  establecido" vs "false explicito". Helper ShouldExposeViaMCP() devuelve
  true cuando enabled && (nil || *true).
- ClaudeCodeCfg.MCPConfigPath y MCPServerName: poblados en runtime por
  la launcher, NUNCA por YAML.

Pieza 3 — launcher wiring (devagents/mcp_bridge.go + cmd/launcher/main.go):
- ApplyMCPBridge(cfg, logger): si DeviceMesh.ShouldExposeViaMCP() y
  provider=claude-code, resuelve binario devicemesh-mcp (junto al
  launcher), URL device_agent (env override > YAML), lista tools allowed
  (RegisterBuiltins + FilterByAllowed igual que registry_build.go), y
  escribe /tmp/<agent_id>-mcp-config.json (0600).
- Aplica overrides a cfg.LLM.Primary.ClaudeCode: MCPConfigPath,
  AllowedTools (formato mcp__<server>__<tool>), DisableTools=false
  defensivo.
- Launcher main.go llama ApplyMCPBridge inmediatamente despues de
  config.Load, ANTES de devagents.New (que es donde se construye el
  CompleteFunc del provider).

Pieza 4 — claude args (shell/llm/claudecode.go):
- buildClaudeArgs ahora emite "--mcp-config <path>" cuando
  cfg.MCPConfigPath no esta vacio.
- Guard defensivo: DisableTools=true + AllowedTools no vacio ahora
  produce solo --allowedTools (efectivamente ignora DisableTools). El
  launcher ya lo previene en ApplyMCPBridge, pero esto protege a
  callers directos.

Build limpio con goolm.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 18:28:34 +02:00
parent 15596df7e4
commit b92a350023
4 changed files with 368 additions and 1 deletions
+33
View File
@@ -78,6 +78,27 @@ type DeviceMeshConfig struct {
// client_timeout_s; we accept both. When both set, ClientTimeoutS wins
// when non-zero.
ClientTimeoutS int `yaml:"client_timeout_s,omitempty"`
// ExposeViaMCP gates the MCP bridge (issue 0145). When the field is
// absent from YAML, the launcher defaults to "expose" (true) so an
// agent with device_mesh.enabled=true gets the bridge for free. The
// pointer shape lets us distinguish "unset" from "explicitly false";
// use ShouldExposeViaMCP() to read it.
ExposeViaMCP *bool `yaml:"expose_via_mcp,omitempty"`
}
// ShouldExposeViaMCP reports whether the launcher must build the MCP bridge
// for this device-mesh block. Returns false when the block is nil or not
// enabled; otherwise returns true unless ExposeViaMCP is explicitly false.
// Pure function — used by both the launcher and tests.
func (d *DeviceMeshConfig) ShouldExposeViaMCP() bool {
if d == nil || !d.Enabled {
return false
}
if d.ExposeViaMCP != nil {
return *d.ExposeViaMCP
}
return true
}
// ResolvedHost returns Host if non-empty, otherwise DeviceID. Used by the
@@ -211,6 +232,18 @@ type ClaudeCodeCfg struct {
AddDirs []string `yaml:"add_dirs"` // additional directories accessible
Streaming bool `yaml:"streaming"` // use --output-format stream-json for realtime progress
ShowToolProgress bool `yaml:"show_tool_progress"` // edit Matrix message to show tool usage progress
// MCPConfigPath points to a JSON file consumed by `claude -p --mcp-config`.
// Set at runtime by the launcher (issue 0145) when the agent has
// device_mesh.enabled=true and ExposeViaMCP. Empty means claude runs
// without external MCP servers. NEVER set in YAML — overrides the
// runtime-generated bridge.
MCPConfigPath string `yaml:"mcp_config_path,omitempty"`
// MCPServerName is the key inside the mcp-config JSON's "mcpServers"
// map. claude prefixes tool names exposed to the model as
// `mcp__<MCPServerName>__<tool>`. Defaults to "devicemesh" when empty.
MCPServerName string `yaml:"mcp_server_name,omitempty"`
}
type LLMReasoningCfg struct {