From ffc8bdba367510749ae06be3e35fe6972c7fa79c Mon Sep 17 00:00:00 2001 From: afjcjsbx Date: Wed, 13 May 2026 19:30:59 +0200 Subject: [PATCH] fix(mcp): normalize streamable-http before config validation --- cmd/picoclaw/internal/mcp/command_test.go | 22 ++++++++++++++++++++ cmd/picoclaw/internal/mcp/helpers.go | 25 +++++++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/cmd/picoclaw/internal/mcp/command_test.go b/cmd/picoclaw/internal/mcp/command_test.go index cae4a2c32..5924a8c61 100644 --- a/cmd/picoclaw/internal/mcp/command_test.go +++ b/cmd/picoclaw/internal/mcp/command_test.go @@ -315,6 +315,28 @@ func TestMCPAddSupportsStreamableHTTPAlias(t *testing.T) { assert.Equal(t, "https://mcp.context7.com/mcp", server.URL) } +func TestSaveValidatedConfigNormalizesStreamableHTTPAlias(t *testing.T) { + configPath := setupMCPConfigEnv(t) + + cfg := config.DefaultConfig() + cfg.Tools.MCP.Enabled = true + cfg.Tools.MCP.Servers = map[string]config.MCPServerConfig{ + "context7": { + Enabled: true, + Type: "streamable-http", + URL: "https://mcp.context7.com/mcp", + }, + } + + require.NoError(t, saveValidatedConfig(cfg)) + + saved := readMCPConfig(t, configPath) + server := saved.Tools.MCP.Servers["context7"] + assert.Equal(t, "http", server.Type) + assert.Equal(t, "https://mcp.context7.com/mcp", server.URL) + assert.Equal(t, "streamable-http", cfg.Tools.MCP.Servers["context7"].Type) +} + func TestMCPRemoveRemovesLastServerAndDisablesMCP(t *testing.T) { configPath := setupMCPConfigEnv(t) writeMCPConfig(t, configPath, &config.Config{ diff --git a/cmd/picoclaw/internal/mcp/helpers.go b/cmd/picoclaw/internal/mcp/helpers.go index 39124c787..e7122cc40 100644 --- a/cmd/picoclaw/internal/mcp/helpers.go +++ b/cmd/picoclaw/internal/mcp/helpers.go @@ -108,7 +108,9 @@ func saveValidatedConfig(cfg *config.Config) error { return fmt.Errorf("config is nil") } - data, err := json.Marshal(cfg) + normalizedCfg := normalizedConfigForSave(cfg) + + data, err := json.Marshal(normalizedCfg) if err != nil { return fmt.Errorf("failed to serialize config: %w", err) } @@ -117,13 +119,32 @@ func saveValidatedConfig(cfg *config.Config) error { return err } - if err := config.SaveConfig(internal.GetConfigPath(), cfg); err != nil { + if err := config.SaveConfig(internal.GetConfigPath(), normalizedCfg); err != nil { return fmt.Errorf("failed to save config: %w", err) } return nil } +func normalizedConfigForSave(cfg *config.Config) *config.Config { + clone := *cfg + if cfg.Tools.MCP.Servers == nil { + return &clone + } + + clone.Tools = cfg.Tools + clone.Tools.MCP = cfg.Tools.MCP + clone.Tools.MCP.Servers = make(map[string]config.MCPServerConfig, len(cfg.Tools.MCP.Servers)) + for name, server := range cfg.Tools.MCP.Servers { + if server.Type != "" { + server.Type = config.NormalizeMCPTransportType(server.Type) + } + clone.Tools.MCP.Servers[name] = server + } + + return &clone +} + func validateConfigDocument(data []byte) error { var instance map[string]any if err := json.Unmarshal(data, &instance); err != nil {