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:
parent
257b0d82b5
commit
077d7c8d9b
5 changed files with 186 additions and 94 deletions
|
|
@ -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."
|
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)
|
registry := NewAgentRegistry(cfg, provider)
|
||||||
|
|
||||||
// Register shared tools to all agents
|
// Register shared tools to all agents
|
||||||
|
|
@ -166,7 +170,7 @@ func (al *AgentLoop) Run(ctx context.Context) error {
|
||||||
if al.cfg.Tools.MCP.Enabled {
|
if al.cfg.Tools.MCP.Enabled {
|
||||||
mcpManager := mcp.NewManager()
|
mcpManager := mcp.NewManager()
|
||||||
defaultAgent := al.registry.GetDefaultAgent()
|
defaultAgent := al.registry.GetDefaultAgent()
|
||||||
workspacePath := ""
|
var workspacePath string
|
||||||
if defaultAgent != nil && defaultAgent.Workspace != "" {
|
if defaultAgent != nil && defaultAgent.Workspace != "" {
|
||||||
workspacePath = defaultAgent.Workspace
|
workspacePath = defaultAgent.Workspace
|
||||||
} else {
|
} 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 {
|
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",
|
logger.WarnCF("agent", "Failed to load MCP servers, MCP tools will not be available",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -183,7 +187,7 @@ func (al *AgentLoop) Run(ctx context.Context) error {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := mcpManager.Close(); err != nil {
|
if err := mcpManager.Close(); err != nil {
|
||||||
logger.ErrorCF("agent", "Failed to close MCP manager",
|
logger.ErrorCF("agent", "Failed to close MCP manager",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -208,7 +212,7 @@ func (al *AgentLoop) Run(ctx context.Context) error {
|
||||||
agent.Tools.Register(mcpTool)
|
agent.Tools.Register(mcpTool)
|
||||||
totalRegistrations++
|
totalRegistrations++
|
||||||
logger.DebugCF("agent", "Registered MCP tool",
|
logger.DebugCF("agent", "Registered MCP tool",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"agent_id": agentID,
|
"agent_id": agentID,
|
||||||
"server": serverName,
|
"server": serverName,
|
||||||
"tool": tool.Name,
|
"tool": tool.Name,
|
||||||
|
|
@ -218,7 +222,7 @@ func (al *AgentLoop) Run(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.InfoCF("agent", "MCP tools registered successfully",
|
logger.InfoCF("agent", "MCP tools registered successfully",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"server_count": len(servers),
|
"server_count": len(servers),
|
||||||
"unique_tools": uniqueTools,
|
"unique_tools": uniqueTools,
|
||||||
"total_registrations": totalRegistrations,
|
"total_registrations": totalRegistrations,
|
||||||
|
|
@ -367,7 +371,10 @@ func (al *AgentLoop) RecordLastChatID(chatID string) error {
|
||||||
return al.state.SetLastChatID(chatID)
|
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")
|
return al.ProcessDirectWithChannel(ctx, content, sessionKey, "cli", "direct")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -388,7 +395,10 @@ func (al *AgentLoop) ProcessDirectWithChannel(
|
||||||
|
|
||||||
// ProcessHeartbeat processes a heartbeat request without session history.
|
// ProcessHeartbeat processes a heartbeat request without session history.
|
||||||
// Each heartbeat is independent and doesn't accumulate context.
|
// 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()
|
agent := al.registry.GetDefaultAgent()
|
||||||
if agent == nil {
|
if agent == nil {
|
||||||
return "", fmt.Errorf("no default agent for heartbeat")
|
return "", fmt.Errorf("no default agent for heartbeat")
|
||||||
|
|
@ -413,13 +423,16 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
||||||
} else {
|
} else {
|
||||||
logContent = utils.Truncate(msg.Content, 80)
|
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{
|
map[string]any{
|
||||||
"channel": msg.Channel,
|
"channel": msg.Channel,
|
||||||
"chat_id": msg.ChatID,
|
"chat_id": msg.ChatID,
|
||||||
"sender_id": msg.SenderID,
|
"sender_id": msg.SenderID,
|
||||||
"session_key": msg.SessionKey,
|
"session_key": msg.SessionKey,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// Route system messages to processSystemMessage
|
// Route system messages to processSystemMessage
|
||||||
if msg.Channel == "system" {
|
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" {
|
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",
|
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.
|
// 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)
|
// 0. Record last channel for heartbeat notifications (skip internal channels)
|
||||||
if opts.Channel != "" && opts.ChatID != "" {
|
if opts.Channel != "" && opts.ChatID != "" {
|
||||||
// Don't record internal channels (cli, system, subagent)
|
// Don't record internal channels (cli, system, subagent)
|
||||||
if !constants.IsInternalChannel(opts.Channel) {
|
if !constants.IsInternalChannel(opts.Channel) {
|
||||||
channelKey := fmt.Sprintf("%s:%s", opts.Channel, opts.ChatID)
|
channelKey := fmt.Sprintf("%s:%s", opts.Channel, opts.ChatID)
|
||||||
if err := al.RecordLastChannel(channelKey); err != nil {
|
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 ""
|
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 == "" {
|
if reasoningContent == "" || channelName == "" || channelID == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -697,22 +727,33 @@ func (al *AgentLoop) runLLMIteration(
|
||||||
|
|
||||||
callLLM := func() (*providers.LLMResponse, error) {
|
callLLM := func() (*providers.LLMResponse, error) {
|
||||||
if len(agent.Candidates) > 1 && al.fallback != nil {
|
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) {
|
func(ctx context.Context, provider, model string) (*providers.LLMResponse, error) {
|
||||||
return agent.Provider.Chat(ctx, messages, providerToolDefs, model, map[string]any{
|
return agent.Provider.Chat(
|
||||||
|
ctx,
|
||||||
|
messages,
|
||||||
|
providerToolDefs,
|
||||||
|
model,
|
||||||
|
map[string]any{
|
||||||
"max_tokens": agent.MaxTokens,
|
"max_tokens": agent.MaxTokens,
|
||||||
"temperature": agent.Temperature,
|
"temperature": agent.Temperature,
|
||||||
"prompt_cache_key": agent.ID,
|
"prompt_cache_key": agent.ID,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if fbErr != nil {
|
if fbErr != nil {
|
||||||
return nil, fbErr
|
return nil, fbErr
|
||||||
}
|
}
|
||||||
if fbResult.Provider != "" && len(fbResult.Attempts) > 0 {
|
if fbResult.Provider != "" && len(fbResult.Attempts) > 0 {
|
||||||
logger.InfoCF("agent", fmt.Sprintf("Fallback: succeeded with %s/%s after %d attempts",
|
logger.InfoCF(
|
||||||
|
"agent",
|
||||||
|
fmt.Sprintf("Fallback: succeeded with %s/%s after %d attempts",
|
||||||
fbResult.Provider, fbResult.Model, len(fbResult.Attempts)+1),
|
fbResult.Provider, fbResult.Model, len(fbResult.Attempts)+1),
|
||||||
map[string]any{"agent_id": agent.ID, "iteration": iteration})
|
map[string]any{"agent_id": agent.ID, "iteration": iteration},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return fbResult.Response, nil
|
return fbResult.Response, nil
|
||||||
}
|
}
|
||||||
|
|
@ -738,10 +779,14 @@ func (al *AgentLoop) runLLMIteration(
|
||||||
strings.Contains(errMsg, "length")
|
strings.Contains(errMsg, "length")
|
||||||
|
|
||||||
if isContextError && retry < maxRetries {
|
if isContextError && retry < maxRetries {
|
||||||
logger.WarnCF("agent", "Context window error detected, attempting compression", map[string]any{
|
logger.WarnCF(
|
||||||
|
"agent",
|
||||||
|
"Context window error detected, attempting compression",
|
||||||
|
map[string]any{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
"retry": retry,
|
"retry": retry,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
if retry == 0 && !constants.IsInternalChannel(opts.Channel) {
|
if retry == 0 && !constants.IsInternalChannel(opts.Channel) {
|
||||||
al.bus.PublishOutbound(ctx, bus.OutboundMessage{
|
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)
|
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",
|
logger.DebugCF("agent", "LLM response",
|
||||||
map[string]any{
|
map[string]any{
|
||||||
|
|
@ -1075,7 +1125,11 @@ func formatMessagesForLog(messages []providers.Message) string {
|
||||||
for _, tc := range msg.ToolCalls {
|
for _, tc := range msg.ToolCalls {
|
||||||
fmt.Fprintf(&sb, " - ID: %s, Type: %s, Name: %s\n", tc.ID, tc.Type, tc.Name)
|
fmt.Fprintf(&sb, " - ID: %s, Type: %s, Name: %s\n", tc.ID, tc.Type, tc.Name)
|
||||||
if tc.Function != nil {
|
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, " [%d] Type: %s, Name: %s\n", i, tool.Type, tool.Function.Name)
|
||||||
fmt.Fprintf(&sb, " Description: %s\n", tool.Function.Description)
|
fmt.Fprintf(&sb, " Description: %s\n", tool.Function.Description)
|
||||||
if len(tool.Function.Parameters) > 0 {
|
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("]")
|
sb.WriteString("]")
|
||||||
|
|
@ -1201,7 +1259,9 @@ func (al *AgentLoop) summarizeBatch(
|
||||||
existingSummary string,
|
existingSummary string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
var sb strings.Builder
|
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 != "" {
|
if existingSummary != "" {
|
||||||
sb.WriteString("Existing context: ")
|
sb.WriteString("Existing context: ")
|
||||||
sb.WriteString(existingSummary)
|
sb.WriteString(existingSummary)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/config"
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
"github.com/sipeed/picoclaw/pkg/logger"
|
"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.
|
// LoadFromMCPConfig loads MCP servers from MCP configuration and workspace path.
|
||||||
// This is the minimal dependency version that doesn't require the full Config object.
|
// 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 {
|
if !mcpCfg.Enabled {
|
||||||
logger.InfoCF("mcp", "MCP integration is disabled", nil)
|
logger.InfoCF("mcp", "MCP integration is disabled", nil)
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -137,7 +142,7 @@ func (m *Manager) LoadFromMCPConfig(ctx context.Context, mcpCfg config.MCPConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.InfoCF("mcp", "Initializing MCP servers",
|
logger.InfoCF("mcp", "Initializing MCP servers",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"count": len(mcpCfg.Servers),
|
"count": len(mcpCfg.Servers),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -148,7 +153,7 @@ func (m *Manager) LoadFromMCPConfig(ctx context.Context, mcpCfg config.MCPConfig
|
||||||
for name, serverCfg := range mcpCfg.Servers {
|
for name, serverCfg := range mcpCfg.Servers {
|
||||||
if !serverCfg.Enabled {
|
if !serverCfg.Enabled {
|
||||||
logger.DebugCF("mcp", "Skipping disabled server",
|
logger.DebugCF("mcp", "Skipping disabled server",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"server": name,
|
"server": name,
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
|
|
@ -162,9 +167,13 @@ func (m *Manager) LoadFromMCPConfig(ctx context.Context, mcpCfg config.MCPConfig
|
||||||
// Resolve relative envFile paths relative to workspace
|
// Resolve relative envFile paths relative to workspace
|
||||||
if serverCfg.EnvFile != "" && !filepath.IsAbs(serverCfg.EnvFile) {
|
if serverCfg.EnvFile != "" && !filepath.IsAbs(serverCfg.EnvFile) {
|
||||||
if workspace == "" {
|
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",
|
logger.ErrorCF("mcp", "Invalid MCP server configuration",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"server": name,
|
"server": name,
|
||||||
"env_file": serverCfg.EnvFile,
|
"env_file": serverCfg.EnvFile,
|
||||||
"error": err.Error(),
|
"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 {
|
if err := m.ConnectServer(ctx, name, serverCfg); err != nil {
|
||||||
logger.ErrorCF("mcp", "Failed to connect to MCP server",
|
logger.ErrorCF("mcp", "Failed to connect to MCP server",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"server": name,
|
"server": name,
|
||||||
"error": err.Error(),
|
"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 all enabled servers failed to connect, return aggregated error
|
||||||
if enabledCount > 0 && connectedCount == 0 {
|
if enabledCount > 0 && connectedCount == 0 {
|
||||||
logger.ErrorCF("mcp", "All MCP servers failed to connect",
|
logger.ErrorCF("mcp", "All MCP servers failed to connect",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"failed": len(allErrors),
|
"failed": len(allErrors),
|
||||||
"total": enabledCount,
|
"total": enabledCount,
|
||||||
})
|
})
|
||||||
|
|
@ -209,7 +218,7 @@ func (m *Manager) LoadFromMCPConfig(ctx context.Context, mcpCfg config.MCPConfig
|
||||||
|
|
||||||
if len(allErrors) > 0 {
|
if len(allErrors) > 0 {
|
||||||
logger.WarnCF("mcp", "Some MCP servers failed to connect",
|
logger.WarnCF("mcp", "Some MCP servers failed to connect",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"failed": len(allErrors),
|
"failed": len(allErrors),
|
||||||
"connected": connectedCount,
|
"connected": connectedCount,
|
||||||
"total": enabledCount,
|
"total": enabledCount,
|
||||||
|
|
@ -218,7 +227,7 @@ func (m *Manager) LoadFromMCPConfig(ctx context.Context, mcpCfg config.MCPConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.InfoCF("mcp", "MCP server initialization complete",
|
logger.InfoCF("mcp", "MCP server initialization complete",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"connected": connectedCount,
|
"connected": connectedCount,
|
||||||
"total": enabledCount,
|
"total": enabledCount,
|
||||||
})
|
})
|
||||||
|
|
@ -227,9 +236,13 @@ func (m *Manager) LoadFromMCPConfig(ctx context.Context, mcpCfg config.MCPConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectServer connects to a single MCP server
|
// 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",
|
logger.InfoCF("mcp", "Connecting to MCP server",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"server": name,
|
"server": name,
|
||||||
"command": cfg.Command,
|
"command": cfg.Command,
|
||||||
"args": cfg.Args,
|
"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")
|
return fmt.Errorf("URL is required for SSE/HTTP transport")
|
||||||
}
|
}
|
||||||
logger.DebugCF("mcp", "Using SSE/HTTP transport",
|
logger.DebugCF("mcp", "Using SSE/HTTP transport",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"server": name,
|
"server": name,
|
||||||
"url": cfg.URL,
|
"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",
|
logger.DebugCF("mcp", "Added custom HTTP headers",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"server": name,
|
"server": name,
|
||||||
"header_count": len(cfg.Headers),
|
"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")
|
return fmt.Errorf("command is required for stdio transport")
|
||||||
}
|
}
|
||||||
logger.DebugCF("mcp", "Using stdio transport",
|
logger.DebugCF("mcp", "Using stdio transport",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"server": name,
|
"server": name,
|
||||||
"command": cfg.Command,
|
"command": cfg.Command,
|
||||||
})
|
})
|
||||||
|
|
@ -322,7 +335,7 @@ func (m *Manager) ConnectServer(ctx context.Context, name string, cfg config.MCP
|
||||||
envMap[k] = v
|
envMap[k] = v
|
||||||
}
|
}
|
||||||
logger.DebugCF("mcp", "Loaded environment variables from file",
|
logger.DebugCF("mcp", "Loaded environment variables from file",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"server": name,
|
"server": name,
|
||||||
"envFile": cfg.EnvFile,
|
"envFile": cfg.EnvFile,
|
||||||
"var_count": len(envVars),
|
"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}
|
transport = &mcp.CommandTransport{Command: cmd}
|
||||||
default:
|
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
|
// Connect to server
|
||||||
|
|
@ -355,7 +371,7 @@ func (m *Manager) ConnectServer(ctx context.Context, name string, cfg config.MCP
|
||||||
// Get server info
|
// Get server info
|
||||||
initResult := session.InitializeResult()
|
initResult := session.InitializeResult()
|
||||||
logger.InfoCF("mcp", "Connected to MCP server",
|
logger.InfoCF("mcp", "Connected to MCP server",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"server": name,
|
"server": name,
|
||||||
"serverName": initResult.ServerInfo.Name,
|
"serverName": initResult.ServerInfo.Name,
|
||||||
"serverVersion": initResult.ServerInfo.Version,
|
"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) {
|
for tool, err := range session.Tools(ctx, nil) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WarnCF("mcp", "Error listing tool",
|
logger.WarnCF("mcp", "Error listing tool",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"server": name,
|
"server": name,
|
||||||
"error": err.Error(),
|
"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",
|
logger.InfoCF("mcp", "Listed tools from MCP server",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"server": name,
|
"server": name,
|
||||||
"toolCount": len(tools),
|
"toolCount": len(tools),
|
||||||
})
|
})
|
||||||
|
|
@ -419,7 +435,11 @@ func (m *Manager) GetServer(name string) (*ServerConnection, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallTool calls a tool on a specific server
|
// 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()
|
m.mu.RLock()
|
||||||
if m.closed {
|
if m.closed {
|
||||||
m.mu.RUnlock()
|
m.mu.RUnlock()
|
||||||
|
|
@ -466,7 +486,7 @@ func (m *Manager) Close() error {
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
logger.InfoCF("mcp", "Closing all MCP server connections",
|
logger.InfoCF("mcp", "Closing all MCP server connections",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"count": len(m.servers),
|
"count": len(m.servers),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -474,7 +494,7 @@ func (m *Manager) Close() error {
|
||||||
for name, conn := range m.servers {
|
for name, conn := range m.servers {
|
||||||
if err := conn.Session.Close(); err != nil {
|
if err := conn.Session.Close(); err != nil {
|
||||||
logger.ErrorCF("mcp", "Failed to close server connection",
|
logger.ErrorCF("mcp", "Failed to close server connection",
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"server": name,
|
"server": name,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
sdkmcp "github.com/modelcontextprotocol/go-sdk/mcp"
|
sdkmcp "github.com/modelcontextprotocol/go-sdk/mcp"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/config"
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -95,7 +96,7 @@ PORT =8080`,
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
envFile := filepath.Join(tmpDir, ".env")
|
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)
|
t.Fatalf("Failed to create test file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,7 +145,7 @@ func TestEnvFilePriority(t *testing.T) {
|
||||||
DATABASE_URL=from_file
|
DATABASE_URL=from_file
|
||||||
SHARED_VAR=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)
|
t.Fatalf("Failed to create .env file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,7 +177,10 @@ SHARED_VAR=from_file`
|
||||||
|
|
||||||
// Verify priority: config.Env should override envFile
|
// Verify priority: config.Env should override envFile
|
||||||
if merged["SHARED_VAR"] != "from_config" {
|
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" {
|
if merged["API_KEY"] != "from_file" {
|
||||||
t.Errorf("Expected API_KEY=from_file, got %s", merged["API_KEY"])
|
t.Errorf("Expected API_KEY=from_file, got %s", merged["API_KEY"])
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,11 @@ import (
|
||||||
// MCPManager defines the interface for MCP manager operations
|
// MCPManager defines the interface for MCP manager operations
|
||||||
// This allows for easier testing with mock implementations
|
// This allows for easier testing with mock implementations
|
||||||
type MCPManager interface {
|
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
|
// 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
|
// 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
|
// The InputSchema is already a JSON Schema object
|
||||||
schema := t.tool.InputSchema
|
schema := t.tool.InputSchema
|
||||||
|
|
||||||
// Handle nil schema
|
// Handle nil schema
|
||||||
if schema == nil {
|
if schema == nil {
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": map[string]interface{}{},
|
"properties": map[string]any{},
|
||||||
"required": []string{},
|
"required": []string{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try direct conversion first (fast path)
|
// Try direct conversion first (fast path)
|
||||||
if schemaMap, ok := schema.(map[string]interface{}); ok {
|
if schemaMap, ok := schema.(map[string]any); ok {
|
||||||
return schemaMap
|
return schemaMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,14 +79,14 @@ func (t *MCPTool) Parameters() map[string]interface{} {
|
||||||
}
|
}
|
||||||
|
|
||||||
if jsonData != nil {
|
if jsonData != nil {
|
||||||
var result map[string]interface{}
|
var result map[string]any
|
||||||
if err := json.Unmarshal(jsonData, &result); err == nil {
|
if err := json.Unmarshal(jsonData, &result); err == nil {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
// Fallback on error
|
// Fallback on error
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": map[string]interface{}{},
|
"properties": map[string]any{},
|
||||||
"required": []string{},
|
"required": []string{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -92,19 +96,19 @@ func (t *MCPTool) Parameters() map[string]interface{} {
|
||||||
jsonData, err = json.Marshal(schema)
|
jsonData, err = json.Marshal(schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Fallback to empty schema if marshaling fails
|
// Fallback to empty schema if marshaling fails
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": map[string]interface{}{},
|
"properties": map[string]any{},
|
||||||
"required": []string{},
|
"required": []string{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var result map[string]interface{}
|
var result map[string]any
|
||||||
if err := json.Unmarshal(jsonData, &result); err != nil {
|
if err := json.Unmarshal(jsonData, &result); err != nil {
|
||||||
// Fallback to empty schema if unmarshaling fails
|
// Fallback to empty schema if unmarshaling fails
|
||||||
return map[string]interface{}{
|
return map[string]any{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": map[string]interface{}{},
|
"properties": map[string]any{},
|
||||||
"required": []string{},
|
"required": []string{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +117,7 @@ func (t *MCPTool) Parameters() map[string]interface{} {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute executes the MCP tool
|
// 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)
|
result, err := t.manager.CallTool(ctx, t.serverName, t.tool.Name, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrorResult(fmt.Sprintf("MCP tool execution failed: %v", err)).WithError(err)
|
return ErrorResult(fmt.Sprintf("MCP tool execution failed: %v", err)).WithError(err)
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,14 @@ import (
|
||||||
|
|
||||||
// MockMCPManager is a mock implementation of MCPManager interface for testing
|
// MockMCPManager is a mock implementation of MCPManager interface for testing
|
||||||
type MockMCPManager struct {
|
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 {
|
if m.callToolFunc != nil {
|
||||||
return m.callToolFunc(ctx, serverName, toolName, arguments)
|
return m.callToolFunc(ctx, serverName, toolName, arguments)
|
||||||
}
|
}
|
||||||
|
|
@ -32,10 +36,10 @@ func TestNewMCPTool(t *testing.T) {
|
||||||
tool := &mcp.Tool{
|
tool := &mcp.Tool{
|
||||||
Name: "test_tool",
|
Name: "test_tool",
|
||||||
Description: "A test tool",
|
Description: "A test tool",
|
||||||
InputSchema: map[string]interface{}{
|
InputSchema: map[string]any{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": map[string]interface{}{
|
"properties": map[string]any{
|
||||||
"input": map[string]interface{}{
|
"input": map[string]any{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Test input",
|
"description": "Test input",
|
||||||
},
|
},
|
||||||
|
|
@ -142,17 +146,17 @@ func TestMCPTool_Description(t *testing.T) {
|
||||||
func TestMCPTool_Parameters(t *testing.T) {
|
func TestMCPTool_Parameters(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
inputSchema interface{}
|
inputSchema any
|
||||||
expectType string
|
expectType string
|
||||||
checkProperty string
|
checkProperty string
|
||||||
expectProperty bool
|
expectProperty bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "map schema",
|
name: "map schema",
|
||||||
inputSchema: map[string]interface{}{
|
inputSchema: map[string]any{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": map[string]interface{}{
|
"properties": map[string]any{
|
||||||
"query": map[string]interface{}{
|
"query": map[string]any{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Search query",
|
"description": "Search query",
|
||||||
},
|
},
|
||||||
|
|
@ -212,7 +216,7 @@ func TestMCPTool_Parameters(t *testing.T) {
|
||||||
|
|
||||||
// Check if property exists when expected
|
// Check if property exists when expected
|
||||||
if tt.checkProperty != "" {
|
if tt.checkProperty != "" {
|
||||||
properties, ok := params["properties"].(map[string]interface{})
|
properties, ok := params["properties"].(map[string]any)
|
||||||
if !ok && tt.expectProperty {
|
if !ok && tt.expectProperty {
|
||||||
t.Errorf("Expected properties to be a map")
|
t.Errorf("Expected properties to be a map")
|
||||||
return
|
return
|
||||||
|
|
@ -232,7 +236,7 @@ func TestMCPTool_Parameters(t *testing.T) {
|
||||||
// TestMCPTool_Execute_Success tests successful tool execution
|
// TestMCPTool_Execute_Success tests successful tool execution
|
||||||
func TestMCPTool_Execute_Success(t *testing.T) {
|
func TestMCPTool_Execute_Success(t *testing.T) {
|
||||||
manager := &MockMCPManager{
|
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
|
// Verify correct parameters passed
|
||||||
if serverName != "github" {
|
if serverName != "github" {
|
||||||
t.Errorf("Expected serverName 'github', got '%s'", serverName)
|
t.Errorf("Expected serverName 'github', got '%s'", serverName)
|
||||||
|
|
@ -257,7 +261,7 @@ func TestMCPTool_Execute_Success(t *testing.T) {
|
||||||
mcpTool := NewMCPTool(manager, "github", tool)
|
mcpTool := NewMCPTool(manager, "github", tool)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
args := map[string]interface{}{
|
args := map[string]any{
|
||||||
"query": "golang mcp",
|
"query": "golang mcp",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -277,7 +281,7 @@ func TestMCPTool_Execute_Success(t *testing.T) {
|
||||||
// TestMCPTool_Execute_ManagerError tests execution when manager returns error
|
// TestMCPTool_Execute_ManagerError tests execution when manager returns error
|
||||||
func TestMCPTool_Execute_ManagerError(t *testing.T) {
|
func TestMCPTool_Execute_ManagerError(t *testing.T) {
|
||||||
manager := &MockMCPManager{
|
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")
|
return nil, fmt.Errorf("connection failed")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -286,7 +290,7 @@ func TestMCPTool_Execute_ManagerError(t *testing.T) {
|
||||||
mcpTool := NewMCPTool(manager, "test_server", tool)
|
mcpTool := NewMCPTool(manager, "test_server", tool)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
result := mcpTool.Execute(ctx, map[string]interface{}{})
|
result := mcpTool.Execute(ctx, map[string]any{})
|
||||||
|
|
||||||
if result == nil {
|
if result == nil {
|
||||||
t.Fatal("Result should not be 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
|
// TestMCPTool_Execute_ServerError tests execution when server returns error
|
||||||
func TestMCPTool_Execute_ServerError(t *testing.T) {
|
func TestMCPTool_Execute_ServerError(t *testing.T) {
|
||||||
manager := &MockMCPManager{
|
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{
|
return &mcp.CallToolResult{
|
||||||
Content: []mcp.Content{
|
Content: []mcp.Content{
|
||||||
&mcp.TextContent{Text: "Invalid API key"},
|
&mcp.TextContent{Text: "Invalid API key"},
|
||||||
|
|
@ -319,7 +323,7 @@ func TestMCPTool_Execute_ServerError(t *testing.T) {
|
||||||
mcpTool := NewMCPTool(manager, "test_server", tool)
|
mcpTool := NewMCPTool(manager, "test_server", tool)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
result := mcpTool.Execute(ctx, map[string]interface{}{})
|
result := mcpTool.Execute(ctx, map[string]any{})
|
||||||
|
|
||||||
if result == nil {
|
if result == nil {
|
||||||
t.Fatal("Result should not be 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
|
// TestMCPTool_Execute_MultipleContent tests execution with multiple content items
|
||||||
func TestMCPTool_Execute_MultipleContent(t *testing.T) {
|
func TestMCPTool_Execute_MultipleContent(t *testing.T) {
|
||||||
manager := &MockMCPManager{
|
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{
|
return &mcp.CallToolResult{
|
||||||
Content: []mcp.Content{
|
Content: []mcp.Content{
|
||||||
&mcp.TextContent{Text: "First line"},
|
&mcp.TextContent{Text: "First line"},
|
||||||
|
|
@ -354,7 +358,7 @@ func TestMCPTool_Execute_MultipleContent(t *testing.T) {
|
||||||
mcpTool := NewMCPTool(manager, "test_server", tool)
|
mcpTool := NewMCPTool(manager, "test_server", tool)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
result := mcpTool.Execute(ctx, map[string]interface{}{})
|
result := mcpTool.Execute(ctx, map[string]any{})
|
||||||
|
|
||||||
if result.IsError {
|
if result.IsError {
|
||||||
t.Errorf("Expected no error, got: %s", result.ForLLM)
|
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
|
// TestMCPTool_Parameters_MapSchema tests schema that's already a map
|
||||||
func TestMCPTool_Parameters_MapSchema(t *testing.T) {
|
func TestMCPTool_Parameters_MapSchema(t *testing.T) {
|
||||||
manager := &MockMCPManager{}
|
manager := &MockMCPManager{}
|
||||||
schema := map[string]interface{}{
|
schema := map[string]any{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": map[string]interface{}{
|
"properties": map[string]any{
|
||||||
"name": map[string]interface{}{
|
"name": map[string]any{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The name parameter",
|
"description": "The name parameter",
|
||||||
},
|
},
|
||||||
|
|
@ -472,12 +476,12 @@ func TestMCPTool_Parameters_MapSchema(t *testing.T) {
|
||||||
t.Errorf("Expected type 'object', got '%v'", params["type"])
|
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 {
|
if !ok {
|
||||||
t.Error("Properties should be a map")
|
t.Error("Properties should be a map")
|
||||||
}
|
}
|
||||||
|
|
||||||
nameParam, ok := props["name"].(map[string]interface{})
|
nameParam, ok := props["name"].(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Error("Name parameter should exist")
|
t.Error("Name parameter should exist")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue