diff --git a/internal/pod/parser.go b/internal/pod/parser.go index 5b60b70..9535e99 100644 --- a/internal/pod/parser.go +++ b/internal/pod/parser.go @@ -221,7 +221,11 @@ func Parse(r io.Reader) (*Pod, error) { if skills == nil { skills = make([]string, 0) } - handles, err := parseHandles(mergeHandleDefaults(raw.XClaw.HandlesDefaults, svc.XClaw.Handles)) + rawHandles := svc.XClaw.Handles + if svc.XClaw.MCPStdio == nil { + rawHandles = mergeHandleDefaults(raw.XClaw.HandlesDefaults, svc.XClaw.Handles) + } + handles, err := parseHandles(rawHandles) if err != nil { return nil, fmt.Errorf("service %q: parse handles: %w", name, err) } @@ -714,14 +718,21 @@ func expandPodDefaults(root map[string]interface{}) error { } } - if err := applyRawPodDefaults(rawBlock, defaults); err != nil { - return fmt.Errorf("service %q: %w", name, err) + if !rawBlockHasMCPStdio(rawBlock) { + if err := applyRawPodDefaults(rawBlock, defaults); err != nil { + return fmt.Errorf("service %q: %w", name, err) + } } } return nil } +func rawBlockHasMCPStdio(raw map[string]interface{}) bool { + _, ok := raw["mcp-stdio"] + return ok +} + type podDefaults struct { CllamaDefaults map[string]interface{} ModelsDefaults map[string]interface{} diff --git a/internal/pod/parser_mcp_stdio_test.go b/internal/pod/parser_mcp_stdio_test.go index 79b2ed9..bb84fc6 100644 --- a/internal/pod/parser_mcp_stdio_test.go +++ b/internal/pod/parser_mcp_stdio_test.go @@ -40,6 +40,75 @@ services: } } +func TestParseMCPStdioDoesNotInheritPodAgentDefaults(t *testing.T) { + p, err := Parse(strings.NewReader(` +x-claw: + handles-defaults: + discord: + guilds: + - id: "guild-1" + name: "Trading Floor" + cllama-defaults: + proxy: [passthrough] + env: + OPENROUTER_API_KEY: ${OPENROUTER_API_KEY} + surfaces-defaults: + - "service://trading-api" + feeds-defaults: + - market-context + tools-defaults: + - trading-api + skills-defaults: + - ./skills/shared.md + +services: + perplexity: + image: ghcr.io/mostlydev/claw-mcp-stdio:v0.12.0 + x-claw: + mcp-stdio: + command: npx + args: ["-y", "perplexity-mcp"] + agent: + image: agent:latest + x-claw: + agent: ./AGENTS.md + handles: + discord: + id: "agent-id" +`)) + if err != nil { + t.Fatalf("Parse: %v", err) + } + + sidecar := p.Services["perplexity"] + if sidecar == nil || sidecar.Claw == nil || sidecar.Claw.MCPStdio == nil { + t.Fatalf("expected mcp-stdio sidecar, got %+v", sidecar) + } + if len(sidecar.Claw.Cllama) != 0 { + t.Fatalf("mcp-stdio sidecar inherited cllama defaults: %v", sidecar.Claw.Cllama) + } + if len(sidecar.Claw.CllamaEnv) != 0 { + t.Fatalf("mcp-stdio sidecar inherited cllama env: %v", sidecar.Claw.CllamaEnv) + } + if len(sidecar.Claw.Handles) != 0 { + t.Fatalf("mcp-stdio sidecar inherited handles defaults: %+v", sidecar.Claw.Handles) + } + if len(sidecar.Claw.Surfaces) != 0 || len(sidecar.Claw.Feeds) != 0 || len(sidecar.Claw.Tools) != 0 || len(sidecar.Claw.Skills) != 0 { + t.Fatalf("mcp-stdio sidecar inherited agent defaults: surfaces=%v feeds=%v tools=%v skills=%v", sidecar.Claw.Surfaces, sidecar.Claw.Feeds, sidecar.Claw.Tools, sidecar.Claw.Skills) + } + + agent := p.Services["agent"] + if agent == nil || agent.Claw == nil { + t.Fatal("expected agent service") + } + if len(agent.Claw.Cllama) != 1 || agent.Claw.Cllama[0] != "passthrough" { + t.Fatalf("agent did not inherit cllama defaults: %v", agent.Claw.Cllama) + } + if agent.Claw.Handles["discord"] == nil || len(agent.Claw.Handles["discord"].Guilds) != 1 { + t.Fatalf("agent did not inherit handle topology: %+v", agent.Claw.Handles) + } +} + func TestParseMCPStdioValidation(t *testing.T) { tests := []struct { name string