diff --git a/pkg/agent/mcp_bootstrap_test.go b/pkg/agent/mcp_bootstrap_test.go deleted file mode 100644 index 2e22453ba..000000000 --- a/pkg/agent/mcp_bootstrap_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package agent - -import ( - "testing" - "time" - - "github.com/sipeed/picoclaw/pkg/config" - "github.com/sipeed/picoclaw/pkg/mcp" -) - -func TestCalculateMCPDiscoveryTimeout_UsesMaxInitWithGrace(t *testing.T) { - serverConfigs := map[string]struct { - initSeconds int - }{ - "fast": {initSeconds: 5}, - "slow": {initSeconds: 60}, - } - - cfg := config.MCPToolsConfig{ - Enabled: true, - Servers: map[string]config.MCPServerConfig{ - "fast": { - Enabled: true, - Command: "fast", - InitTimeoutSeconds: serverConfigs["fast"].initSeconds, - }, - "slow": { - Enabled: true, - Command: "slow", - InitTimeoutSeconds: serverConfigs["slow"].initSeconds, - }, - }, - } - - mcpConfigs := buildMCPServerConfigs(cfg) - timeout := calculateMCPDiscoveryTimeout(mcpConfigs) - - want := 65 * time.Second - if timeout != want { - t.Fatalf("calculateMCPDiscoveryTimeout() = %v, want %v", timeout, want) - } -} - -func TestBuildMCPServerConfigs_SkipsDisabledServers(t *testing.T) { - cfg := config.MCPToolsConfig{ - Enabled: true, - Servers: map[string]config.MCPServerConfig{ - "context7": { - Enabled: true, - Command: "context7-mcp", - Protocol: "jsonl", - }, - "disabled": { - Enabled: false, - Command: "ignored", - }, - }, - } - - mcpConfigs := buildMCPServerConfigs(cfg) - if len(mcpConfigs) != 1 { - t.Fatalf("buildMCPServerConfigs() count = %d, want 1", len(mcpConfigs)) - } - - context7, ok := mcpConfigs["context7"] - if !ok { - t.Fatalf("context7 not found in buildMCPServerConfigs output") - } - if context7.Protocol != "jsonl" { - t.Fatalf("context7 protocol = %q, want jsonl", context7.Protocol) - } -} - -func TestInferMCPProtocol_Context7DefaultsToJSONL(t *testing.T) { - got := inferMCPProtocol("", "context7-mcp") - if got != mcp.ProtocolJSONLines { - t.Fatalf("inferMCPProtocol() = %q, want %s", got, mcp.ProtocolJSONLines) - } -} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index a517dbb5c..febfd0456 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -150,17 +150,6 @@ func TestDefaultConfig_WebTools(t *testing.T) { } } -func TestDefaultConfig_MCPTools(t *testing.T) { - cfg := DefaultConfig() - - if cfg.Tools.MCP.Enabled { - t.Error("MCP tools should be disabled by default") - } - if cfg.Tools.MCP.Servers == nil { - t.Error("MCP servers map should be initialized") - } -} - func TestSaveConfig_FilePermissions(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("file permission bits are not enforced on Windows") @@ -215,58 +204,3 @@ func TestConfig_Complete(t *testing.T) { t.Error("Heartbeat should be enabled by default") } } - -func TestLoadConfig_LegacyMCPServersCompatibility(t *testing.T) { - tmpDir := t.TempDir() - configPath := filepath.Join(tmpDir, "config.json") - - configJSON := `{ - "agents": { - "defaults": { - "workspace": "~/.picoclaw/workspace", - "model": "test-model", - "max_tokens": 1024, - "temperature": 0.7, - "max_tool_iterations": 10 - } - }, - "mcpServers": { - "context7": { - "type": "stdio", - "protocol": "jsonl", - "command": "npx", - "args": ["-y", "@upstash/context7-mcp", "--api-key", "test-key"] - } - } - }` - - if err := os.WriteFile(configPath, []byte(configJSON), 0600); err != nil { - t.Fatalf("WriteFile failed: %v", err) - } - - cfg, err := LoadConfig(configPath) - if err != nil { - t.Fatalf("LoadConfig failed: %v", err) - } - - if !cfg.Tools.MCP.Enabled { - t.Fatal("Tools.MCP should be enabled from legacy mcpServers") - } - - server, ok := cfg.Tools.MCP.Servers["context7"] - if !ok { - t.Fatal("context7 server not mapped from legacy mcpServers") - } - if !server.Enabled { - t.Fatal("context7 server should be enabled") - } - if server.Command != "npx" { - t.Fatalf("context7 command = %q, want npx", server.Command) - } - if server.Protocol != "jsonl" { - t.Fatalf("context7 protocol = %q, want jsonl", server.Protocol) - } - if len(server.Args) == 0 { - t.Fatal("context7 args should be mapped") - } -} diff --git a/pkg/mcp/client_test.go b/pkg/mcp/client_test.go deleted file mode 100644 index 5411f93e1..000000000 --- a/pkg/mcp/client_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package mcp - -import "testing" - -func TestNormalizeProtocol(t *testing.T) { - tests := []struct { - input string - want string - }{ - {input: "", want: ProtocolMCPFrames}, - {input: "mcp", want: ProtocolMCPFrames}, - {input: "jsonl", want: ProtocolJSONLines}, - {input: "JSONL", want: ProtocolJSONLines}, - {input: "unknown", want: ProtocolMCPFrames}, - } - - for _, tt := range tests { - got := normalizeProtocol(tt.input) - if got != tt.want { - t.Fatalf("normalizeProtocol(%q) = %q, want %q", tt.input, got, tt.want) - } - } -} diff --git a/pkg/mcp/format_test.go b/pkg/mcp/format_test.go deleted file mode 100644 index cf4e4bcb4..000000000 --- a/pkg/mcp/format_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package mcp - -import ( - "encoding/json" - "strings" - "testing" -) - -func TestFormatCallPayload_TextAndStructured(t *testing.T) { - raw := json.RawMessage(`{ - "content":[{"type":"text","text":"hello"}], - "structuredContent":{"ok":true} - }`) - - result, err := formatCallPayload(raw, 4096) - if err != nil { - t.Fatalf("formatCallPayload() error = %v", err) - } - if result.IsError { - t.Fatalf("expected IsError=false") - } - if !strings.Contains(result.Content, "hello") { - t.Fatalf("expected content to contain text block, got %q", result.Content) - } - if !strings.Contains(result.Content, `"ok":true`) { - t.Fatalf("expected content to contain structured content, got %q", result.Content) - } -} - -func TestFormatCallPayload_Truncates(t *testing.T) { - raw := json.RawMessage(`{"content":[{"type":"text","text":"abcdefghijklmnopqrstuvwxyz"}]}`) - - result, err := formatCallPayload(raw, 12) - if err != nil { - t.Fatalf("formatCallPayload() error = %v", err) - } - if len(result.Content) != 12 { - t.Fatalf("expected truncated length 12, got %d", len(result.Content)) - } -} - -func TestFormatCallPayload_RespectsIsError(t *testing.T) { - raw := json.RawMessage(`{"content":[{"type":"text","text":"failed"}],"isError":true}`) - - result, err := formatCallPayload(raw, 4096) - if err != nil { - t.Fatalf("formatCallPayload() error = %v", err) - } - if !result.IsError { - t.Fatalf("expected IsError=true") - } -} diff --git a/pkg/mcp/manager_test.go b/pkg/mcp/manager_test.go deleted file mode 100644 index 3560a7533..000000000 --- a/pkg/mcp/manager_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package mcp - -import ( - "context" - "testing" -) - -type fakeClient struct { - tools []RemoteTool - callResult CallResult - callErr error - - lastToolName string - lastArgs map[string]any -} - -func (f *fakeClient) Start(_ context.Context) error { return nil } -func (f *fakeClient) ListTools(_ context.Context) ([]RemoteTool, error) { - return f.tools, nil -} -func (f *fakeClient) CallTool(_ context.Context, toolName string, arguments map[string]any) (CallResult, error) { - f.lastToolName = toolName - f.lastArgs = arguments - if f.callErr != nil { - return CallResult{}, f.callErr - } - return f.callResult, nil -} -func (f *fakeClient) Close() error { return nil } - -func TestManager_DiscoverTools_FilterAndCall(t *testing.T) { - serverCfg := map[string]ServerConfig{ - "Local Dev": { - Command: "fake", - IncludeTools: []string{"alpha", "beta"}, - ExcludeTools: []string{"beta"}, - }, - } - manager := NewManager(serverCfg) - - client := &fakeClient{ - tools: []RemoteTool{ - {Name: "alpha", Description: "tool alpha"}, - {Name: "beta", Description: "tool beta"}, - {Name: "gamma", Description: "tool gamma"}, - }, - callResult: CallResult{Content: "ok"}, - } - manager.newClient = func(_ ServerConfig) Client { - return client - } - - tools, err := manager.DiscoverTools(context.Background()) - if err != nil { - t.Fatalf("DiscoverTools() error = %v", err) - } - if len(tools) != 1 { - t.Fatalf("DiscoverTools() returned %d tools, want 1", len(tools)) - } - - tool := tools[0] - if tool.ToolName != "alpha" { - t.Fatalf("discovered tool = %q, want alpha", tool.ToolName) - } - - result, err := manager.CallTool(context.Background(), tool.QualifiedName, map[string]any{"x": 1}) - if err != nil { - t.Fatalf("CallTool() error = %v", err) - } - if result.Content != "ok" { - t.Fatalf("CallTool() content = %q, want ok", result.Content) - } - if client.lastToolName != "alpha" { - t.Fatalf("called MCP tool = %q, want alpha", client.lastToolName) - } -} - -func TestManager_NormalizeEmptySchema(t *testing.T) { - serverCfg := map[string]ServerConfig{ - "srv": {Command: "fake"}, - } - manager := NewManager(serverCfg) - manager.newClient = func(_ ServerConfig) Client { - return &fakeClient{ - tools: []RemoteTool{{Name: "empty_schema", InputSchema: nil}}, - } - } - - tools, err := manager.DiscoverTools(context.Background()) - if err != nil { - t.Fatalf("DiscoverTools() error = %v", err) - } - if len(tools) != 1 { - t.Fatalf("DiscoverTools() returned %d tools, want 1", len(tools)) - } - - parameters := tools[0].Parameters - if parameters["type"] != "object" { - t.Fatalf("normalized schema type = %v, want object", parameters["type"]) - } -} diff --git a/pkg/mcp/naming_test.go b/pkg/mcp/naming_test.go deleted file mode 100644 index f23aefe5a..000000000 --- a/pkg/mcp/naming_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package mcp - -import "testing" - -func TestQualifiedToolName_SanitizesAndPrefixes(t *testing.T) { - got := QualifiedToolName("My Server", "Read-File!") - want := "mcp_my_server__read_file" - if got != want { - t.Fatalf("QualifiedToolName() = %q, want %q", got, want) - } -} - -func TestQualifiedToolName_TrimToMaxLen(t *testing.T) { - longToolName := "tool_name_with_many_segments_and_extra_text_that_exceeds_the_limit_significantly" - got := QualifiedToolName("server", longToolName) - if len(got) > qualifiedNameMaxLen { - t.Fatalf("qualified name length = %d, want <= %d", len(got), qualifiedNameMaxLen) - } -} diff --git a/pkg/tools/mcp_test.go b/pkg/tools/mcp_test.go deleted file mode 100644 index ee846a041..000000000 --- a/pkg/tools/mcp_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package tools - -import ( - "testing" - - "github.com/sipeed/picoclaw/pkg/mcp" -) - -func TestRegisterKnownMCPTools_RegistersAllTools(t *testing.T) { - registry := NewToolRegistry() - manager := &mcp.Manager{} - discovered := []mcp.RegisteredTool{ - { - QualifiedName: "mcp_context7__resolve_library_id", - ServerName: "context7", - ToolName: "resolve-library-id", - Description: "Resolve library ID", - Parameters: map[string]any{ - "type": "object", - }, - }, - { - QualifiedName: "mcp_context7__query_docs", - ServerName: "context7", - ToolName: "query-docs", - Description: "Query docs", - Parameters: map[string]any{ - "type": "object", - }, - }, - } - - count := RegisterKnownMCPTools(registry, manager, discovered) - if count != 2 { - t.Fatalf("RegisterKnownMCPTools count = %d, want 2", count) - } - - if _, ok := registry.Get("mcp_context7__resolve_library_id"); !ok { - t.Fatalf("expected mcp_context7__resolve_library_id to be registered") - } - if _, ok := registry.Get("mcp_context7__query_docs"); !ok { - t.Fatalf("expected mcp_context7__query_docs to be registered") - } -}