From b5bcf2f6553796e5d568870dd9a327b65bde86e2 Mon Sep 17 00:00:00 2001 From: majiayu000 <1835304752@qq.com> Date: Fri, 2 Jan 2026 07:59:32 +0800 Subject: [PATCH] fix: filterToolsByName returns all matching tools for feature flag filtering When multiple tools share the same name but have different feature flags (like GetJobLogs and ActionsGetJobLogs both named "get_job_logs"), filterToolsByName was only returning the first match. This caused the remote server to fail with "unknown tool" error when the first matching tool was disabled by feature flags, even though another variant was enabled. The fix modifies filterToolsByName to return ALL tools with matching names, allowing the feature flag filtering in AvailableTools to select the correct variant based on the enabled flags. Fixes #1714 Signed-off-by: majiayu000 <1835304752@qq.com> --- pkg/inventory/filters.go | 15 +++++++++--- pkg/inventory/registry_test.go | 45 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/pkg/inventory/filters.go b/pkg/inventory/filters.go index 991001a64..c5156e61a 100644 --- a/pkg/inventory/filters.go +++ b/pkg/inventory/filters.go @@ -178,22 +178,29 @@ func (r *Inventory) AvailablePrompts(ctx context.Context) []ServerPrompt { // filterToolsByName returns tools matching the given name, checking deprecated aliases. // Uses linear scan - optimized for single-lookup per-request scenarios (ForMCPRequest). +// Returns ALL tools matching the name to support feature-flagged tool variants +// (e.g., GetJobLogs and ActionsGetJobLogs both use name "get_job_logs" but are +// controlled by different feature flags). func (r *Inventory) filterToolsByName(name string) []ServerTool { - // First check for exact match + var result []ServerTool + // Check for exact matches - multiple tools may share the same name with different feature flags for i := range r.tools { if r.tools[i].Tool.Name == name { - return []ServerTool{r.tools[i]} + result = append(result, r.tools[i]) } } + if len(result) > 0 { + return result + } // Check if name is a deprecated alias if canonical, isAlias := r.deprecatedAliases[name]; isAlias { for i := range r.tools { if r.tools[i].Tool.Name == canonical { - return []ServerTool{r.tools[i]} + result = append(result, r.tools[i]) } } } - return []ServerTool{} + return result } // filterResourcesByURI returns resource templates matching the given URI pattern. diff --git a/pkg/inventory/registry_test.go b/pkg/inventory/registry_test.go index 41e94b8d9..742ad3646 100644 --- a/pkg/inventory/registry_test.go +++ b/pkg/inventory/registry_test.go @@ -1643,3 +1643,48 @@ func TestFilteringOrder(t *testing.T) { } } } + +func TestForMCPRequest_ToolsCall_FeatureFlaggedVariants(t *testing.T) { + // Simulate the get_job_logs scenario: two tools with the same name but different feature flags + // - "get_job_logs" with FeatureFlagDisable (available when flag is OFF) + // - "get_job_logs" with FeatureFlagEnable (available when flag is ON) + tools := []ServerTool{ + mockToolWithFlags("get_job_logs", "actions", true, "", "consolidated_flag"), // disabled when flag is ON + mockToolWithFlags("get_job_logs", "actions", true, "consolidated_flag", ""), // enabled when flag is ON + mockTool("other_tool", "actions", true), + } + + // Test 1: Flag is OFF - first tool variant should be available + regFlagOff := NewBuilder(). + SetTools(tools). + WithToolsets([]string{"all"}). + Build() + filteredOff := regFlagOff.ForMCPRequest(MCPMethodToolsCall, "get_job_logs") + availableOff := filteredOff.AvailableTools(context.Background()) + if len(availableOff) != 1 { + t.Fatalf("Flag OFF: Expected 1 tool, got %d", len(availableOff)) + } + if availableOff[0].FeatureFlagDisable != "consolidated_flag" { + t.Errorf("Flag OFF: Expected tool with FeatureFlagDisable, got FeatureFlagEnable=%q, FeatureFlagDisable=%q", + availableOff[0].FeatureFlagEnable, availableOff[0].FeatureFlagDisable) + } + + // Test 2: Flag is ON - second tool variant should be available + checker := func(_ context.Context, flag string) (bool, error) { + return flag == "consolidated_flag", nil + } + regFlagOn := NewBuilder(). + SetTools(tools). + WithToolsets([]string{"all"}). + WithFeatureChecker(checker). + Build() + filteredOn := regFlagOn.ForMCPRequest(MCPMethodToolsCall, "get_job_logs") + availableOn := filteredOn.AvailableTools(context.Background()) + if len(availableOn) != 1 { + t.Fatalf("Flag ON: Expected 1 tool, got %d", len(availableOn)) + } + if availableOn[0].FeatureFlagEnable != "consolidated_flag" { + t.Errorf("Flag ON: Expected tool with FeatureFlagEnable, got FeatureFlagEnable=%q, FeatureFlagDisable=%q", + availableOn[0].FeatureFlagEnable, availableOn[0].FeatureFlagDisable) + } +}