chore: fix lint issues in mcp and agent packages

- Fix gci import ordering in manager.go, manager_test.go
- Fix gofmt formatting in loop.go, manager.go, mcp_tool.go, mcp_tool_test.go
- Fix gofumpt formatting in manager_test.go
- Fix golines line length issues in manager.go, mcp_tool_test.go
- Fix wastedassign: replace redundant zero-value init with var declaration in loop.go
This commit is contained in:
yuchou87 2026-03-01 08:53:13 +08:00
parent 257b0d82b5
commit 077d7c8d9b
5 changed files with 186 additions and 94 deletions

View file

@ -58,7 +58,11 @@ type processOptions struct {
const defaultResponse = "I've completed processing but have no response to give. Increase `max_tool_iterations` in config.json."
func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers.LLMProvider) *AgentLoop {
func NewAgentLoop(
cfg *config.Config,
msgBus *bus.MessageBus,
provider providers.LLMProvider,
) *AgentLoop {
registry := NewAgentRegistry(cfg, provider)
// Register shared tools to all agents
@ -166,7 +170,7 @@ func (al *AgentLoop) Run(ctx context.Context) error {
if al.cfg.Tools.MCP.Enabled {
mcpManager := mcp.NewManager()
defaultAgent := al.registry.GetDefaultAgent()
workspacePath := ""
var workspacePath string
if defaultAgent != nil && defaultAgent.Workspace != "" {
workspacePath = defaultAgent.Workspace
} else {
@ -175,7 +179,7 @@ func (al *AgentLoop) Run(ctx context.Context) error {
if err := mcpManager.LoadFromMCPConfig(ctx, al.cfg.Tools.MCP, workspacePath); err != nil {
logger.WarnCF("agent", "Failed to load MCP servers, MCP tools will not be available",
map[string]interface{}{
map[string]any{
"error": err.Error(),
})
} else {
@ -183,7 +187,7 @@ func (al *AgentLoop) Run(ctx context.Context) error {
defer func() {
if err := mcpManager.Close(); err != nil {
logger.ErrorCF("agent", "Failed to close MCP manager",
map[string]interface{}{
map[string]any{
"error": err.Error(),
})
}
@ -208,7 +212,7 @@ func (al *AgentLoop) Run(ctx context.Context) error {
agent.Tools.Register(mcpTool)
totalRegistrations++
logger.DebugCF("agent", "Registered MCP tool",
map[string]interface{}{
map[string]any{
"agent_id": agentID,
"server": serverName,
"tool": tool.Name,
@ -218,7 +222,7 @@ func (al *AgentLoop) Run(ctx context.Context) error {
}
}
logger.InfoCF("agent", "MCP tools registered successfully",
map[string]interface{}{
map[string]any{
"server_count": len(servers),
"unique_tools": uniqueTools,
"total_registrations": totalRegistrations,
@ -367,7 +371,10 @@ func (al *AgentLoop) RecordLastChatID(chatID string) error {
return al.state.SetLastChatID(chatID)
}
func (al *AgentLoop) ProcessDirect(ctx context.Context, content, sessionKey string) (string, error) {
func (al *AgentLoop) ProcessDirect(
ctx context.Context,
content, sessionKey string,
) (string, error) {
return al.ProcessDirectWithChannel(ctx, content, sessionKey, "cli", "direct")
}
@ -388,7 +395,10 @@ func (al *AgentLoop) ProcessDirectWithChannel(
// ProcessHeartbeat processes a heartbeat request without session history.
// Each heartbeat is independent and doesn't accumulate context.
func (al *AgentLoop) ProcessHeartbeat(ctx context.Context, content, channel, chatID string) (string, error) {
func (al *AgentLoop) ProcessHeartbeat(
ctx context.Context,
content, channel, chatID string,
) (string, error) {
agent := al.registry.GetDefaultAgent()
if agent == nil {
return "", fmt.Errorf("no default agent for heartbeat")
@ -413,13 +423,16 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
} else {
logContent = utils.Truncate(msg.Content, 80)
}
logger.InfoCF("agent", fmt.Sprintf("Processing message from %s:%s: %s", msg.Channel, msg.SenderID, logContent),
logger.InfoCF(
"agent",
fmt.Sprintf("Processing message from %s:%s: %s", msg.Channel, msg.SenderID, logContent),
map[string]any{
"channel": msg.Channel,
"chat_id": msg.ChatID,
"sender_id": msg.SenderID,
"session_key": msg.SessionKey,
})
},
)
// Route system messages to processSystemMessage
if msg.Channel == "system" {
@ -480,9 +493,15 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
})
}
func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMessage) (string, error) {
func (al *AgentLoop) processSystemMessage(
ctx context.Context,
msg bus.InboundMessage,
) (string, error) {
if msg.Channel != "system" {
return "", fmt.Errorf("processSystemMessage called with non-system message channel: %s", msg.Channel)
return "", fmt.Errorf(
"processSystemMessage called with non-system message channel: %s",
msg.Channel,
)
}
logger.InfoCF("agent", "Processing system message",
@ -540,14 +559,22 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe
}
// runAgentLoop is the core message processing logic.
func (al *AgentLoop) runAgentLoop(ctx context.Context, agent *AgentInstance, opts processOptions) (string, error) {
func (al *AgentLoop) runAgentLoop(
ctx context.Context,
agent *AgentInstance,
opts processOptions,
) (string, error) {
// 0. Record last channel for heartbeat notifications (skip internal channels)
if opts.Channel != "" && opts.ChatID != "" {
// Don't record internal channels (cli, system, subagent)
if !constants.IsInternalChannel(opts.Channel) {
channelKey := fmt.Sprintf("%s:%s", opts.Channel, opts.ChatID)
if err := al.RecordLastChannel(channelKey); err != nil {
logger.WarnCF("agent", "Failed to record last channel", map[string]any{"error": err.Error()})
logger.WarnCF(
"agent",
"Failed to record last channel",
map[string]any{"error": err.Error()},
)
}
}
}
@ -629,7 +656,10 @@ func (al *AgentLoop) targetReasoningChannelID(channelName string) (chatID string
return ""
}
func (al *AgentLoop) handleReasoning(ctx context.Context, reasoningContent, channelName, channelID string) {
func (al *AgentLoop) handleReasoning(
ctx context.Context,
reasoningContent, channelName, channelID string,
) {
if reasoningContent == "" || channelName == "" || channelID == "" {
return
}
@ -697,22 +727,33 @@ func (al *AgentLoop) runLLMIteration(
callLLM := func() (*providers.LLMResponse, error) {
if len(agent.Candidates) > 1 && al.fallback != nil {
fbResult, fbErr := al.fallback.Execute(ctx, agent.Candidates,
fbResult, fbErr := al.fallback.Execute(
ctx,
agent.Candidates,
func(ctx context.Context, provider, model string) (*providers.LLMResponse, error) {
return agent.Provider.Chat(ctx, messages, providerToolDefs, model, map[string]any{
"max_tokens": agent.MaxTokens,
"temperature": agent.Temperature,
"prompt_cache_key": agent.ID,
})
return agent.Provider.Chat(
ctx,
messages,
providerToolDefs,
model,
map[string]any{
"max_tokens": agent.MaxTokens,
"temperature": agent.Temperature,
"prompt_cache_key": agent.ID,
},
)
},
)
if fbErr != nil {
return nil, fbErr
}
if fbResult.Provider != "" && len(fbResult.Attempts) > 0 {
logger.InfoCF("agent", fmt.Sprintf("Fallback: succeeded with %s/%s after %d attempts",
fbResult.Provider, fbResult.Model, len(fbResult.Attempts)+1),
map[string]any{"agent_id": agent.ID, "iteration": iteration})
logger.InfoCF(
"agent",
fmt.Sprintf("Fallback: succeeded with %s/%s after %d attempts",
fbResult.Provider, fbResult.Model, len(fbResult.Attempts)+1),
map[string]any{"agent_id": agent.ID, "iteration": iteration},
)
}
return fbResult.Response, nil
}
@ -738,10 +779,14 @@ func (al *AgentLoop) runLLMIteration(
strings.Contains(errMsg, "length")
if isContextError && retry < maxRetries {
logger.WarnCF("agent", "Context window error detected, attempting compression", map[string]any{
"error": err.Error(),
"retry": retry,
})
logger.WarnCF(
"agent",
"Context window error detected, attempting compression",
map[string]any{
"error": err.Error(),
"retry": retry,
},
)
if retry == 0 && !constants.IsInternalChannel(opts.Channel) {
al.bus.PublishOutbound(ctx, bus.OutboundMessage{
@ -773,7 +818,12 @@ func (al *AgentLoop) runLLMIteration(
return "", iteration, fmt.Errorf("LLM call failed after retries: %w", err)
}
go al.handleReasoning(ctx, response.Reasoning, opts.Channel, al.targetReasoningChannelID(opts.Channel))
go al.handleReasoning(
ctx,
response.Reasoning,
opts.Channel,
al.targetReasoningChannelID(opts.Channel),
)
logger.DebugCF("agent", "LLM response",
map[string]any{
@ -1075,7 +1125,11 @@ func formatMessagesForLog(messages []providers.Message) string {
for _, tc := range msg.ToolCalls {
fmt.Fprintf(&sb, " - ID: %s, Type: %s, Name: %s\n", tc.ID, tc.Type, tc.Name)
if tc.Function != nil {
fmt.Fprintf(&sb, " Arguments: %s\n", utils.Truncate(tc.Function.Arguments, 200))
fmt.Fprintf(
&sb,
" Arguments: %s\n",
utils.Truncate(tc.Function.Arguments, 200),
)
}
}
}
@ -1104,7 +1158,11 @@ func formatToolsForLog(toolDefs []providers.ToolDefinition) string {
fmt.Fprintf(&sb, " [%d] Type: %s, Name: %s\n", i, tool.Type, tool.Function.Name)
fmt.Fprintf(&sb, " Description: %s\n", tool.Function.Description)
if len(tool.Function.Parameters) > 0 {
fmt.Fprintf(&sb, " Parameters: %s\n", utils.Truncate(fmt.Sprintf("%v", tool.Function.Parameters), 200))
fmt.Fprintf(
&sb,
" Parameters: %s\n",
utils.Truncate(fmt.Sprintf("%v", tool.Function.Parameters), 200),
)
}
}
sb.WriteString("]")
@ -1201,7 +1259,9 @@ func (al *AgentLoop) summarizeBatch(
existingSummary string,
) (string, error) {
var sb strings.Builder
sb.WriteString("Provide a concise summary of this conversation segment, preserving core context and key points.\n")
sb.WriteString(
"Provide a concise summary of this conversation segment, preserving core context and key points.\n",
)
if existingSummary != "" {
sb.WriteString("Existing context: ")
sb.WriteString(existingSummary)

View file

@ -13,6 +13,7 @@ import (
"sync"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/logger"
)
@ -125,7 +126,11 @@ func (m *Manager) LoadFromConfig(ctx context.Context, cfg *config.Config) error
// LoadFromMCPConfig loads MCP servers from MCP configuration and workspace path.
// This is the minimal dependency version that doesn't require the full Config object.
func (m *Manager) LoadFromMCPConfig(ctx context.Context, mcpCfg config.MCPConfig, workspacePath string) error {
func (m *Manager) LoadFromMCPConfig(
ctx context.Context,
mcpCfg config.MCPConfig,
workspacePath string,
) error {
if !mcpCfg.Enabled {
logger.InfoCF("mcp", "MCP integration is disabled", nil)
return nil
@ -137,7 +142,7 @@ func (m *Manager) LoadFromMCPConfig(ctx context.Context, mcpCfg config.MCPConfig
}
logger.InfoCF("mcp", "Initializing MCP servers",
map[string]interface{}{
map[string]any{
"count": len(mcpCfg.Servers),
})
@ -148,7 +153,7 @@ func (m *Manager) LoadFromMCPConfig(ctx context.Context, mcpCfg config.MCPConfig
for name, serverCfg := range mcpCfg.Servers {
if !serverCfg.Enabled {
logger.DebugCF("mcp", "Skipping disabled server",
map[string]interface{}{
map[string]any{
"server": name,
})
continue
@ -162,9 +167,13 @@ func (m *Manager) LoadFromMCPConfig(ctx context.Context, mcpCfg config.MCPConfig
// Resolve relative envFile paths relative to workspace
if serverCfg.EnvFile != "" && !filepath.IsAbs(serverCfg.EnvFile) {
if workspace == "" {
err := fmt.Errorf("workspace path is empty while resolving relative envFile %q for server %s", serverCfg.EnvFile, name)
err := fmt.Errorf(
"workspace path is empty while resolving relative envFile %q for server %s",
serverCfg.EnvFile,
name,
)
logger.ErrorCF("mcp", "Invalid MCP server configuration",
map[string]interface{}{
map[string]any{
"server": name,
"env_file": serverCfg.EnvFile,
"error": err.Error(),
@ -177,7 +186,7 @@ func (m *Manager) LoadFromMCPConfig(ctx context.Context, mcpCfg config.MCPConfig
if err := m.ConnectServer(ctx, name, serverCfg); err != nil {
logger.ErrorCF("mcp", "Failed to connect to MCP server",
map[string]interface{}{
map[string]any{
"server": name,
"error": err.Error(),
})
@ -200,7 +209,7 @@ func (m *Manager) LoadFromMCPConfig(ctx context.Context, mcpCfg config.MCPConfig
// If all enabled servers failed to connect, return aggregated error
if enabledCount > 0 && connectedCount == 0 {
logger.ErrorCF("mcp", "All MCP servers failed to connect",
map[string]interface{}{
map[string]any{
"failed": len(allErrors),
"total": enabledCount,
})
@ -209,7 +218,7 @@ func (m *Manager) LoadFromMCPConfig(ctx context.Context, mcpCfg config.MCPConfig
if len(allErrors) > 0 {
logger.WarnCF("mcp", "Some MCP servers failed to connect",
map[string]interface{}{
map[string]any{
"failed": len(allErrors),
"connected": connectedCount,
"total": enabledCount,
@ -218,7 +227,7 @@ func (m *Manager) LoadFromMCPConfig(ctx context.Context, mcpCfg config.MCPConfig
}
logger.InfoCF("mcp", "MCP server initialization complete",
map[string]interface{}{
map[string]any{
"connected": connectedCount,
"total": enabledCount,
})
@ -227,9 +236,13 @@ func (m *Manager) LoadFromMCPConfig(ctx context.Context, mcpCfg config.MCPConfig
}
// ConnectServer connects to a single MCP server
func (m *Manager) ConnectServer(ctx context.Context, name string, cfg config.MCPServerConfig) error {
func (m *Manager) ConnectServer(
ctx context.Context,
name string,
cfg config.MCPServerConfig,
) error {
logger.InfoCF("mcp", "Connecting to MCP server",
map[string]interface{}{
map[string]any{
"server": name,
"command": cfg.Command,
"args": cfg.Args,
@ -263,7 +276,7 @@ func (m *Manager) ConnectServer(ctx context.Context, name string, cfg config.MCP
return fmt.Errorf("URL is required for SSE/HTTP transport")
}
logger.DebugCF("mcp", "Using SSE/HTTP transport",
map[string]interface{}{
map[string]any{
"server": name,
"url": cfg.URL,
})
@ -282,7 +295,7 @@ func (m *Manager) ConnectServer(ctx context.Context, name string, cfg config.MCP
},
}
logger.DebugCF("mcp", "Added custom HTTP headers",
map[string]interface{}{
map[string]any{
"server": name,
"header_count": len(cfg.Headers),
})
@ -294,7 +307,7 @@ func (m *Manager) ConnectServer(ctx context.Context, name string, cfg config.MCP
return fmt.Errorf("command is required for stdio transport")
}
logger.DebugCF("mcp", "Using stdio transport",
map[string]interface{}{
map[string]any{
"server": name,
"command": cfg.Command,
})
@ -322,7 +335,7 @@ func (m *Manager) ConnectServer(ctx context.Context, name string, cfg config.MCP
envMap[k] = v
}
logger.DebugCF("mcp", "Loaded environment variables from file",
map[string]interface{}{
map[string]any{
"server": name,
"envFile": cfg.EnvFile,
"var_count": len(envVars),
@ -343,7 +356,10 @@ func (m *Manager) ConnectServer(ctx context.Context, name string, cfg config.MCP
transport = &mcp.CommandTransport{Command: cmd}
default:
return fmt.Errorf("unsupported transport type: %s (supported: stdio, sse, http)", transportType)
return fmt.Errorf(
"unsupported transport type: %s (supported: stdio, sse, http)",
transportType,
)
}
// Connect to server
@ -355,7 +371,7 @@ func (m *Manager) ConnectServer(ctx context.Context, name string, cfg config.MCP
// Get server info
initResult := session.InitializeResult()
logger.InfoCF("mcp", "Connected to MCP server",
map[string]interface{}{
map[string]any{
"server": name,
"serverName": initResult.ServerInfo.Name,
"serverVersion": initResult.ServerInfo.Version,
@ -368,7 +384,7 @@ func (m *Manager) ConnectServer(ctx context.Context, name string, cfg config.MCP
for tool, err := range session.Tools(ctx, nil) {
if err != nil {
logger.WarnCF("mcp", "Error listing tool",
map[string]interface{}{
map[string]any{
"server": name,
"error": err.Error(),
})
@ -378,7 +394,7 @@ func (m *Manager) ConnectServer(ctx context.Context, name string, cfg config.MCP
}
logger.InfoCF("mcp", "Listed tools from MCP server",
map[string]interface{}{
map[string]any{
"server": name,
"toolCount": len(tools),
})
@ -419,7 +435,11 @@ func (m *Manager) GetServer(name string) (*ServerConnection, bool) {
}
// CallTool calls a tool on a specific server
func (m *Manager) CallTool(ctx context.Context, serverName, toolName string, arguments map[string]interface{}) (*mcp.CallToolResult, error) {
func (m *Manager) CallTool(
ctx context.Context,
serverName, toolName string,
arguments map[string]any,
) (*mcp.CallToolResult, error) {
m.mu.RLock()
if m.closed {
m.mu.RUnlock()
@ -466,7 +486,7 @@ func (m *Manager) Close() error {
defer m.mu.Unlock()
logger.InfoCF("mcp", "Closing all MCP server connections",
map[string]interface{}{
map[string]any{
"count": len(m.servers),
})
@ -474,7 +494,7 @@ func (m *Manager) Close() error {
for name, conn := range m.servers {
if err := conn.Session.Close(); err != nil {
logger.ErrorCF("mcp", "Failed to close server connection",
map[string]interface{}{
map[string]any{
"server": name,
"error": err.Error(),
})

View file

@ -8,6 +8,7 @@ import (
"testing"
sdkmcp "github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/sipeed/picoclaw/pkg/config"
)
@ -95,7 +96,7 @@ PORT =8080`,
tmpDir := t.TempDir()
envFile := filepath.Join(tmpDir, ".env")
if err := os.WriteFile(envFile, []byte(tt.content), 0644); err != nil {
if err := os.WriteFile(envFile, []byte(tt.content), 0o644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
@ -144,7 +145,7 @@ func TestEnvFilePriority(t *testing.T) {
DATABASE_URL=from_file
SHARED_VAR=from_file`
if err := os.WriteFile(envFile, []byte(envContent), 0644); err != nil {
if err := os.WriteFile(envFile, []byte(envContent), 0o644); err != nil {
t.Fatalf("Failed to create .env file: %v", err)
}
@ -176,7 +177,10 @@ SHARED_VAR=from_file`
// Verify priority: config.Env should override envFile
if merged["SHARED_VAR"] != "from_config" {
t.Errorf("Expected SHARED_VAR=from_config (config should override file), got %s", merged["SHARED_VAR"])
t.Errorf(
"Expected SHARED_VAR=from_config (config should override file), got %s",
merged["SHARED_VAR"],
)
}
if merged["API_KEY"] != "from_file" {
t.Errorf("Expected API_KEY=from_file, got %s", merged["API_KEY"])

View file

@ -12,7 +12,11 @@ import (
// MCPManager defines the interface for MCP manager operations
// This allows for easier testing with mock implementations
type MCPManager interface {
CallTool(ctx context.Context, serverName, toolName string, arguments map[string]interface{}) (*mcp.CallToolResult, error)
CallTool(
ctx context.Context,
serverName, toolName string,
arguments map[string]any,
) (*mcp.CallToolResult, error)
}
// MCPTool wraps an MCP tool to implement the Tool interface
@ -48,21 +52,21 @@ func (t *MCPTool) Description() string {
}
// Parameters returns the tool parameters schema
func (t *MCPTool) Parameters() map[string]interface{} {
func (t *MCPTool) Parameters() map[string]any {
// The InputSchema is already a JSON Schema object
schema := t.tool.InputSchema
// Handle nil schema
if schema == nil {
return map[string]interface{}{
return map[string]any{
"type": "object",
"properties": map[string]interface{}{},
"properties": map[string]any{},
"required": []string{},
}
}
// Try direct conversion first (fast path)
if schemaMap, ok := schema.(map[string]interface{}); ok {
if schemaMap, ok := schema.(map[string]any); ok {
return schemaMap
}
@ -75,14 +79,14 @@ func (t *MCPTool) Parameters() map[string]interface{} {
}
if jsonData != nil {
var result map[string]interface{}
var result map[string]any
if err := json.Unmarshal(jsonData, &result); err == nil {
return result
}
// Fallback on error
return map[string]interface{}{
return map[string]any{
"type": "object",
"properties": map[string]interface{}{},
"properties": map[string]any{},
"required": []string{},
}
}
@ -92,19 +96,19 @@ func (t *MCPTool) Parameters() map[string]interface{} {
jsonData, err = json.Marshal(schema)
if err != nil {
// Fallback to empty schema if marshaling fails
return map[string]interface{}{
return map[string]any{
"type": "object",
"properties": map[string]interface{}{},
"properties": map[string]any{},
"required": []string{},
}
}
var result map[string]interface{}
var result map[string]any
if err := json.Unmarshal(jsonData, &result); err != nil {
// Fallback to empty schema if unmarshaling fails
return map[string]interface{}{
return map[string]any{
"type": "object",
"properties": map[string]interface{}{},
"properties": map[string]any{},
"required": []string{},
}
}
@ -113,7 +117,7 @@ func (t *MCPTool) Parameters() map[string]interface{} {
}
// Execute executes the MCP tool
func (t *MCPTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult {
func (t *MCPTool) Execute(ctx context.Context, args map[string]any) *ToolResult {
result, err := t.manager.CallTool(ctx, t.serverName, t.tool.Name, args)
if err != nil {
return ErrorResult(fmt.Sprintf("MCP tool execution failed: %v", err)).WithError(err)

View file

@ -11,10 +11,14 @@ import (
// MockMCPManager is a mock implementation of MCPManager interface for testing
type MockMCPManager struct {
callToolFunc func(ctx context.Context, serverName, toolName string, arguments map[string]interface{}) (*mcp.CallToolResult, error)
callToolFunc func(ctx context.Context, serverName, toolName string, arguments map[string]any) (*mcp.CallToolResult, error)
}
func (m *MockMCPManager) CallTool(ctx context.Context, serverName, toolName string, arguments map[string]interface{}) (*mcp.CallToolResult, error) {
func (m *MockMCPManager) CallTool(
ctx context.Context,
serverName, toolName string,
arguments map[string]any,
) (*mcp.CallToolResult, error) {
if m.callToolFunc != nil {
return m.callToolFunc(ctx, serverName, toolName, arguments)
}
@ -32,10 +36,10 @@ func TestNewMCPTool(t *testing.T) {
tool := &mcp.Tool{
Name: "test_tool",
Description: "A test tool",
InputSchema: map[string]interface{}{
InputSchema: map[string]any{
"type": "object",
"properties": map[string]interface{}{
"input": map[string]interface{}{
"properties": map[string]any{
"input": map[string]any{
"type": "string",
"description": "Test input",
},
@ -142,17 +146,17 @@ func TestMCPTool_Description(t *testing.T) {
func TestMCPTool_Parameters(t *testing.T) {
tests := []struct {
name string
inputSchema interface{}
inputSchema any
expectType string
checkProperty string
expectProperty bool
}{
{
name: "map schema",
inputSchema: map[string]interface{}{
inputSchema: map[string]any{
"type": "object",
"properties": map[string]interface{}{
"query": map[string]interface{}{
"properties": map[string]any{
"query": map[string]any{
"type": "string",
"description": "Search query",
},
@ -212,7 +216,7 @@ func TestMCPTool_Parameters(t *testing.T) {
// Check if property exists when expected
if tt.checkProperty != "" {
properties, ok := params["properties"].(map[string]interface{})
properties, ok := params["properties"].(map[string]any)
if !ok && tt.expectProperty {
t.Errorf("Expected properties to be a map")
return
@ -232,7 +236,7 @@ func TestMCPTool_Parameters(t *testing.T) {
// TestMCPTool_Execute_Success tests successful tool execution
func TestMCPTool_Execute_Success(t *testing.T) {
manager := &MockMCPManager{
callToolFunc: func(ctx context.Context, serverName, toolName string, arguments map[string]interface{}) (*mcp.CallToolResult, error) {
callToolFunc: func(ctx context.Context, serverName, toolName string, arguments map[string]any) (*mcp.CallToolResult, error) {
// Verify correct parameters passed
if serverName != "github" {
t.Errorf("Expected serverName 'github', got '%s'", serverName)
@ -257,7 +261,7 @@ func TestMCPTool_Execute_Success(t *testing.T) {
mcpTool := NewMCPTool(manager, "github", tool)
ctx := context.Background()
args := map[string]interface{}{
args := map[string]any{
"query": "golang mcp",
}
@ -277,7 +281,7 @@ func TestMCPTool_Execute_Success(t *testing.T) {
// TestMCPTool_Execute_ManagerError tests execution when manager returns error
func TestMCPTool_Execute_ManagerError(t *testing.T) {
manager := &MockMCPManager{
callToolFunc: func(ctx context.Context, serverName, toolName string, arguments map[string]interface{}) (*mcp.CallToolResult, error) {
callToolFunc: func(ctx context.Context, serverName, toolName string, arguments map[string]any) (*mcp.CallToolResult, error) {
return nil, fmt.Errorf("connection failed")
},
}
@ -286,7 +290,7 @@ func TestMCPTool_Execute_ManagerError(t *testing.T) {
mcpTool := NewMCPTool(manager, "test_server", tool)
ctx := context.Background()
result := mcpTool.Execute(ctx, map[string]interface{}{})
result := mcpTool.Execute(ctx, map[string]any{})
if result == nil {
t.Fatal("Result should not be nil")
@ -305,7 +309,7 @@ func TestMCPTool_Execute_ManagerError(t *testing.T) {
// TestMCPTool_Execute_ServerError tests execution when server returns error
func TestMCPTool_Execute_ServerError(t *testing.T) {
manager := &MockMCPManager{
callToolFunc: func(ctx context.Context, serverName, toolName string, arguments map[string]interface{}) (*mcp.CallToolResult, error) {
callToolFunc: func(ctx context.Context, serverName, toolName string, arguments map[string]any) (*mcp.CallToolResult, error) {
return &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: "Invalid API key"},
@ -319,7 +323,7 @@ func TestMCPTool_Execute_ServerError(t *testing.T) {
mcpTool := NewMCPTool(manager, "test_server", tool)
ctx := context.Background()
result := mcpTool.Execute(ctx, map[string]interface{}{})
result := mcpTool.Execute(ctx, map[string]any{})
if result == nil {
t.Fatal("Result should not be nil")
@ -338,7 +342,7 @@ func TestMCPTool_Execute_ServerError(t *testing.T) {
// TestMCPTool_Execute_MultipleContent tests execution with multiple content items
func TestMCPTool_Execute_MultipleContent(t *testing.T) {
manager := &MockMCPManager{
callToolFunc: func(ctx context.Context, serverName, toolName string, arguments map[string]interface{}) (*mcp.CallToolResult, error) {
callToolFunc: func(ctx context.Context, serverName, toolName string, arguments map[string]any) (*mcp.CallToolResult, error) {
return &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: "First line"},
@ -354,7 +358,7 @@ func TestMCPTool_Execute_MultipleContent(t *testing.T) {
mcpTool := NewMCPTool(manager, "test_server", tool)
ctx := context.Background()
result := mcpTool.Execute(ctx, map[string]interface{}{})
result := mcpTool.Execute(ctx, map[string]any{})
if result.IsError {
t.Errorf("Expected no error, got: %s", result.ForLLM)
@ -448,10 +452,10 @@ func TestMCPTool_InterfaceCompliance(t *testing.T) {
// TestMCPTool_Parameters_MapSchema tests schema that's already a map
func TestMCPTool_Parameters_MapSchema(t *testing.T) {
manager := &MockMCPManager{}
schema := map[string]interface{}{
schema := map[string]any{
"type": "object",
"properties": map[string]interface{}{
"name": map[string]interface{}{
"properties": map[string]any{
"name": map[string]any{
"type": "string",
"description": "The name parameter",
},
@ -472,12 +476,12 @@ func TestMCPTool_Parameters_MapSchema(t *testing.T) {
t.Errorf("Expected type 'object', got '%v'", params["type"])
}
props, ok := params["properties"].(map[string]interface{})
props, ok := params["properties"].(map[string]any)
if !ok {
t.Error("Properties should be a map")
}
nameParam, ok := props["name"].(map[string]interface{})
nameParam, ok := props["name"].(map[string]any)
if !ok {
t.Error("Name parameter should exist")
}