Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions internal/server/allowed_tools_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions internal/server/call_backend_tool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
18 changes: 17 additions & 1 deletion internal/server/unified.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,14 +378,20 @@ 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. 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 {
return sets
}
for serverID, serverCfg := range cfg.Servers {
if len(serverCfg.Tools) > 0 {
// 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)
Comment on lines 379 to +392
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc/comment text says only a wildcard entry ["*"] is treated as allow-all, but the implementation treats "*" anywhere in the Tools list as allow-all (see hasWildcard(serverCfg.Tools) and the mixed-list tests). Please update the docstring and inline comment to reflect the actual semantics (e.g., "if the Tools list contains '*' anywhere").

Copilot uses AI. Check for mistakes.
continue
}
set := make(map[string]bool, len(serverCfg.Tools))
for _, t := range serverCfg.Tools {
set[t] = true
Expand All @@ -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.
Expand Down
Loading