From c8ab0e5d71f7a2f667eb14025c72c865b43c2baa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:07:59 +0000 Subject: [PATCH 1/3] Initial plan From 689e8b2049f3d2673238c2581da429ef6c6ed12d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 17:14:01 +0000 Subject: [PATCH 2/3] Support wildcard ["*"] in allowed-tools filtering Add wildcard detection in buildAllowedToolSets() so that if the Tools list contains "*", the server is treated as having no restriction (same as when Tools is empty/nil). This fixes the issue where the compiler passes tools: ["*"] and the gateway filters out all tools. - Add hasWildcard() helper to detect "*" anywhere in the tools list - Log when wildcard is detected for a server - Add unit tests for wildcard in buildAllowedToolSets, isToolAllowed - Add integration test for registerToolsFromBackend with wildcard Agent-Logs-Url: https://github.com/github/gh-aw-mcpg/sessions/3811320c-55c9-44e8-bdf0-2af76abb5fc2 Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- .../server/allowed_tools_integration_test.go | 82 +++++++++++++++++++ internal/server/call_backend_tool_test.go | 2 + internal/server/unified.go | 18 +++- 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/internal/server/allowed_tools_integration_test.go b/internal/server/allowed_tools_integration_test.go index d0d5a8c81..801589959 100644 --- a/internal/server/allowed_tools_integration_test.go +++ b/internal/server/allowed_tools_integration_test.go @@ -463,6 +463,88 @@ func TestIsToolAllowed_Integration(t *testing.T) { assert.True(t, us.isToolAllowed("unknown", "tool")) } +// TestBuildAllowedToolSets_WildcardStar verifies that Tools: ["*"] results in no +// entry in the sets map, meaning all tools are allowed (same as an empty list). +func TestBuildAllowedToolSets_WildcardStar(t *testing.T) { + cfg := &config.Config{ + Servers: map[string]*config.ServerConfig{ + "wildcard": {Tools: []string{"*"}}, + "restricted": {Tools: []string{"a", "b"}}, + "open": {}, + }, + } + sets := buildAllowedToolSets(cfg) + + _, hasWildcardServer := sets["wildcard"] + assert.False(t, hasWildcardServer, "server with wildcard must not be in the set map") + + _, hasRestricted := sets["restricted"] + assert.True(t, hasRestricted, "restricted server should still be in the set map") + + _, hasOpen := sets["open"] + assert.False(t, hasOpen, "open server must not be in the set map") +} + +// TestBuildAllowedToolSets_WildcardMixed verifies that a "*" anywhere in the +// Tools list causes the server to be treated as unrestricted. +func TestBuildAllowedToolSets_WildcardMixed(t *testing.T) { + cfg := &config.Config{ + Servers: map[string]*config.ServerConfig{ + "mixed": {Tools: []string{"tool_a", "*", "tool_b"}}, + }, + } + sets := buildAllowedToolSets(cfg) + + _, hasMixed := sets["mixed"] + assert.False(t, hasMixed, "server with wildcard in mixed list must not be in the set map") +} + +// TestIsToolAllowed_Wildcard verifies that isToolAllowed returns true for any +// tool name when the server is configured with Tools: ["*"]. +func TestIsToolAllowed_Wildcard(t *testing.T) { + cfg := &config.Config{ + Servers: map[string]*config.ServerConfig{ + "wildcard": {Tools: []string{"*"}}, + }, + } + us := &UnifiedServer{allowedToolSets: buildAllowedToolSets(cfg)} + + assert.True(t, us.isToolAllowed("wildcard", "any_tool")) + assert.True(t, us.isToolAllowed("wildcard", "another_tool")) + assert.True(t, us.isToolAllowed("wildcard", "delete_everything")) +} + +// TestRegisterToolsFromBackend_WildcardAllowsAll verifies that when a backend +// is configured with Tools: ["*"], all tools from the backend are registered. +func TestRegisterToolsFromBackend_WildcardAllowsAll(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + backend := newMockMCPBackendWithTools(t, "elastic-docs", []string{"search_code", "get_file_contents", "delete_repo"}) + defer backend.Close() + + cfg := &config.Config{ + Servers: map[string]*config.ServerConfig{ + "elastic-docs": { + Type: "http", + URL: backend.URL, + Tools: []string{"*"}, // wildcard — all tools allowed + }, + }, + } + + us, err := NewUnified(context.Background(), cfg) + require.NoError(err) + defer us.Close() + + us.toolsMu.RLock() + defer us.toolsMu.RUnlock() + + assert.Contains(us.tools, "elastic-docs___search_code", "search_code should be registered") + assert.Contains(us.tools, "elastic-docs___get_file_contents", "get_file_contents should be registered") + assert.Contains(us.tools, "elastic-docs___delete_repo", "delete_repo should be registered with wildcard") +} + // ----- helpers ----------------------------------------------------------- // toolNameSet converts a []ToolInfo slice into a name -> bool map for easy lookup. diff --git a/internal/server/call_backend_tool_test.go b/internal/server/call_backend_tool_test.go index 78a39cb21..fa4ce82c1 100644 --- a/internal/server/call_backend_tool_test.go +++ b/internal/server/call_backend_tool_test.go @@ -399,6 +399,8 @@ func TestIsToolAllowed(t *testing.T) { {"empty list allows anything", []string{}, "any_tool", true}, {"tool in list", []string{"a", "b"}, "a", true}, {"tool not in list", []string{"a", "b"}, "c", false}, + {"wildcard allows anything", []string{"*"}, "any_tool", true}, + {"wildcard in mixed list allows anything", []string{"a", "*"}, "z", true}, } for _, tc := range tests { diff --git a/internal/server/unified.go b/internal/server/unified.go index ecf30c461..1e99595db 100644 --- a/internal/server/unified.go +++ b/internal/server/unified.go @@ -378,7 +378,8 @@ func newErrorCallToolResult(err error) (*sdk.CallToolResult, interface{}, error) // buildAllowedToolSets converts the per-server Tools lists from the config into pre-computed // map[string]bool sets for O(1) lookup. Servers with no Tools list are not added to the map, -// which signals that all tools are permitted. +// which signals that all tools are permitted. A wildcard entry ["*"] is treated the same as +// an empty list (all tools allowed). func buildAllowedToolSets(cfg *config.Config) map[string]map[string]bool { sets := make(map[string]map[string]bool) if cfg == nil { @@ -386,6 +387,11 @@ func buildAllowedToolSets(cfg *config.Config) map[string]map[string]bool { } for serverID, serverCfg := range cfg.Servers { if len(serverCfg.Tools) > 0 { + // Treat ["*"] as "allow all" — skip adding to the filter map + if hasWildcard(serverCfg.Tools) { + logger.LogInfo("backend", "[allowed-tools] Wildcard \"*\" configured for %s: allowing all tools", serverID) + continue + } set := make(map[string]bool, len(serverCfg.Tools)) for _, t := range serverCfg.Tools { set[t] = true @@ -396,6 +402,16 @@ func buildAllowedToolSets(cfg *config.Config) map[string]map[string]bool { return sets } +// hasWildcard reports whether the tools list contains a "*" entry. +func hasWildcard(tools []string) bool { + for _, t := range tools { + if t == "*" { + return true + } + } + return false +} + // isToolAllowed reports whether toolName is permitted by the server's configured // allowed-tools list. When no list is configured (empty), all tools are allowed. // Uses the pre-computed allowedToolSets map for O(1) lookup. From 4e0dc144f6a4572049ad971627a8ac08f559eca1 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Thu, 9 Apr 2026 10:55:32 -0700 Subject: [PATCH 3/3] docs: clarify wildcard matches anywhere in Tools list Update docstring and inline comment to reflect that "*" anywhere in the Tools list (not just as the sole entry) triggers allow-all behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- internal/server/unified.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/server/unified.go b/internal/server/unified.go index 1e99595db..d397f4ee9 100644 --- a/internal/server/unified.go +++ b/internal/server/unified.go @@ -378,8 +378,8 @@ func newErrorCallToolResult(err error) (*sdk.CallToolResult, interface{}, error) // buildAllowedToolSets converts the per-server Tools lists from the config into pre-computed // map[string]bool sets for O(1) lookup. Servers with no Tools list are not added to the map, -// which signals that all tools are permitted. A wildcard entry ["*"] is treated the same as -// an empty list (all tools allowed). +// which signals that all tools are permitted. If the Tools list contains a "*" entry anywhere, +// the server is treated the same as having no list (all tools allowed). func buildAllowedToolSets(cfg *config.Config) map[string]map[string]bool { sets := make(map[string]map[string]bool) if cfg == nil { @@ -387,7 +387,7 @@ func buildAllowedToolSets(cfg *config.Config) map[string]map[string]bool { } for serverID, serverCfg := range cfg.Servers { if len(serverCfg.Tools) > 0 { - // Treat ["*"] as "allow all" — skip adding to the filter map + // Treat "*" anywhere in the list as "allow all" — skip adding to the filter map if hasWildcard(serverCfg.Tools) { logger.LogInfo("backend", "[allowed-tools] Wildcard \"*\" configured for %s: allowing all tools", serverID) continue