fix(mcp): treat tools.include: [] as an explicit empty whitelist
_normalize_name_filter([]) returns an empty set, which is falsy, so
_should_register fell through to "no filter" and registered every tool
— the exact opposite of what _apply_tool_selection wrote when the user
unchecked everything in the install checklist ("contributes nothing
until reconfigured"). Whitelist mode is now keyed on the include key
holding a valid filter shape (str/list/tuple/set) rather than on set
truthiness, at both the live-discovery and cached-manifest sites.
Invalid include values keep the old warn-and-ignore behaviour.
This commit is contained in:
committed by
Teknium
parent
164e25a936
commit
fb1ec36a4b
@@ -2394,6 +2394,24 @@ class TestMCPSelectiveToolLoading:
|
||||
)
|
||||
assert registered == ["mcp__ink__create_service"]
|
||||
|
||||
def test_empty_include_registers_nothing(self):
|
||||
"""include: [] is an explicit empty whitelist, not "no filter".
|
||||
|
||||
The install checklist writes include: [] when the user unchecks
|
||||
every tool ("contributes nothing until reconfigured") — the next
|
||||
session must not register the full tool surface.
|
||||
"""
|
||||
config = {
|
||||
"url": "https://mcp.example.com",
|
||||
"tools": {"include": []},
|
||||
}
|
||||
registered, _ = self._run_discover(
|
||||
"ink",
|
||||
["create_service", "delete_service", "list_services"],
|
||||
config,
|
||||
session=SimpleNamespace(),
|
||||
)
|
||||
assert registered == []
|
||||
|
||||
def test_enabled_false_skips_connection_attempt(self):
|
||||
from tools.mcp_tool import discover_mcp_tools
|
||||
|
||||
@@ -7125,17 +7125,21 @@ def _register_server_tools(name: str, server: MCPServerTask, config: dict) -> Li
|
||||
# tools.exclude — blacklist: all tools EXCEPT matching ones are registered
|
||||
# entries may be exact names or fnmatch globs (e.g. "*_radar_*")
|
||||
# include takes precedence over exclude
|
||||
# include: [] → register nothing (an explicit empty whitelist, as
|
||||
# written by the install checklist's "uncheck everything" path)
|
||||
# Neither set → register all tools (backward-compatible default)
|
||||
tools_filter = config.get("tools") or {}
|
||||
include_raw = tools_filter.get("include")
|
||||
include_set = _normalize_name_filter(
|
||||
tools_filter.get("include"), f"mcp_servers.{name}.tools.include"
|
||||
include_raw, f"mcp_servers.{name}.tools.include"
|
||||
)
|
||||
include_active = isinstance(include_raw, (str, list, tuple, set))
|
||||
exclude_set = _normalize_name_filter(
|
||||
tools_filter.get("exclude"), f"mcp_servers.{name}.tools.exclude"
|
||||
)
|
||||
|
||||
def _should_register(tool_name: str) -> bool:
|
||||
if include_set:
|
||||
if include_active:
|
||||
return matches_name_filter(tool_name, include_set)
|
||||
if exclude_set:
|
||||
return not matches_name_filter(tool_name, exclude_set)
|
||||
@@ -7389,15 +7393,19 @@ def _register_from_cache_sync(name: str, config: dict, entry: dict) -> List[str]
|
||||
fingerprint = config_fingerprint(config)
|
||||
tool_timeout = _resolve_tool_timeout(config)
|
||||
tools_filter = config.get("tools") or {}
|
||||
include_raw = tools_filter.get("include")
|
||||
include_set = _normalize_name_filter(
|
||||
tools_filter.get("include"), f"mcp_servers.{name}.tools.include"
|
||||
include_raw, f"mcp_servers.{name}.tools.include"
|
||||
)
|
||||
# include: [] is an explicit empty whitelist (register nothing) — see the
|
||||
# live discovery path above for the full filter rules.
|
||||
include_active = isinstance(include_raw, (str, list, tuple, set))
|
||||
exclude_set = _normalize_name_filter(
|
||||
tools_filter.get("exclude"), f"mcp_servers.{name}.tools.exclude"
|
||||
)
|
||||
|
||||
def _should_register(tool_name: str) -> bool:
|
||||
if include_set:
|
||||
if include_active:
|
||||
return matches_name_filter(tool_name, include_set)
|
||||
if exclude_set:
|
||||
return not matches_name_filter(tool_name, exclude_set)
|
||||
|
||||
Reference in New Issue
Block a user