Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions internal/pod/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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{}
Expand Down
69 changes: 69 additions & 0 deletions internal/pod/parser_mcp_stdio_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down